mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-04-02 09:22:36 +00:00
* feat(telegram): include author context in reply tags (#2605) * fix(telegram): handle missing attributes in reply_user safely
This commit is contained in:
parent
2780292c9d
commit
c41ab9e52e
@ -637,8 +637,7 @@ class TelegramChannel(BaseChannel):
|
||||
"reply_to_message_id": getattr(reply_to, "message_id", None) if reply_to else None,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _extract_reply_context(message) -> str | None:
|
||||
async def _extract_reply_context(self, message) -> str | None:
|
||||
"""Extract text from the message being replied to, if any."""
|
||||
reply = getattr(message, "reply_to_message", None)
|
||||
if not reply:
|
||||
@ -646,7 +645,21 @@ class TelegramChannel(BaseChannel):
|
||||
text = getattr(reply, "text", None) or getattr(reply, "caption", None) or ""
|
||||
if len(text) > TELEGRAM_REPLY_CONTEXT_MAX_LEN:
|
||||
text = text[:TELEGRAM_REPLY_CONTEXT_MAX_LEN] + "..."
|
||||
return f"[Reply to: {text}]" if text else None
|
||||
|
||||
if not text:
|
||||
return None
|
||||
|
||||
bot_id, _ = await self._ensure_bot_identity()
|
||||
reply_user = getattr(reply, "from_user", None)
|
||||
|
||||
if bot_id and reply_user and getattr(reply_user, "id", None) == bot_id:
|
||||
return f"[Reply to bot: {text}]"
|
||||
elif reply_user and getattr(reply_user, "username", None):
|
||||
return f"[Reply to @{reply_user.username}: {text}]"
|
||||
elif reply_user and getattr(reply_user, "first_name", None):
|
||||
return f"[Reply to {reply_user.first_name}: {text}]"
|
||||
else:
|
||||
return f"[Reply to: {text}]"
|
||||
|
||||
async def _download_message_media(
|
||||
self, msg, *, add_failure_content: bool = False
|
||||
@ -838,7 +851,7 @@ class TelegramChannel(BaseChannel):
|
||||
# Reply context: text and/or media from the replied-to message
|
||||
reply = getattr(message, "reply_to_message", None)
|
||||
if reply is not None:
|
||||
reply_ctx = self._extract_reply_context(message)
|
||||
reply_ctx = await self._extract_reply_context(message)
|
||||
reply_media, reply_media_parts = await self._download_message_media(reply)
|
||||
if reply_media:
|
||||
media_paths = reply_media + media_paths
|
||||
|
||||
@ -647,43 +647,56 @@ async def test_group_policy_open_accepts_plain_group_message() -> None:
|
||||
assert channel._app.bot.get_me_calls == 0
|
||||
|
||||
|
||||
def test_extract_reply_context_no_reply() -> None:
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_reply_context_no_reply() -> None:
|
||||
"""When there is no reply_to_message, _extract_reply_context returns None."""
|
||||
channel = TelegramChannel(TelegramConfig(enabled=True, token="123:abc"), MessageBus())
|
||||
message = SimpleNamespace(reply_to_message=None)
|
||||
assert TelegramChannel._extract_reply_context(message) is None
|
||||
assert await channel._extract_reply_context(message) is None
|
||||
|
||||
|
||||
def test_extract_reply_context_with_text() -> None:
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_reply_context_with_text() -> None:
|
||||
"""When reply has text, return prefixed string."""
|
||||
reply = SimpleNamespace(text="Hello world", caption=None)
|
||||
channel = TelegramChannel(TelegramConfig(enabled=True, token="123:abc"), MessageBus())
|
||||
channel._app = _FakeApp(lambda: None)
|
||||
reply = SimpleNamespace(text="Hello world", caption=None, from_user=SimpleNamespace(id=2, username="testuser", first_name="Test"))
|
||||
message = SimpleNamespace(reply_to_message=reply)
|
||||
assert TelegramChannel._extract_reply_context(message) == "[Reply to: Hello world]"
|
||||
assert await channel._extract_reply_context(message) == "[Reply to @testuser: Hello world]"
|
||||
|
||||
|
||||
def test_extract_reply_context_with_caption_only() -> None:
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_reply_context_with_caption_only() -> None:
|
||||
"""When reply has only caption (no text), caption is used."""
|
||||
reply = SimpleNamespace(text=None, caption="Photo caption")
|
||||
channel = TelegramChannel(TelegramConfig(enabled=True, token="123:abc"), MessageBus())
|
||||
channel._app = _FakeApp(lambda: None)
|
||||
reply = SimpleNamespace(text=None, caption="Photo caption", from_user=SimpleNamespace(id=2, username=None, first_name="Test"))
|
||||
message = SimpleNamespace(reply_to_message=reply)
|
||||
assert TelegramChannel._extract_reply_context(message) == "[Reply to: Photo caption]"
|
||||
assert await channel._extract_reply_context(message) == "[Reply to Test: Photo caption]"
|
||||
|
||||
|
||||
def test_extract_reply_context_truncation() -> None:
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_reply_context_truncation() -> None:
|
||||
"""Reply text is truncated at TELEGRAM_REPLY_CONTEXT_MAX_LEN."""
|
||||
channel = TelegramChannel(TelegramConfig(enabled=True, token="123:abc"), MessageBus())
|
||||
channel._app = _FakeApp(lambda: None)
|
||||
long_text = "x" * (TELEGRAM_REPLY_CONTEXT_MAX_LEN + 100)
|
||||
reply = SimpleNamespace(text=long_text, caption=None)
|
||||
reply = SimpleNamespace(text=long_text, caption=None, from_user=SimpleNamespace(id=2, username=None, first_name=None))
|
||||
message = SimpleNamespace(reply_to_message=reply)
|
||||
result = TelegramChannel._extract_reply_context(message)
|
||||
result = await channel._extract_reply_context(message)
|
||||
assert result is not None
|
||||
assert result.startswith("[Reply to: ")
|
||||
assert result.endswith("...]")
|
||||
assert len(result) == len("[Reply to: ]") + TELEGRAM_REPLY_CONTEXT_MAX_LEN + len("...")
|
||||
|
||||
|
||||
def test_extract_reply_context_no_text_returns_none() -> None:
|
||||
@pytest.mark.asyncio
|
||||
async def test_extract_reply_context_no_text_returns_none() -> None:
|
||||
"""When reply has no text/caption, _extract_reply_context returns None (media handled separately)."""
|
||||
channel = TelegramChannel(TelegramConfig(enabled=True, token="123:abc"), MessageBus())
|
||||
reply = SimpleNamespace(text=None, caption=None)
|
||||
message = SimpleNamespace(reply_to_message=reply)
|
||||
assert TelegramChannel._extract_reply_context(message) is None
|
||||
assert await channel._extract_reply_context(message) is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user