diff --git a/lark_oapi/channel/channel.py b/lark_oapi/channel/channel.py index 8d4cfa1e1..fd4f3d989 100644 --- a/lark_oapi/channel/channel.py +++ b/lark_oapi/channel/channel.py @@ -1156,6 +1156,7 @@ async def _handle_message_event(self, data: Any) -> None: event_id=event_id, message_event=message, sender=sender, + bot_open_id=self._bot_open_id, ) if inbound is None: return diff --git a/lark_oapi/channel/normalize/pipeline.py b/lark_oapi/channel/normalize/pipeline.py index ffb33f60f..259e55b5c 100644 --- a/lark_oapi/channel/normalize/pipeline.py +++ b/lark_oapi/channel/normalize/pipeline.py @@ -128,6 +128,7 @@ async def process( event_id: Optional[str], message_event: Any, sender: Any, + bot_open_id: Optional[str] = None, ) -> Optional[InboundMessage]: """Return InboundMessage or None if the event was deduped / filtered.""" msg = _message_to_dict(message_event) @@ -159,8 +160,10 @@ async def process( content = parse_message_content(message_type, msg.get("content")) # Process mentions for text / post (node-aligned: extract → resolve). + # ``bot_open_id`` lets extract_mentions flag self-mentions so + # ``InboundMessage.mentioned_bot`` is correct (fixes #134). raw_mentions = msg.get("mentions") or [] - ext = extract_mentions(raw_mentions) + ext = extract_mentions(raw_mentions, bot_open_id=bot_open_id) mentions: List[Mention] = list(ext.mention_list) mentioned_all = ext.mentioned_all if isinstance(content, TextContent): @@ -257,6 +260,7 @@ async def process( sender=sender_identity, mentions=mentions, mentioned_all=mentioned_all, + mentioned_bot=ext.mentioned_bot, reply=reply, content=content, raw=msg if isinstance(msg, dict) else {}, diff --git a/lark_oapi/channel/tests/test_pipeline.py b/lark_oapi/channel/tests/test_pipeline.py index f6a35e32c..648dbb283 100644 --- a/lark_oapi/channel/tests/test_pipeline.py +++ b/lark_oapi/channel/tests/test_pipeline.py @@ -186,3 +186,54 @@ async def fetch_message(mid: str): inbound = await p.process(event_id="e", message_event=msg, sender=_sender()) assert isinstance(inbound.content, InteractiveContent) assert inbound.content.card_version == "v2" + + +# --------------------------------------------------------------------------- +# mentioned_bot (#134): the field must be set when the bot is in mentions[] +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_mentioned_bot_true_when_bot_in_mentions(): + msg = _msg( + mentions=[ + {"key": "@_user_1", "id": {"open_id": "ou_user"}, "name": "Alice"}, + {"key": "@_user_2", "id": {"open_id": "ou_bot"}, "name": "my-bot"}, + ] + ) + p = InboundPipeline(PipelineConfig(), PipelineDeps()) + inbound = await p.process( + event_id="e", message_event=msg, sender=_sender(), bot_open_id="ou_bot" + ) + assert inbound is not None + assert inbound.mentioned_bot is True + # the bot self-mention is excluded from the public mention list + assert [m.open_id for m in inbound.mentions] == ["ou_user"] + + +@pytest.mark.asyncio +async def test_mentioned_bot_false_without_bot_open_id(): + msg = _msg( + mentions=[{"key": "@_user_1", "id": {"open_id": "ou_user"}, "name": "Alice"}] + ) + p = InboundPipeline(PipelineConfig(), PipelineDeps()) + inbound = await p.process(event_id="e", message_event=msg, sender=_sender()) + assert inbound is not None + assert inbound.mentioned_bot is False + + +@pytest.mark.asyncio +async def test_mentioned_bot_false_when_bot_not_mentioned(): + msg = _msg( + mentions=[ + {"key": "@_user_1", "id": {"open_id": "ou_user"}, "name": "Alice"}, + {"key": "@_user_2", "id": {"open_id": "ou_other"}, "name": "Bob"}, + ] + ) + p = InboundPipeline(PipelineConfig(), PipelineDeps()) + inbound = await p.process( + event_id="e", message_event=msg, sender=_sender(), bot_open_id="ou_bot" + ) + assert inbound is not None + assert inbound.mentioned_bot is False + assert len(inbound.mentions) == 2