mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-14 23:03:59 +00:00
fix(feishu): strip leading bot mention before commands
This commit is contained in:
parent
fa423dffbc
commit
894811db8b
@ -483,7 +483,10 @@ class FeishuChannel(BaseChannel):
|
|||||||
|
|
||||||
for mention in mentions:
|
for mention in mentions:
|
||||||
key = mention.key or None
|
key = mention.key or None
|
||||||
if not key or key not in text:
|
if not key:
|
||||||
|
continue
|
||||||
|
pattern = rf"{re.escape(key)}(?=\s|$)"
|
||||||
|
if not re.search(pattern, text):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
user_id_obj = mention.id or None
|
user_id_obj = mention.id or None
|
||||||
@ -502,7 +505,40 @@ class FeishuChannel(BaseChannel):
|
|||||||
else:
|
else:
|
||||||
replacement = f"@{name}"
|
replacement = f"@{name}"
|
||||||
|
|
||||||
text = text.replace(key, replacement)
|
text = re.sub(pattern, replacement, text)
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _is_bot_mention_event(self, mention: Any) -> bool:
|
||||||
|
mid = getattr(mention, "id", None)
|
||||||
|
if not mid:
|
||||||
|
return False
|
||||||
|
|
||||||
|
mention_open_id = getattr(mid, "open_id", None) or ""
|
||||||
|
bot_open_id = getattr(self, "_bot_open_id", None) or ""
|
||||||
|
if bot_open_id:
|
||||||
|
return mention_open_id == bot_open_id
|
||||||
|
|
||||||
|
# Fallback heuristic when bot open_id is unavailable.
|
||||||
|
return not getattr(mid, "user_id", None) and mention_open_id.startswith("ou_")
|
||||||
|
|
||||||
|
def _strip_leading_bot_mention(
|
||||||
|
self, text: str, mentions: list[MentionEvent] | None
|
||||||
|
) -> str:
|
||||||
|
"""Remove a required leading bot mention before slash command routing."""
|
||||||
|
if not mentions or not text:
|
||||||
|
return text
|
||||||
|
|
||||||
|
candidate = text.lstrip()
|
||||||
|
for mention in mentions:
|
||||||
|
key = getattr(mention, "key", None) or ""
|
||||||
|
if not key or not re.match(rf"{re.escape(key)}(?=\s|$)", candidate):
|
||||||
|
continue
|
||||||
|
if not self._is_bot_mention_event(mention):
|
||||||
|
continue
|
||||||
|
|
||||||
|
stripped = candidate[len(key) :].strip()
|
||||||
|
return stripped or text
|
||||||
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
@ -513,17 +549,8 @@ class FeishuChannel(BaseChannel):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
for mention in getattr(message, "mentions", None) or []:
|
for mention in getattr(message, "mentions", None) or []:
|
||||||
mid = getattr(mention, "id", None)
|
if self._is_bot_mention_event(mention):
|
||||||
if not mid:
|
return True
|
||||||
continue
|
|
||||||
mention_open_id = getattr(mid, "open_id", None) or ""
|
|
||||||
if self._bot_open_id:
|
|
||||||
if mention_open_id == self._bot_open_id:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
# Fallback heuristic when bot open_id is unavailable
|
|
||||||
if not getattr(mid, "user_id", None) and mention_open_id.startswith("ou_"):
|
|
||||||
return True
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _is_group_message_for_bot(self, message: Any) -> bool:
|
def _is_group_message_for_bot(self, message: Any) -> bool:
|
||||||
@ -1747,6 +1774,7 @@ class FeishuChannel(BaseChannel):
|
|||||||
text = content_json.get("text", "")
|
text = content_json.get("text", "")
|
||||||
if text:
|
if text:
|
||||||
mentions = getattr(message, "mentions", None)
|
mentions = getattr(message, "mentions", None)
|
||||||
|
text = self._strip_leading_bot_mention(text, mentions)
|
||||||
text = self._resolve_mentions(text, mentions)
|
text = self._resolve_mentions(text, mentions)
|
||||||
content_parts.append(text)
|
content_parts.append(text)
|
||||||
|
|
||||||
|
|||||||
@ -56,6 +56,7 @@ def _make_feishu_event(
|
|||||||
sender_open_id: str = "ou_alice",
|
sender_open_id: str = "ou_alice",
|
||||||
parent_id: str | None = None,
|
parent_id: str | None = None,
|
||||||
root_id: str | None = None,
|
root_id: str | None = None,
|
||||||
|
mentions=None,
|
||||||
):
|
):
|
||||||
message = SimpleNamespace(
|
message = SimpleNamespace(
|
||||||
message_id=message_id,
|
message_id=message_id,
|
||||||
@ -65,7 +66,7 @@ def _make_feishu_event(
|
|||||||
content=content,
|
content=content,
|
||||||
parent_id=parent_id,
|
parent_id=parent_id,
|
||||||
root_id=root_id,
|
root_id=root_id,
|
||||||
mentions=[],
|
mentions=mentions or [],
|
||||||
)
|
)
|
||||||
sender = SimpleNamespace(
|
sender = SimpleNamespace(
|
||||||
sender_type="user",
|
sender_type="user",
|
||||||
@ -547,6 +548,71 @@ async def test_on_message_no_extra_api_call_when_no_parent_id() -> None:
|
|||||||
assert len(captured) == 1
|
assert len(captured) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_on_message_strips_required_leading_bot_mention_for_commands() -> None:
|
||||||
|
channel = _make_feishu_channel(group_policy="mention")
|
||||||
|
channel._processed_message_ids.clear()
|
||||||
|
channel._bot_open_id = "ou_bot"
|
||||||
|
captured = []
|
||||||
|
|
||||||
|
async def _capture(**kwargs):
|
||||||
|
captured.append(kwargs)
|
||||||
|
|
||||||
|
channel._handle_message = _capture
|
||||||
|
mention = SimpleNamespace(
|
||||||
|
key="@_user_1",
|
||||||
|
name="nanobot",
|
||||||
|
id=SimpleNamespace(open_id="ou_bot", user_id=None),
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(channel, "_add_reaction", return_value=None):
|
||||||
|
await channel._on_message(
|
||||||
|
_make_feishu_event(
|
||||||
|
chat_type="group",
|
||||||
|
content=json.dumps({"text": "@_user_1 /new"}),
|
||||||
|
mentions=[mention],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(captured) == 1
|
||||||
|
assert captured[0]["content"] == "/new"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_on_message_keeps_longer_mention_key_that_shares_bot_prefix() -> None:
|
||||||
|
channel = _make_feishu_channel(group_policy="mention")
|
||||||
|
channel._processed_message_ids.clear()
|
||||||
|
channel._bot_open_id = "ou_bot"
|
||||||
|
captured = []
|
||||||
|
|
||||||
|
async def _capture(**kwargs):
|
||||||
|
captured.append(kwargs)
|
||||||
|
|
||||||
|
channel._handle_message = _capture
|
||||||
|
bot_mention = SimpleNamespace(
|
||||||
|
key="@_user_1",
|
||||||
|
name="nanobot",
|
||||||
|
id=SimpleNamespace(open_id="ou_bot", user_id=None),
|
||||||
|
)
|
||||||
|
user_mention = SimpleNamespace(
|
||||||
|
key="@_user_10",
|
||||||
|
name="Alice",
|
||||||
|
id=SimpleNamespace(open_id="ou_alice", user_id=None),
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch.object(channel, "_add_reaction", return_value=None):
|
||||||
|
await channel._on_message(
|
||||||
|
_make_feishu_event(
|
||||||
|
chat_type="group",
|
||||||
|
content=json.dumps({"text": "@_user_10 /new @_user_1"}),
|
||||||
|
mentions=[bot_mention, user_mention],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(captured) == 1
|
||||||
|
assert captured[0]["content"] == "@Alice (ou_alice) /new @nanobot (ou_bot)"
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Inbound media tests
|
# Inbound media tests
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user