From 1b368a33dcb66c16cbe5e8b9cd5f49e5a01d9582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=89=E6=B3=89?= Date: Wed, 1 Apr 2026 23:23:23 +0800 Subject: [PATCH] fix(feishu): match bot's own open_id in _is_bot_mentioned to prevent cross-bot false positives Previously, _is_bot_mentioned used a heuristic (no user_id + open_id prefix "ou_") which caused other bots in the same group to falsely think they were mentioned. Now fetches the bot's own open_id via GET /open-apis/bot/v3/info at startup and does an exact match. --- nanobot/channels/feishu.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/nanobot/channels/feishu.py b/nanobot/channels/feishu.py index 1128c0e16..323b51fe5 100644 --- a/nanobot/channels/feishu.py +++ b/nanobot/channels/feishu.py @@ -298,6 +298,7 @@ class FeishuChannel(BaseChannel): self._processed_message_ids: OrderedDict[str, None] = OrderedDict() # Ordered dedup cache self._loop: asyncio.AbstractEventLoop | None = None self._stream_bufs: dict[str, _FeishuStreamBuf] = {} + self._bot_open_id: str | None = None @staticmethod def _register_optional_event(builder: Any, method_name: str, handler: Any) -> Any: @@ -378,6 +379,15 @@ class FeishuChannel(BaseChannel): self._ws_thread = threading.Thread(target=run_ws, daemon=True) self._ws_thread.start() + # Fetch bot's own open_id for accurate @mention matching + self._bot_open_id = await asyncio.get_running_loop().run_in_executor( + None, self._fetch_bot_open_id + ) + if self._bot_open_id: + logger.info("Feishu bot open_id: {}", self._bot_open_id) + else: + logger.warning("Could not fetch bot open_id; @mention matching may be inaccurate") + logger.info("Feishu bot started with WebSocket long connection") logger.info("No public IP required - using WebSocket to receive events") @@ -396,6 +406,20 @@ class FeishuChannel(BaseChannel): self._running = False logger.info("Feishu bot stopped") + def _fetch_bot_open_id(self) -> str | None: + """Fetch the bot's own open_id via GET /open-apis/bot/v3/info.""" + from lark_oapi.api.bot.v3 import GetBotInfoRequest + try: + request = GetBotInfoRequest.builder().build() + response = self._client.bot.v3.bot_info.get(request) + if response.success() and response.data and response.data.bot: + return getattr(response.data.bot, "open_id", None) + logger.warning("Failed to get bot info: code={}, msg={}", response.code, response.msg) + return None + except Exception as e: + logger.warning("Error fetching bot info: {}", e) + return None + def _is_bot_mentioned(self, message: Any) -> bool: """Check if the bot is @mentioned in the message.""" raw_content = message.content or "" @@ -406,8 +430,8 @@ class FeishuChannel(BaseChannel): mid = getattr(mention, "id", None) if not mid: continue - # Bot mentions have no user_id (None or "") but a valid open_id - if not getattr(mid, "user_id", None) and (getattr(mid, "open_id", None) or "").startswith("ou_"): + mention_open_id = getattr(mid, "open_id", None) or "" + if self._bot_open_id and mention_open_id == self._bot_open_id: return True return False