mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-04 08:45:54 +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
2e5308ff28
commit
49c40e6b31
@ -637,8 +637,7 @@ class TelegramChannel(BaseChannel):
|
|||||||
"reply_to_message_id": getattr(reply_to, "message_id", None) if reply_to else None,
|
"reply_to_message_id": getattr(reply_to, "message_id", None) if reply_to else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
async def _extract_reply_context(self, message) -> str | None:
|
||||||
def _extract_reply_context(message) -> str | None:
|
|
||||||
"""Extract text from the message being replied to, if any."""
|
"""Extract text from the message being replied to, if any."""
|
||||||
reply = getattr(message, "reply_to_message", None)
|
reply = getattr(message, "reply_to_message", None)
|
||||||
if not reply:
|
if not reply:
|
||||||
@ -646,7 +645,21 @@ class TelegramChannel(BaseChannel):
|
|||||||
text = getattr(reply, "text", None) or getattr(reply, "caption", None) or ""
|
text = getattr(reply, "text", None) or getattr(reply, "caption", None) or ""
|
||||||
if len(text) > TELEGRAM_REPLY_CONTEXT_MAX_LEN:
|
if len(text) > TELEGRAM_REPLY_CONTEXT_MAX_LEN:
|
||||||
text = 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(
|
async def _download_message_media(
|
||||||
self, msg, *, add_failure_content: bool = False
|
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 context: text and/or media from the replied-to message
|
||||||
reply = getattr(message, "reply_to_message", None)
|
reply = getattr(message, "reply_to_message", None)
|
||||||
if reply is not 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)
|
reply_media, reply_media_parts = await self._download_message_media(reply)
|
||||||
if reply_media:
|
if reply_media:
|
||||||
media_paths = reply_media + media_paths
|
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
|
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."""
|
"""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)
|
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."""
|
"""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)
|
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."""
|
"""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)
|
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."""
|
"""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)
|
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)
|
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 is not None
|
||||||
assert result.startswith("[Reply to: ")
|
assert result.startswith("[Reply to: ")
|
||||||
assert result.endswith("...]")
|
assert result.endswith("...]")
|
||||||
assert len(result) == len("[Reply to: ]") + TELEGRAM_REPLY_CONTEXT_MAX_LEN + len("...")
|
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)."""
|
"""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)
|
reply = SimpleNamespace(text=None, caption=None)
|
||||||
message = SimpleNamespace(reply_to_message=reply)
|
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
|
@pytest.mark.asyncio
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user