fix(gateway): include replied-to media attachments (#46107)
This commit is contained in:
parent
a27d7e68cc
commit
efbe1635dd
@ -5560,6 +5560,52 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||||||
event.text = self._append_observed_note(event.text, cached.context_note())
|
event.text = self._append_observed_note(event.text, cached.context_note())
|
||||||
logger.info("[Telegram] Cached observed group %s at %s", cached.kind, cached.path)
|
logger.info("[Telegram] Cached observed group %s at %s", cached.kind, cached.path)
|
||||||
|
|
||||||
|
async def _cache_replied_media(self, msg: Any, event: MessageEvent) -> None:
|
||||||
|
"""Cache media from the message this turn replies to, if any."""
|
||||||
|
from gateway.platforms.base import cache_media_bytes
|
||||||
|
|
||||||
|
reply_msg = getattr(msg, "reply_to_message", None)
|
||||||
|
if reply_msg is None:
|
||||||
|
return
|
||||||
|
source, filename, mime, kind = self._observed_media_source(reply_msg)
|
||||||
|
if source is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
max_bytes = getattr(self, "_max_doc_bytes", 20 * 1024 * 1024)
|
||||||
|
file_size = getattr(source, "file_size", None)
|
||||||
|
try:
|
||||||
|
size = int(file_size or 0)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
size = 0
|
||||||
|
if not (0 < size <= max_bytes):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
file_obj = await source.get_file()
|
||||||
|
data = bytes(await file_obj.download_as_bytearray())
|
||||||
|
if not filename:
|
||||||
|
filename = os.path.basename(getattr(file_obj, "file_path", "") or "")
|
||||||
|
cached = cache_media_bytes(data, filename=filename, mime_type=mime, default_kind=kind)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning("[Telegram] Failed to cache replied-to media: %s", exc, exc_info=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
if cached is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
event.media_urls.append(cached.path)
|
||||||
|
event.media_types.append(cached.media_type)
|
||||||
|
if len(event.media_urls) == 1:
|
||||||
|
if cached.kind == "image":
|
||||||
|
event.message_type = MessageType.PHOTO
|
||||||
|
elif cached.kind == "video":
|
||||||
|
event.message_type = MessageType.VIDEO
|
||||||
|
event.text = self._append_observed_note(
|
||||||
|
event.text,
|
||||||
|
f"[Replied-to {cached.kind} '{cached.display_name}' saved at: {cached.path}]",
|
||||||
|
)
|
||||||
|
logger.info("[Telegram] Cached replied-to %s at %s", cached.kind, cached.path)
|
||||||
|
|
||||||
def _observed_media_source(self, msg: Message):
|
def _observed_media_source(self, msg: Message):
|
||||||
"""Return (telegram_file_source, filename, mime, default_kind) or Nones."""
|
"""Return (telegram_file_source, filename, mime, default_kind) or Nones."""
|
||||||
if msg.photo:
|
if msg.photo:
|
||||||
@ -5749,6 +5795,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||||||
|
|
||||||
event = self._build_message_event(msg, MessageType.TEXT, update_id=update.update_id)
|
event = self._build_message_event(msg, MessageType.TEXT, update_id=update.update_id)
|
||||||
event.text = self._clean_bot_trigger_text(event.text)
|
event.text = self._clean_bot_trigger_text(event.text)
|
||||||
|
await self._cache_replied_media(msg, event)
|
||||||
event = self._apply_telegram_group_observe_attribution(event)
|
event = self._apply_telegram_group_observe_attribution(event)
|
||||||
self._enqueue_text_event(event)
|
self._enqueue_text_event(event)
|
||||||
|
|
||||||
@ -5763,6 +5810,7 @@ class TelegramAdapter(BasePlatformAdapter):
|
|||||||
|
|
||||||
event = self._build_message_event(msg, MessageType.COMMAND, update_id=update.update_id)
|
event = self._build_message_event(msg, MessageType.COMMAND, update_id=update.update_id)
|
||||||
event.text = self._clean_bot_trigger_text(event.text)
|
event.text = self._clean_bot_trigger_text(event.text)
|
||||||
|
await self._cache_replied_media(msg, event)
|
||||||
event = self._apply_telegram_group_observe_attribution(event)
|
event = self._apply_telegram_group_observe_attribution(event)
|
||||||
await self.handle_message(event)
|
await self.handle_message(event)
|
||||||
|
|
||||||
|
|||||||
@ -4973,7 +4973,13 @@ class DiscordAdapter(BasePlatformAdapter):
|
|||||||
auto_threaded_channel = thread
|
auto_threaded_channel = thread
|
||||||
self._threads.mark(thread_id)
|
self._threads.mark(thread_id)
|
||||||
|
|
||||||
all_attachments = list(message.attachments) + snapshot_attachments
|
referenced_attachments = []
|
||||||
|
reference = getattr(message, "reference", None)
|
||||||
|
resolved_reference = getattr(reference, "resolved", None) if reference else None
|
||||||
|
if resolved_reference is not None:
|
||||||
|
referenced_attachments = list(getattr(resolved_reference, "attachments", []) or [])
|
||||||
|
|
||||||
|
all_attachments = list(message.attachments) + snapshot_attachments + referenced_attachments
|
||||||
|
|
||||||
# Determine message type
|
# Determine message type
|
||||||
msg_type = MessageType.TEXT
|
msg_type = MessageType.TEXT
|
||||||
|
|||||||
@ -5,6 +5,7 @@ Covers the fix for slash commands not being recognized when sent via
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from types import SimpleNamespace
|
||||||
from unittest.mock import AsyncMock
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -104,3 +105,51 @@ class TestAutoThreadingPreservesCommand:
|
|||||||
response = get_response_text(discord_adapter)
|
response = get_response_text(discord_adapter)
|
||||||
assert response is not None
|
assert response is not None
|
||||||
assert "/new" in response
|
assert "/new" in response
|
||||||
|
|
||||||
|
|
||||||
|
class TestRepliedToMediaDispatch:
|
||||||
|
async def test_reply_to_image_message_caches_referenced_attachment(
|
||||||
|
self, discord_adapter, bot_user, monkeypatch
|
||||||
|
):
|
||||||
|
"""A text reply to an image-bearing Discord message should give the agent that image."""
|
||||||
|
cached_path = "/tmp/replied-discord-image.png"
|
||||||
|
|
||||||
|
async def fake_cache_image_from_url(url, *, ext=".jpg"):
|
||||||
|
assert url == "https://cdn.discordapp.com/attachments/image.png"
|
||||||
|
assert ext == ".png"
|
||||||
|
return cached_path
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"plugins.platforms.discord.adapter.cache_image_from_url",
|
||||||
|
fake_cache_image_from_url,
|
||||||
|
)
|
||||||
|
discord_adapter.handle_message = AsyncMock()
|
||||||
|
|
||||||
|
attachment = SimpleNamespace(
|
||||||
|
content_type="image/png",
|
||||||
|
filename="image.png",
|
||||||
|
url="https://cdn.discordapp.com/attachments/image.png",
|
||||||
|
size=1234,
|
||||||
|
)
|
||||||
|
referenced_message = SimpleNamespace(
|
||||||
|
id=12345,
|
||||||
|
content="",
|
||||||
|
attachments=[attachment],
|
||||||
|
)
|
||||||
|
msg = make_discord_message(
|
||||||
|
content=f"<@{BOT_USER_ID}> what's in this image?",
|
||||||
|
mentions=[bot_user],
|
||||||
|
)
|
||||||
|
msg.type = 19
|
||||||
|
msg.reference = SimpleNamespace(message_id=12345, resolved=referenced_message)
|
||||||
|
|
||||||
|
await discord_adapter._handle_message(msg)
|
||||||
|
|
||||||
|
discord_adapter.handle_message.assert_awaited_once()
|
||||||
|
await_args = discord_adapter.handle_message.await_args
|
||||||
|
assert await_args is not None
|
||||||
|
event = await_args.args[0]
|
||||||
|
assert event.reply_to_message_id == "12345"
|
||||||
|
assert event.media_urls == [cached_path]
|
||||||
|
assert event.media_types == ["image/png"]
|
||||||
|
assert event.message_type.value == "photo"
|
||||||
|
|||||||
@ -1007,6 +1007,53 @@ def test_triggered_voice_message_uses_shared_session_in_observe_mode():
|
|||||||
asyncio.run(_run())
|
asyncio.run(_run())
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Replied-to media caching
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_text_reply_to_photo_caches_referenced_media(monkeypatch, tmp_path):
|
||||||
|
async def _run():
|
||||||
|
adapter = _make_adapter(require_mention=False)
|
||||||
|
adapter.handle_message = AsyncMock()
|
||||||
|
cached_path = tmp_path / "reply_photo.png"
|
||||||
|
monkeypatch.setattr(
|
||||||
|
"gateway.platforms.base.cache_image_from_bytes",
|
||||||
|
lambda _data, ext=".jpg": str(cached_path),
|
||||||
|
)
|
||||||
|
file_obj = SimpleNamespace(
|
||||||
|
file_path="photos/replied.png",
|
||||||
|
download_as_bytearray=AsyncMock(return_value=bytearray(b"\x89PNG\r\n\x1a\n reply")),
|
||||||
|
)
|
||||||
|
photo = SimpleNamespace(file_size=1234, get_file=AsyncMock(return_value=file_obj))
|
||||||
|
replied = SimpleNamespace(
|
||||||
|
message_id=51,
|
||||||
|
text=None,
|
||||||
|
caption=None,
|
||||||
|
photo=[photo],
|
||||||
|
video=None,
|
||||||
|
audio=None,
|
||||||
|
voice=None,
|
||||||
|
document=None,
|
||||||
|
)
|
||||||
|
msg = _group_message("what's in this image?", reply_to_bot=False)
|
||||||
|
msg.reply_to_message = replied
|
||||||
|
update = SimpleNamespace(update_id=3010, message=msg, effective_message=msg)
|
||||||
|
|
||||||
|
await adapter._handle_text_message(update, SimpleNamespace())
|
||||||
|
await asyncio.sleep(0.05)
|
||||||
|
|
||||||
|
adapter.handle_message.assert_awaited_once()
|
||||||
|
await_args = adapter.handle_message.await_args
|
||||||
|
assert await_args is not None
|
||||||
|
event = await_args.args[0]
|
||||||
|
assert event.reply_to_message_id == "51"
|
||||||
|
assert event.media_urls == [str(cached_path)]
|
||||||
|
assert event.media_types == ["image/png"]
|
||||||
|
assert event.message_type == MessageType.PHOTO
|
||||||
|
|
||||||
|
asyncio.run(_run())
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Observed-media caching (unmentioned group attachments)
|
# Observed-media caching (unmentioned group attachments)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user