From 84992bcd6c608fa996f8f4828e9c9903ccd7825b Mon Sep 17 00:00:00 2001 From: liuwei Date: Wed, 29 Apr 2026 10:36:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DMaiBot=E9=80=82=E9=85=8D?= =?UTF-8?q?=E5=99=A8=E7=BE=A4=E8=81=8A=E8=AF=AF=E5=88=A4=E4=B8=BA=E7=A7=81?= =?UTF-8?q?=E8=81=8A=E5=B9=B6=E8=A1=A5=E5=85=85=E8=B7=AF=E7=94=B1=E8=AF=8A?= =?UTF-8?q?=E6=96=AD=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 变更项:\n1. 新增统一会话路由解析函数 _resolve_chat_route,按 plugin_roomid -> wx_roomid -> wx_to_user -> 原始报文 From/ToUserName 逐级兜底识别群聊。\n2. can_process 与转发构包统一使用同一套路由判定,避免采集判定和发送判定不一致。\n3. 在 additional_config 中新增 abot_route_type 与 abot_route_source,便于服务端链路排查。\n4. 补充详细路由日志,记录 route_type、route_source、plugin_roomid、wx_roomid、wx_to_user、sender、message_id。\n5. 入队日志增加 route_type/route_source 字段,便于快速筛查是否持续发生私聊误判。 --- plugins/maibot_adapter/main.py | 73 +++++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/plugins/maibot_adapter/main.py b/plugins/maibot_adapter/main.py index 3f288ad..6277c03 100644 --- a/plugins/maibot_adapter/main.py +++ b/plugins/maibot_adapter/main.py @@ -166,8 +166,8 @@ class MaiBotAdapterPlugin(MessagePluginInterface): if isinstance(full_msg, WxMessage) and full_msg.from_self(): return False - roomid = str(message.get("roomid", "") or "").strip() - if roomid: + chat_route = self._resolve_chat_route(message) + if chat_route["is_group"]: return self._collect_group_messages return self._collect_private_messages @@ -193,7 +193,8 @@ class MaiBotAdapterPlugin(MessagePluginInterface): try: outbound_queue.put_nowait(payload) self.LOG.info( - f"[{self.name}] 消息已入队: roomid={payload['roomid']}, sender={payload['sender']}, " + f"[{self.name}] 消息已入队: route_type={payload['route_type']}, roomid={payload['roomid']}, " + f"route_source={payload['route_source']}, sender={payload['sender']}, " f"msg_type={payload['message_type']}, queue_size={outbound_queue.qsize()}, " f"content_preview={normalized_content[:120]}" ) @@ -491,7 +492,11 @@ class MaiBotAdapterPlugin(MessagePluginInterface): def _build_outbound_payload(self, message: Dict[str, Any], normalized_content: str) -> Dict[str, Any]: """把微信消息包装成 MaiBot 官方 API Server 的 APIMessageBase 结构。""" full_msg = message.get("full_wx_msg") - roomid = str(message.get("roomid", "") or "").strip() + # 会话路由统一在这里解析,避免“can_process 一套、转发又一套”导致判定漂移。 + chat_route = self._resolve_chat_route(message) + roomid = chat_route["roomid"] + route_type = chat_route["route_type"] + route_source = chat_route["route_source"] sender = str(message.get("sender", "") or "").strip() msg_type = self._resolve_message_type(message) timestamp = self._resolve_message_timestamp(message, full_msg) @@ -500,6 +505,15 @@ class MaiBotAdapterPlugin(MessagePluginInterface): sender_name = self._resolve_sender_name(message, sender) group_name = self._resolve_group_name(message, roomid) + # 路由诊断日志:这里把关键字段一次性打全,便于定位“群消息为何被识别成私聊”。 + self.LOG.info( + f"[{self.name}] 路由判定: route_type={route_type}, route_source={route_source}, " + f"plugin_roomid={str(message.get('roomid', '') or '').strip()}, " + f"wx_roomid={str(getattr(full_msg, 'roomid', '') or '').strip() if isinstance(full_msg, WxMessage) else ''}, " + f"wx_to_user={str(getattr(full_msg, 'to_user', '') or '').strip() if isinstance(full_msg, WxMessage) else ''}, " + f"sender={sender}, message_id={message_id}" + ) + sender_info: Dict[str, Any] = { "user_info": { "platform": self._platform_name, @@ -545,6 +559,9 @@ class MaiBotAdapterPlugin(MessagePluginInterface): "is_at": bool(message.get("is_at", False)), "wx_message_type": msg_type, "collect_only": True, + # 额外把本地路由判定结果透传给 MaiBot,便于服务端/日志排查“为何被识别成私聊”。 + "abot_route_type": route_type, + "abot_route_source": route_source, }, "sender_info": sender_info, "receiver_info": receiver_info, @@ -560,6 +577,8 @@ class MaiBotAdapterPlugin(MessagePluginInterface): } return { + "route_type": route_type, + "route_source": route_source, "roomid": roomid, "sender": sender, "message_type": msg_type, @@ -568,6 +587,51 @@ class MaiBotAdapterPlugin(MessagePluginInterface): "api_message": api_message, } + def _resolve_chat_route(self, message: Dict[str, Any]) -> Dict[str, str]: + """解析消息的群聊/私聊路由,兼容上游字段缺失场景。""" + full_msg = message.get("full_wx_msg") + + # 先看插件消息里已经带好的 roomid,这是最直接、最便宜的一跳。 + plugin_roomid = str(message.get("roomid", "") or "").strip() + if plugin_roomid.endswith("@chatroom"): + return {"is_group": True, "route_type": "group", "roomid": plugin_roomid, "route_source": "plugin_roomid"} + + # 再看 WxMessage 里计算过的 roomid。 + wx_roomid = "" + wx_to_user = "" + wx_from_group = False + if isinstance(full_msg, WxMessage): + wx_roomid = str(getattr(full_msg, "roomid", "") or "").strip() + wx_to_user = str(getattr(full_msg, "to_user", "") or "").strip() + try: + wx_from_group = bool(full_msg.from_group()) + except Exception: + wx_from_group = False + + if wx_roomid.endswith("@chatroom"): + return {"is_group": True, "route_type": "group", "roomid": wx_roomid, "route_source": "wx_roomid"} + + # 某些边界消息里,roomid 可能没落下来,但 to_user 仍是 chatroom。 + if wx_to_user.endswith("@chatroom"): + return {"is_group": True, "route_type": "group", "roomid": wx_to_user, "route_source": "wx_to_user"} + + # 最后兜底原始报文,避免上游字段偶发缺失时把群消息误判成私聊。 + raw_data = getattr(full_msg, "raw_data", {}) if isinstance(full_msg, WxMessage) else {} + raw_from = "" + raw_to = "" + if isinstance(raw_data, dict): + raw_from = str(((raw_data.get("FromUserName") or {}).get("string")) or "").strip() + raw_to = str(((raw_data.get("ToUserName") or {}).get("string")) or "").strip() + + if raw_from.endswith("@chatroom"): + return {"is_group": True, "route_type": "group", "roomid": raw_from, "route_source": "raw_from_user"} + if raw_to.endswith("@chatroom"): + return {"is_group": True, "route_type": "group", "roomid": raw_to, "route_source": "raw_to_user"} + + # 到这里仍不是群聊,就按私聊处理,但保留详细来源,方便日志反查。 + private_source = "wx_from_group_flag" if wx_from_group else "private_default" + return {"is_group": False, "route_type": "private", "roomid": "", "route_source": private_source} + def _resolve_reply_route(self, message_info: Dict[str, Any]) -> Optional[Dict[str, str]]: """从 MaiBot 返回的 message_info 中解析微信路由。""" receiver_info = message_info.get("receiver_info") or {} @@ -794,4 +858,3 @@ class MaiBotAdapterPlugin(MessagePluginInterface): self.LOG.warning(f"[{self.name}] 关闭 ClientSession 失败: {exc}") finally: self._client_session = None -