From 0e7448d63abd6864fa7ed4c6b20030890abf813e Mon Sep 17 00:00:00 2001 From: walli Date: Mon, 18 May 2026 18:38:31 +0800 Subject: [PATCH] fix(qqbot): use original attachment filename for cached files Add original_name parameter to _download_and_cache, preferring the attachment metadata filename over the CDN URL path basename. Previously files were cached with meaningless QQ CDN hash names (e.g. qqdownload_...oadftnv5), causing ugly filenames when sent back to users. Aligns with qqbot-agent-sdk's AttachmentDownloader.download_document. --- gateway/platforms/qqbot/adapter.py | 20 +++++++++++++++----- tests/gateway/test_qqbot.py | 8 ++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/gateway/platforms/qqbot/adapter.py b/gateway/platforms/qqbot/adapter.py index 6e2d883831f..ddbffa46238 100644 --- a/gateway/platforms/qqbot/adapter.py +++ b/gateway/platforms/qqbot/adapter.py @@ -1654,7 +1654,7 @@ class QQAdapter(BasePlatformAdapter): elif ct.startswith("image/"): # Image: download and cache locally. try: - cached_path = await self._download_and_cache(url, ct) + cached_path = await self._download_and_cache(url, ct, filename) if cached_path and os.path.isfile(cached_path): image_urls.append(cached_path) image_media_types.append(ct or "image/jpeg") @@ -1669,7 +1669,7 @@ class QQAdapter(BasePlatformAdapter): else: # Other attachments (video, file, etc.): download and record with path. try: - cached_path = await self._download_and_cache(url, ct) + cached_path = await self._download_and_cache(url, ct, filename) if cached_path: name = filename or ct if ct.startswith("video/"): @@ -1687,8 +1687,14 @@ class QQAdapter(BasePlatformAdapter): "attachment_info": attachment_info, } - async def _download_and_cache(self, url: str, content_type: str) -> Optional[str]: - """Download a URL and cache it locally.""" + async def _download_and_cache( + self, url: str, content_type: str, original_name: str = "", + ) -> Optional[str]: + """Download a URL and cache it locally. + + :param original_name: Preferred filename from attachment metadata. + Falls back to the URL path basename if empty. + """ from tools.url_safety import is_safe_url if not is_safe_url(url): @@ -1719,7 +1725,11 @@ class QQAdapter(BasePlatformAdapter): # Convert to .wav using ffmpeg so STT engines can process it. return await self._convert_audio_to_wav(data, url) else: - filename = Path(urlparse(url).path).name or "qq_attachment" + filename = ( + original_name + or Path(urlparse(url).path).name + or "qq_attachment" + ) return cache_document_from_bytes(data, filename) @staticmethod diff --git a/tests/gateway/test_qqbot.py b/tests/gateway/test_qqbot.py index 19d93ac5079..8b02fc92cfe 100644 --- a/tests/gateway/test_qqbot.py +++ b/tests/gateway/test_qqbot.py @@ -1868,7 +1868,7 @@ class TestProcessAttachmentsPathExposure: adapter = self._make_adapter() # Mock _download_and_cache to return a known path - async def fake_download(url, ct): + async def fake_download(url, ct, original_name=""): return "/tmp/cache/video_abc123.mp4" adapter._download_and_cache = fake_download # type: ignore[assignment] @@ -1893,7 +1893,7 @@ class TestProcessAttachmentsPathExposure: async def test_file_attachment_includes_path(self): adapter = self._make_adapter() - async def fake_download(url, ct): + async def fake_download(url, ct, original_name=""): return "/tmp/cache/doc_abc123_report.pdf" adapter._download_and_cache = fake_download # type: ignore[assignment] @@ -1916,7 +1916,7 @@ class TestProcessAttachmentsPathExposure: async def test_video_without_filename_falls_back_to_content_type(self): adapter = self._make_adapter() - async def fake_download(url, ct): + async def fake_download(url, ct, original_name=""): return "/tmp/cache/video_xyz.mp4" adapter._download_and_cache = fake_download # type: ignore[assignment] @@ -1938,7 +1938,7 @@ class TestProcessAttachmentsPathExposure: async def test_download_failure_produces_no_attachment_info(self): adapter = self._make_adapter() - async def fake_download(url, ct): + async def fake_download(url, ct, original_name=""): return None adapter._download_and_cache = fake_download # type: ignore[assignment]