diff --git a/astrbot/core/platform/sources/lark/lark_adapter.py b/astrbot/core/platform/sources/lark/lark_adapter.py index 60e8e0d931..c8d71f1c60 100644 --- a/astrbot/core/platform/sources/lark/lark_adapter.py +++ b/astrbot/core/platform/sources/lark/lark_adapter.py @@ -470,11 +470,19 @@ async def send_by_session( if session.message_type == MessageType.GROUP_MESSAGE: id_type = "chat_id" receive_id = session.session_id - if "%" in receive_id: - receive_id = receive_id.split("%")[1] + # 从 session_id 中提取真实的 chat_id,仅去除已知后缀(%thread% / %root%) + for _suffix in ("%thread%", "%root%"): + if _suffix in receive_id: + receive_id = receive_id.split(_suffix)[0] + break else: id_type = "open_id" receive_id = session.session_id + # 单聊也需要去除已知后缀(%thread% / %root%) + for _suffix in ("%thread%", "%root%"): + if _suffix in receive_id: + receive_id = receive_id.split(_suffix)[0] + break # 复用 LarkMessageEvent 中的通用发送逻辑 await LarkMessageEvent.send_message_chain( @@ -580,20 +588,43 @@ async def convert_msg(self, event: lark.im.v1.P2ImMessageReceiveV1) -> None: user_id=event.event.sender.sender_id.open_id, nickname=event.event.sender.sender_id.open_id[:8], ) + # 构建 session_id:按话题/回复链隔离上下文 if abm.type == MessageType.GROUP_MESSAGE: - abm.session_id = abm.group_id + base_id = abm.group_id or "" + if message.thread_id: + # 话题群中的消息,按 thread_id 隔离 + abm.session_id = f"{base_id}%thread%{message.thread_id}" + elif message.root_id: + # 群聊中的回复链,按 root_id 隔离 + abm.session_id = f"{base_id}%root%{message.root_id}" + else: + abm.session_id = base_id else: - abm.session_id = abm.sender.user_id + base_id = abm.sender.user_id + if message.thread_id: + abm.session_id = f"{base_id}%thread%{message.thread_id}" + elif message.root_id: + # 单聊中的回复链,按 root_id 隔离 + abm.session_id = f"{base_id}%root%{message.root_id}" + else: + abm.session_id = base_id - await self.handle_msg(abm) + # 判断是否需要通过 reply_in_thread 创建新话题 + # 没有已存在的 thread_id 时,需要 reply_in_thread=True 创建话题 + # 已在话题中的消息回复自然在话题内,无需 reply_in_thread + _should_reply_in_thread = not bool(message.thread_id) + await self.handle_msg(abm, should_reply_in_thread=_should_reply_in_thread) - async def handle_msg(self, abm: AstrBotMessage) -> None: + async def handle_msg( + self, abm: AstrBotMessage, should_reply_in_thread: bool = False + ) -> None: event = LarkMessageEvent( message_str=abm.message_str, message_obj=abm, platform_meta=self.meta(), session_id=abm.session_id, bot=self.lark_api, + should_reply_in_thread=should_reply_in_thread, ) self._event_queue.put_nowait(event) diff --git a/astrbot/core/platform/sources/lark/lark_event.py b/astrbot/core/platform/sources/lark/lark_event.py index 0959f63df0..eee29369c0 100644 --- a/astrbot/core/platform/sources/lark/lark_event.py +++ b/astrbot/core/platform/sources/lark/lark_event.py @@ -48,9 +48,11 @@ def __init__( platform_meta, session_id, bot: lark.Client, + should_reply_in_thread: bool = False, ) -> None: super().__init__(message_str, message_obj, platform_meta, session_id) self.bot = bot + self.should_reply_in_thread = should_reply_in_thread @staticmethod async def _send_im_message( @@ -61,6 +63,7 @@ async def _send_im_message( reply_message_id: str | None = None, receive_id: str | None = None, receive_id_type: str | None = None, + reply_in_thread: bool = False, ) -> bool: """发送飞书 IM 消息的通用辅助函数 @@ -71,6 +74,7 @@ async def _send_im_message( reply_message_id: 回复的消息ID(用于回复消息) receive_id: 接收者ID(用于主动发送) receive_id_type: 接收者ID类型(用于主动发送) + reply_in_thread: 是否在话题中回复 Returns: 是否发送成功 @@ -88,7 +92,7 @@ async def _send_im_message( .content(content) .msg_type(msg_type) .uuid(str(uuid.uuid4())) - .reply_in_thread(False) + .reply_in_thread(reply_in_thread) .build() ) .build() @@ -287,6 +291,7 @@ async def send_message_chain( reply_message_id: str | None = None, receive_id: str | None = None, receive_id_type: str | None = None, + reply_in_thread: bool = False, ) -> None: """通用的消息链发送方法 @@ -296,6 +301,7 @@ async def send_message_chain( reply_message_id: 回复的消息ID(用于回复消息) receive_id: 接收者ID(用于主动发送) receive_id_type: 接收者ID类型,如 'open_id', 'chat_id'(用于主动发送) + reply_in_thread: 是否在话题中回复 """ if lark_client.im is None: logger.error("[Lark] API Client im 模块未初始化") @@ -337,22 +343,38 @@ async def send_message_chain( reply_message_id=reply_message_id, receive_id=receive_id, receive_id_type=receive_id_type, + reply_in_thread=reply_in_thread, ) # 发送附件 for file_comp in file_components: await LarkMessageEvent._send_file_message( - file_comp, lark_client, reply_message_id, receive_id, receive_id_type + file_comp, + lark_client, + reply_message_id, + receive_id, + receive_id_type, + reply_in_thread=reply_in_thread, ) for audio_comp in audio_components: await LarkMessageEvent._send_audio_message( - audio_comp, lark_client, reply_message_id, receive_id, receive_id_type + audio_comp, + lark_client, + reply_message_id, + receive_id, + receive_id_type, + reply_in_thread=reply_in_thread, ) for media_comp in media_components: await LarkMessageEvent._send_media_message( - media_comp, lark_client, reply_message_id, receive_id, receive_id_type + media_comp, + lark_client, + reply_message_id, + receive_id, + receive_id_type, + reply_in_thread=reply_in_thread, ) async def send(self, message: MessageChain) -> None: @@ -361,6 +383,7 @@ async def send(self, message: MessageChain) -> None: message, self.bot, reply_message_id=self.message_obj.message_id, + reply_in_thread=self.should_reply_in_thread, ) await super().send(message) @@ -371,6 +394,7 @@ async def _send_file_message( reply_message_id: str | None = None, receive_id: str | None = None, receive_id_type: str | None = None, + reply_in_thread: bool = False, ) -> None: """发送文件消息 @@ -396,6 +420,7 @@ async def _send_file_message( reply_message_id=reply_message_id, receive_id=receive_id, receive_id_type=receive_id_type, + reply_in_thread=reply_in_thread, ) @staticmethod @@ -405,6 +430,7 @@ async def _send_audio_message( reply_message_id: str | None = None, receive_id: str | None = None, receive_id_type: str | None = None, + reply_in_thread: bool = False, ) -> None: """发送音频消息 @@ -469,6 +495,7 @@ async def _send_audio_message( reply_message_id=reply_message_id, receive_id=receive_id, receive_id_type=receive_id_type, + reply_in_thread=reply_in_thread, ) @staticmethod @@ -478,6 +505,7 @@ async def _send_media_message( reply_message_id: str | None = None, receive_id: str | None = None, receive_id_type: str | None = None, + reply_in_thread: bool = False, ) -> None: """发送视频消息 @@ -542,6 +570,7 @@ async def _send_media_message( reply_message_id=reply_message_id, receive_id=receive_id, receive_id_type=receive_id_type, + reply_in_thread=reply_in_thread, ) async def react(self, emoji: str) -> None: @@ -633,6 +662,7 @@ async def _send_card_message( reply_message_id: str | None = None, receive_id: str | None = None, receive_id_type: str | None = None, + reply_in_thread: bool = False, ) -> bool: """将卡片实体作为 interactive 消息发送。""" content = json.dumps( @@ -646,6 +676,7 @@ async def _send_card_message( reply_message_id=reply_message_id, receive_id=receive_id, receive_id_type=receive_id_type, + reply_in_thread=reply_in_thread, ) async def _update_streaming_text( @@ -747,6 +778,10 @@ async def send_streaming(self, generator, use_fallback: bool = False): 使用解耦发送循环,LLM token 到达时只更新 buffer 并唤醒发送协程, 发送频率由网络 RTT 自然限流。 """ + # 非话题消息:通过 reply_in_thread=True 创建新话题,同时使用流式卡片 + if self.should_reply_in_thread: + logger.info("[Lark] 非话题消息,将通过 reply_in_thread=True 创建新话题") + # Step 1: 创建流式卡片实体 card_id = await self._create_streaming_card() if not card_id: @@ -758,6 +793,7 @@ async def send_streaming(self, generator, use_fallback: bool = False): sent = await self._send_card_message( card_id, reply_message_id=self.message_obj.message_id, + reply_in_thread=self.should_reply_in_thread, ) if not sent: logger.error("[Lark] 发送流式卡片消息失败,回退到非流式发送")