From b5c2dc7cd2f68367a5a212bf556adf6c548eca04 Mon Sep 17 00:00:00 2001 From: Zioywishing Date: Sun, 31 May 2026 13:55:40 +0800 Subject: [PATCH] fix(qqbot): skip voice detection for file uploads (content_type='file') Minimal fix: add 'if ct == "file": return False' before extension matching. The original fallback logic is preserved for empty/unknown content_types. Only the bug case (file uploads with audio extensions) is fixed. Removed the over-engineered _looks_like_voice helper. --- gateway/platforms/qqbot/adapter.py | 40 ++++++++++-------------------- tests/gateway/test_qqbot.py | 10 +------- 2 files changed, 14 insertions(+), 36 deletions(-) diff --git a/gateway/platforms/qqbot/adapter.py b/gateway/platforms/qqbot/adapter.py index 44f6aef97fe..0cab4bb794e 100644 --- a/gateway/platforms/qqbot/adapter.py +++ b/gateway/platforms/qqbot/adapter.py @@ -151,15 +151,6 @@ def _coerce_list(value: Any) -> List[str]: # --------------------------------------------------------------------------- -def _looks_like_voice(filename: str) -> bool: - """Return True if *filename* has a known voice/audio extension.""" - fn = filename.strip().lower() - return any( - fn.endswith(ext) - for ext in (".silk", ".amr", ".mp3", ".wav", ".ogg", ".m4a", ".aac", ".speex", ".flac") - ) - - class QQAdapter(BasePlatformAdapter): """QQ Bot adapter backed by the official QQ Bot WebSocket Gateway + REST API.""" @@ -1816,27 +1807,22 @@ class QQAdapter(BasePlatformAdapter): @staticmethod def _is_voice_content_type(content_type: str, filename: str) -> bool: - """Check if an attachment is a voice/audio message. - - The QQ Bot API explicitly sets ``content_type="voice"`` for voice - messages and ``content_type="file"`` for file uploads. When - ``content_type`` is a known non-voice type (``"file"``, ``"image/*"``, - ``"video/*"``), we trust it unconditionally and **skip** extension - sniffing — files sent via QQ's file-transfer feature can have audio - extensions (e.g. ``.wav``, ``.mp3``) but must not be routed through - the STT pipeline. - - When ``content_type`` is empty or unrecognised we fall back to - filename-extension matching as a defensive measure. - """ + """Check if an attachment is a voice/audio message.""" ct = content_type.strip().lower() + fn = filename.strip().lower() if ct == "voice" or ct.startswith("audio/"): return True - # Explicit non-voice types — never treat as voice regardless of extension. - if ct in ("file", "") or ct.startswith("image/") or ct.startswith("video/"): - return ct == "" and _looks_like_voice(filename) - # Unknown content_type — defensive extension fallback. - return _looks_like_voice(filename) + # QQ file uploads have content_type="file" — never treat as voice, + # even if the filename has an audio extension. + if ct == "file": + return False + _VOICE_EXTENSIONS = ( + ".silk", ".amr", ".mp3", ".wav", ".ogg", + ".m4a", ".aac", ".speex", ".flac", + ) + if any(fn.endswith(ext) for ext in _VOICE_EXTENSIONS): + return True + return False def _qq_media_headers(self) -> Dict[str, str]: """Return Authorization headers for QQ multimedia CDN downloads. diff --git a/tests/gateway/test_qqbot.py b/tests/gateway/test_qqbot.py index 50b09d74235..10d558c8260 100644 --- a/tests/gateway/test_qqbot.py +++ b/tests/gateway/test_qqbot.py @@ -151,20 +151,12 @@ class TestIsVoiceContentType: assert self._fn("", "recording.amr") is True def test_file_upload_with_audio_extension(self): - """File upload with audio extension must NOT be treated as voice.""" + """content_type='file' is never voice, even with audio extension.""" assert self._fn("file", "song.mp3") is False assert self._fn("file", "audio-30251.instrumental..wav") is False - - def test_file_upload_never_voice(self): - """content_type='file' is never voice, regardless of extension.""" assert self._fn("file", "recording.silk") is False assert self._fn("file", "voice.amr") is False - def test_unknown_content_type_extension_fallback(self): - """Unknown content_type falls back to extension matching.""" - assert self._fn("unknown/type", "voice.ogg") is True - assert self._fn("unknown/type", "data.json") is False - # --------------------------------------------------------------------------- # Voice attachment SSRF protection