Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/langbot/pkg/api/http/service/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async def get_runtime_bot_info(self, bot_uuid: str, include_secret: bool = True)
adapter_runtime_values['bot_account_id'] = runtime_bot.adapter.bot_account_id

# Webhook URL for unified webhook adapters (independent of bot running state)
if persistence_bot['adapter'] in ['wecom', 'wecombot', 'officialaccount', 'qqofficial', 'slack', 'wecomcs', 'LINE']:
if persistence_bot['adapter'] in ['wecom', 'wecombot', 'officialaccount', 'qqofficial', 'slack', 'wecomcs', 'LINE', 'lark']:
webhook_prefix = self.ap.instance_config.data['api'].get('webhook_prefix', 'http://127.0.0.1:5300')
webhook_url = f'/bots/{bot_uuid}'
adapter_runtime_values['webhook_url'] = webhook_url
Expand Down
122 changes: 75 additions & 47 deletions src/langbot/pkg/platform/sources/lark.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):

message_converter: LarkMessageConverter = LarkMessageConverter()
event_converter: LarkEventConverter = LarkEventConverter()
cipher: AESCipher

listeners: typing.Dict[
typing.Type[platform_events.Event],
Expand All @@ -396,51 +397,12 @@ class LarkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
card_id_dict: dict[str, str] # 消息id到卡片id的映射,便于创建卡片后的发送消息到指定卡片

seq: int # 用于在发送卡片消息中识别消息顺序,直接以seq作为标识
bot_uuid: str = None # 机器人UUID

def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger, **kwargs):
quart_app = quart.Quart(__name__)

@quart_app.route('/lark/callback', methods=['POST'])
async def lark_callback():
try:
data = await quart.request.json

if 'encrypt' in data:
cipher = AESCipher(config['encrypt-key'])
data = cipher.decrypt_string(data['encrypt'])
data = json.loads(data)

type = data.get('type')
if type is None:
context = EventContext(data)
type = context.header.event_type

if 'url_verification' == type:
# todo 验证verification token
return {'challenge': data.get('challenge')}
context = EventContext(data)
type = context.header.event_type
p2v1 = P2ImMessageReceiveV1()
p2v1.header = context.header
event = P2ImMessageReceiveV1Data()
event.message = EventMessage(context.event['message'])
event.sender = EventSender(context.event['sender'])
p2v1.event = event
p2v1.schema = context.schema
if 'im.message.receive_v1' == type:
try:
event = await self.event_converter.target2yiri(p2v1, self.api_client)
except Exception:
await self.logger.error(f'Error in lark callback: {traceback.format_exc()}')

if event.__class__ in self.listeners:
await self.listeners[event.__class__](event, self)

return {'code': 200, 'message': 'ok'}
except Exception:
await self.logger.error(f'Error in lark callback: {traceback.format_exc()}')
return {'code': 500, 'message': 'error'}


async def on_message(event: lark_oapi.im.v1.P2ImMessageReceiveV1):
lb_event = await self.event_converter.target2yiri(event, self.api_client)

Expand All @@ -457,6 +419,7 @@ def sync_on_message(event: lark_oapi.im.v1.P2ImMessageReceiveV1):

bot = lark_oapi.ws.Client(config['app_id'], config['app_secret'], event_handler=event_handler)
api_client = lark_oapi.Client.builder().app_id(config['app_id']).app_secret(config['app_secret']).build()
cipher = AESCipher(config.get('encrypt-key', ''))

super().__init__(
config=config,
Expand All @@ -469,6 +432,7 @@ def sync_on_message(event: lark_oapi.im.v1.P2ImMessageReceiveV1):
bot=bot,
api_client=api_client,
bot_account_id=bot_account_id,
cipher=cipher,
**kwargs,
)

Expand Down Expand Up @@ -853,6 +817,57 @@ def unregister_listener(
):
self.listeners.pop(event_type)

def set_bot_uuid(self, bot_uuid: str):
"""设置 bot UUID(用于生成 webhook URL)"""
self.bot_uuid = bot_uuid

async def handle_unified_webhook(self, bot_uuid: str, path: str, request):
"""处理统一 webhook 请求。
Args:
bot_uuid: Bot 的 UUID
path: 子路径(如果有的话)
request: Quart Request 对象
Returns:
响应数据
"""
try:
data = await request.json

if 'encrypt' in data:
data = self.cipher.decrypt_string(data['encrypt'])
data = json.loads(data)
type = data.get('type')
if type is None:
context = EventContext(data)
type = context.header.event_type

if 'url_verification' == type:
# todo 验证verification token
return {'challenge': data.get('challenge')}
context = EventContext(data)
type = context.header.event_type
p2v1 = P2ImMessageReceiveV1()
p2v1.header = context.header
event = P2ImMessageReceiveV1Data()
event.message = EventMessage(context.event['message'])
event.sender = EventSender(context.event['sender'])
p2v1.event = event
p2v1.schema = context.schema
if 'im.message.receive_v1' == type:
try:
event = await self.event_converter.target2yiri(p2v1, self.api_client)
except Exception:
await self.logger.error(f'Error in lark callback: {traceback.format_exc()}')

if event.__class__ in self.listeners:
await self.listeners[event.__class__](event, self)

return {'code': 200, 'message': 'ok'}
except Exception:
await self.logger.error(f'Error in lark callback: {traceback.format_exc()}')
return {'code': 500, 'message': 'error'}


async def run_async(self):
port = self.config['port']
enable_webhook = self.config['enable-webhook']
Expand All @@ -870,15 +885,28 @@ async def run_async(self):
raise e
else:

async def shutdown_trigger_placeholder():
# 统一 webhook 模式下,不启动独立的 Quart 应用
# 保持运行但不启动独立端口

# 打印 webhook 回调地址
if self.bot_uuid and hasattr(self.logger, 'ap'):
try:
api_port = self.logger.ap.instance_config.data['api']['port']
webhook_url = f'http://127.0.0.1:{api_port}/bots/{self.bot_uuid}'
webhook_url_public = f'http://<Your-Public-IP>:{api_port}/bots/{self.bot_uuid}'

await self.logger.info('Lark 机器人 Webhook 回调地址:')
await self.logger.info(f' 本地地址: {webhook_url}')
await self.logger.info(f' 公网地址: {webhook_url_public}')
await self.logger.info('请在 Lark 机器人后台配置此回调地址')
except Exception as e:
await self.logger.warning(f'无法生成 webhook URL: {e}')

async def keep_alive():
while True:
await asyncio.sleep(1)

await self.quart_app.run_task(
host='0.0.0.0',
port=port,
shutdown_trigger=shutdown_trigger_placeholder,
)
await keep_alive()

async def kill(self) -> bool:
# 需要断开连接,不然旧的连接会继续运行,导致飞书消息来时会随机选择一个连接
Expand Down
10 changes: 0 additions & 10 deletions src/langbot/pkg/platform/sources/lark.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,6 @@ spec:
type: boolean
required: true
default: false
- name: port
label:
en_US: Webhook Port
zh_Hans: Webhook端口
description:
en_US: Only valid when webhook mode is enabled, please fill in the webhook port
zh_Hans: 仅在启用 Webhook 模式时有效,请填写 Webhook 端口
type: integer
required: true
default: 2285
- name: encrypt-key
label:
en_US: Encrypt Key
Expand Down
Loading
Loading