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.
This commit is contained in:
walli 2026-05-18 18:38:31 +08:00 committed by Teknium
parent a54f5afc70
commit 0e7448d63a
2 changed files with 19 additions and 9 deletions

View file

@ -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

View file

@ -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]