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.
This commit is contained in:
Zioywishing 2026-05-31 13:55:40 +08:00 committed by Teknium
parent e7cc10111a
commit b5c2dc7cd2
2 changed files with 14 additions and 36 deletions

View file

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

View file

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