mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-01 01:10:21 +08:00
* fix: unify media reference handling * fix: accept bare base64 record media refs * chore: update agents.md * fix: unify file URI handling across media components and utilities * fix: unify media reference type handling with MediaRefStr alias * Potential fix for pull request finding 'CodeQL / Incomplete URL substring sanitization' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update astrbot/core/platform/sources/discord/discord_platform_adapter.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * fix: unify media handling and improve base64 decoding across components * fix: simplify client_kwargs type definition and enhance media message handling in platform adapter * fix: unify media utility documentation and enhance function descriptions * perf: drop "pilk" requirement, improve audio outbound for tencent-related IM apps which using silk * fix: unify Tencent Silk audio handling and enhance media resolver functionality --- - Centralize media reference materialization and base64 resolution for local paths, http(s), base64://, data URIs, and legacy bare base64 payloads. - Normalize incoming Record audio to wav and Image media to temporary jpg during preprocess, with event-scoped cleanup. - Reuse the shared media resolver across OpenAI, Gemini, Anthropic, MiMo, DeerFlow, STT, and platform media paths while sanitizing logs and cleaning temporary conversion outputs. - Ensure generated TTS audio is tracked for cleanup after the event finishes. fix #8676 fix #8543 fix #7588 fix #7580 fix #8030 fix #8034 fix #7461 fix #7565 fix #6509 fix #7144 fix #7795 --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
167 lines
5.0 KiB
Python
167 lines
5.0 KiB
Python
import pytest
|
|
|
|
from astrbot.api.event import MessageChain
|
|
from astrbot.api.message_components import Record
|
|
from astrbot.core.platform.sources.lark import lark_adapter
|
|
from astrbot.core.platform.sources.lark.lark_adapter import LarkPlatformAdapter
|
|
from astrbot.core.platform.sources.line import line_adapter
|
|
from astrbot.core.platform.sources.line.line_adapter import LinePlatformAdapter
|
|
from astrbot.core.platform.sources.misskey import misskey_utils
|
|
from astrbot.core.platform.sources.misskey.misskey_utils import create_file_component
|
|
from astrbot.core.platform.sources.qqofficial import (
|
|
qqofficial_message_event,
|
|
qqofficial_platform_adapter,
|
|
)
|
|
from astrbot.core.platform.sources.qqofficial.qqofficial_message_event import (
|
|
QQOfficialMessageEvent,
|
|
)
|
|
from astrbot.core.platform.sources.qqofficial.qqofficial_platform_adapter import (
|
|
QQOfficialPlatformAdapter,
|
|
)
|
|
|
|
WAV_PATH = "/tmp/astrbot-platform-audio.wav"
|
|
|
|
|
|
class FakeMediaResolver:
|
|
calls = []
|
|
|
|
def __init__(self, media_ref: str, **kwargs) -> None:
|
|
self.media_ref = media_ref
|
|
self.kwargs = kwargs
|
|
self.calls.append((media_ref, kwargs))
|
|
|
|
async def to_path(self, **kwargs) -> str:
|
|
self.calls[-1] = (*self.calls[-1], kwargs)
|
|
return WAV_PATH
|
|
|
|
|
|
def _patch_resolver(monkeypatch, module) -> None:
|
|
FakeMediaResolver.calls = []
|
|
monkeypatch.setattr(module, "MediaResolver", FakeMediaResolver)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_line_audio_component_uses_media_resolver_for_external_url(monkeypatch):
|
|
_patch_resolver(monkeypatch, line_adapter)
|
|
adapter = LinePlatformAdapter.__new__(LinePlatformAdapter)
|
|
|
|
record = await adapter._build_audio_component(
|
|
"msg-1",
|
|
{
|
|
"contentProvider": {
|
|
"type": "external",
|
|
"originalContentUrl": "https://example.test/voice.m4a",
|
|
}
|
|
},
|
|
)
|
|
|
|
assert isinstance(record, Record)
|
|
assert record.file == WAV_PATH
|
|
assert record.url == WAV_PATH
|
|
assert FakeMediaResolver.calls == [
|
|
(
|
|
"https://example.test/voice.m4a",
|
|
{"media_type": "audio", "default_suffix": ".wav"},
|
|
{"target_format": "wav"},
|
|
)
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_lark_audio_component_uses_media_resolver_after_download(monkeypatch):
|
|
_patch_resolver(monkeypatch, lark_adapter)
|
|
adapter = LarkPlatformAdapter.__new__(LarkPlatformAdapter)
|
|
|
|
async def fake_download_file_resource_to_temp(**kwargs):
|
|
assert kwargs["message_type"] == "audio"
|
|
return "/tmp/lark-source.opus"
|
|
|
|
monkeypatch.setattr(
|
|
adapter,
|
|
"_download_file_resource_to_temp",
|
|
fake_download_file_resource_to_temp,
|
|
)
|
|
|
|
records = await adapter._parse_message_components(
|
|
message_id="msg-1",
|
|
message_type="audio",
|
|
content={"file_key": "file-key"},
|
|
at_map={},
|
|
)
|
|
|
|
assert len(records) == 1
|
|
assert isinstance(records[0], Record)
|
|
assert records[0].file == WAV_PATH
|
|
assert records[0].url == WAV_PATH
|
|
assert FakeMediaResolver.calls == [
|
|
(
|
|
"/tmp/lark-source.opus",
|
|
{"media_type": "audio", "default_suffix": ".wav"},
|
|
{"target_format": "wav"},
|
|
)
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_qqofficial_audio_attachment_uses_media_resolver(monkeypatch):
|
|
_patch_resolver(monkeypatch, qqofficial_platform_adapter)
|
|
|
|
record = await QQOfficialPlatformAdapter._prepare_audio_attachment(
|
|
"https://example.test/voice.amr",
|
|
"voice.amr",
|
|
)
|
|
|
|
assert isinstance(record, Record)
|
|
assert record.file == WAV_PATH
|
|
assert record.url == WAV_PATH
|
|
assert FakeMediaResolver.calls == [
|
|
(
|
|
"https://example.test/voice.amr",
|
|
{"media_type": "audio", "default_suffix": ".amr"},
|
|
{"target_format": "wav"},
|
|
)
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_qqofficial_send_record_resolves_to_tencent_silk(monkeypatch):
|
|
_patch_resolver(monkeypatch, qqofficial_message_event)
|
|
|
|
parsed = await QQOfficialMessageEvent._parse_to_qqofficial(
|
|
MessageChain([Record(file="voice.amr", url="https://example.test/voice.amr")])
|
|
)
|
|
|
|
assert parsed[3] == WAV_PATH
|
|
assert FakeMediaResolver.calls == [
|
|
(
|
|
"https://example.test/voice.amr",
|
|
{"media_type": "audio", "default_suffix": ".wav"},
|
|
{"target_format": "tencent_silk"},
|
|
)
|
|
]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_misskey_audio_file_component_uses_media_resolver(monkeypatch):
|
|
_patch_resolver(monkeypatch, misskey_utils)
|
|
|
|
record, part_text = await create_file_component(
|
|
{
|
|
"url": "https://example.test/voice.ogg",
|
|
"name": "voice.ogg",
|
|
"type": "audio/ogg",
|
|
}
|
|
)
|
|
|
|
assert isinstance(record, Record)
|
|
assert record.file == WAV_PATH
|
|
assert record.url == WAV_PATH
|
|
assert part_text == "音频[voice.ogg]"
|
|
assert FakeMediaResolver.calls == [
|
|
(
|
|
"https://example.test/voice.ogg",
|
|
{"media_type": "audio", "default_suffix": ".wav"},
|
|
{"target_format": "wav"},
|
|
)
|
|
]
|