fix(telegram): avoid media filename collisions

Use file_unique_id when storing downloaded Telegram media so different uploads do not silently overwrite each other on disk.
This commit is contained in:
Protocol Zero
2026-03-09 20:11:16 +00:00
parent 99b896f5d4
commit 0104a2253a
2 changed files with 59 additions and 1 deletions

View File

@@ -539,7 +539,8 @@ class TelegramChannel(BaseChannel):
) )
media_dir = get_media_dir("telegram") media_dir = get_media_dir("telegram")
file_path = media_dir / f"{media_file.file_id[:16]}{ext}" unique_id = getattr(media_file, "file_unique_id", media_file.file_id)
file_path = media_dir / f"{unique_id}{ext}"
await file.download_to_drive(str(file_path)) await file.download_to_drive(str(file_path))
media_paths.append(str(file_path)) media_paths.append(str(file_path))

View File

@@ -27,6 +27,7 @@ class _FakeUpdater:
class _FakeBot: class _FakeBot:
def __init__(self) -> None: def __init__(self) -> None:
self.sent_messages: list[dict] = [] self.sent_messages: list[dict] = []
self.file = None
async def get_me(self): async def get_me(self):
return SimpleNamespace(username="nanobot_test") return SimpleNamespace(username="nanobot_test")
@@ -37,6 +38,9 @@ class _FakeBot:
async def send_message(self, **kwargs) -> None: async def send_message(self, **kwargs) -> None:
self.sent_messages.append(kwargs) self.sent_messages.append(kwargs)
async def get_file(self, _file_id):
return self.file
class _FakeApp: class _FakeApp:
def __init__(self, on_start_polling) -> None: def __init__(self, on_start_polling) -> None:
@@ -182,3 +186,56 @@ async def test_send_reply_infers_topic_from_message_id_cache() -> None:
assert channel._app.bot.sent_messages[0]["message_thread_id"] == 42 assert channel._app.bot.sent_messages[0]["message_thread_id"] == 42
assert channel._app.bot.sent_messages[0]["reply_parameters"].message_id == 10 assert channel._app.bot.sent_messages[0]["reply_parameters"].message_id == 10
@pytest.mark.asyncio
async def test_on_message_uses_file_unique_id_for_downloaded_media(monkeypatch, tmp_path) -> None:
config = TelegramConfig(enabled=True, token="123:abc", allow_from=["*"])
channel = TelegramChannel(config, MessageBus())
channel._app = _FakeApp(lambda: None)
downloaded: dict[str, str] = {}
class _FakeDownloadedFile:
async def download_to_drive(self, path: str) -> None:
downloaded["path"] = path
channel._app.bot.file = _FakeDownloadedFile()
captured: dict[str, object] = {}
async def _capture_message(**kwargs) -> None:
captured.update(kwargs)
monkeypatch.setattr(channel, "_handle_message", _capture_message)
monkeypatch.setattr(channel, "_start_typing", lambda _chat_id: None)
monkeypatch.setattr("nanobot.channels.telegram.get_media_dir", lambda _name=None: tmp_path)
update = SimpleNamespace(
effective_user=SimpleNamespace(id=123, username="alice", first_name="Alice"),
message=SimpleNamespace(
message_id=1,
chat=SimpleNamespace(type="private", is_forum=False),
chat_id=456,
text=None,
caption=None,
photo=[
SimpleNamespace(
file_id="file-id-that-should-not-be-used",
file_unique_id="stable-unique-id",
mime_type="image/jpeg",
file_name=None,
)
],
voice=None,
audio=None,
document=None,
media_group_id=None,
message_thread_id=None,
),
)
await channel._on_message(update, None)
assert downloaded["path"].endswith("stable-unique-id.jpg")
assert captured["media"] == [str(tmp_path / "stable-unique-id.jpg")]