mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-06-13 22:34:06 +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:
|
||||
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
|
||||
|
||||
user_id_obj = mention.id or None
|
||||
@ -502,7 +505,40 @@ class FeishuChannel(BaseChannel):
|
||||
else:
|
||||
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
|
||||
|
||||
@ -513,17 +549,8 @@ class FeishuChannel(BaseChannel):
|
||||
return True
|
||||
|
||||
for mention in getattr(message, "mentions", None) or []:
|
||||
mid = getattr(mention, "id", None)
|
||||
if not mid:
|
||||
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
|
||||
if self._is_bot_mention_event(mention):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _is_group_message_for_bot(self, message: Any) -> bool:
|
||||
@ -1747,6 +1774,7 @@ class FeishuChannel(BaseChannel):
|
||||
text = content_json.get("text", "")
|
||||
if text:
|
||||
mentions = getattr(message, "mentions", None)
|
||||
text = self._strip_leading_bot_mention(text, mentions)
|
||||
text = self._resolve_mentions(text, mentions)
|
||||
content_parts.append(text)
|
||||
|
||||
|
||||
@ -56,6 +56,7 @@ def _make_feishu_event(
|
||||
sender_open_id: str = "ou_alice",
|
||||
parent_id: str | None = None,
|
||||
root_id: str | None = None,
|
||||
mentions=None,
|
||||
):
|
||||
message = SimpleNamespace(
|
||||
message_id=message_id,
|
||||
@ -65,7 +66,7 @@ def _make_feishu_event(
|
||||
content=content,
|
||||
parent_id=parent_id,
|
||||
root_id=root_id,
|
||||
mentions=[],
|
||||
mentions=mentions or [],
|
||||
)
|
||||
sender = SimpleNamespace(
|
||||
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
|
||||
|
||||
|
||||
@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
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user