From 771e46620ca96d97689eb213602ac9f3526a7725 Mon Sep 17 00:00:00 2001 From: unknown <144674902+006lp@users.noreply.github.com> Date: Wed, 28 Oct 2026 13:47:03 +0800 Subject: [PATCH] fix: export MusicShare without song ID --- .../builtins/uniseg/music_share.py | 11 +- tests/test_music_share.py | 133 ++++++++++++++++++ 2 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 tests/test_music_share.py diff --git a/src/nonebot_plugin_alconna/builtins/uniseg/music_share.py b/src/nonebot_plugin_alconna/builtins/uniseg/music_share.py index 5317c7b..e6e66c4 100644 --- a/src/nonebot_plugin_alconna/builtins/uniseg/music_share.py +++ b/src/nonebot_plugin_alconna/builtins/uniseg/music_share.py @@ -137,13 +137,14 @@ async def music_export(exporter: MessageExporter, seg: MusicShare, bot: Bot | No if exporter.get_adapter() is SupportAdapter.onebot11: from nonebot.adapters.onebot.v11.message import MessageSegment - platform = { + platforms = { MusicShareKind.NeteaseCloudMusic: "163", MusicShareKind.QQMusic: "qq", MusicShareKind.MiguMusic: "migu", MusicShareKind.KugouMusic: "kugou", MusicShareKind.KuwoMusic: "kuwo", - }.get(seg.kind, "qq") + } + platform = platforms.get(seg.kind, "qq") if seg.id: return MessageSegment.music(platform, int(seg.id)) @@ -154,7 +155,11 @@ async def music_export(exporter: MessageExporter, seg: MusicShare, bot: Bot | No content=seg.content or seg.summary or "", img_url=seg.thumbnail or "", ) - res.data["subtype"] = platform + if seg.kind in platforms: + res.data["type"] = platform + else: + # 保留自定义音乐消息原有的 QQ subtype。 + res.data["subtype"] = platform return res if exporter.get_adapter() is SupportAdapter.qq: diff --git a/tests/test_music_share.py b/tests/test_music_share.py new file mode 100644 index 0000000..10d9a88 --- /dev/null +++ b/tests/test_music_share.py @@ -0,0 +1,133 @@ +import pytest +from nonebot import get_adapter +from nonebot.adapters.onebot.v11 import Adapter, Bot, Message, MessageSegment +from nonebug import App + +from tests.fake import fake_group_message_event_v11 + +MUSIC_PLATFORMS = [ + ("NeteaseCloudMusic", "163"), + ("QQMusic", "qq"), + ("MiguMusic", "migu"), + ("KugouMusic", "kugou"), + ("KuwoMusic", "kuwo"), +] + + +@pytest.fixture +def music_metadata() -> dict[str, str]: + return { + "title": "示例歌曲", + "content": "示例歌手 | 示例专辑", + "url": "https://example.com/song", + "audio": "https://example.com/song.mp3", + "thumbnail": "https://example.com/cover.jpg", + } + + +@pytest.fixture +def music_data(music_metadata: dict[str, str]) -> dict[str, str]: + return { + "url": music_metadata["url"], + "audio": music_metadata["audio"], + "title": music_metadata["title"], + "content": music_metadata["content"], + "image": music_metadata["thumbnail"], + } + + +@pytest.mark.asyncio() +@pytest.mark.parametrize(("kind", "platform"), MUSIC_PLATFORMS) +@pytest.mark.parametrize("song_id", [None, ""]) +async def test_onebot_music_share_without_id( + app: App, + kind: str, + platform: str, + song_id: str | None, + music_metadata: dict[str, str], + music_data: dict[str, str], +): + from nonebot_plugin_alconna import UniMessage + from nonebot_plugin_alconna.builtins.uniseg.music_share import MusicShare, MusicShareKind + + card = MusicShare(kind=MusicShareKind(kind), id=song_id, **music_metadata) + message = await UniMessage(card).export(adapter="OneBot V11", fallback=False) + + assert message == Message(MessageSegment("music", {"type": platform, **music_data})) + + +@pytest.mark.asyncio() +@pytest.mark.parametrize(("kind", "platform"), [*MUSIC_PLATFORMS, ("Custom", "qq")]) +async def test_onebot_music_share_with_id(app: App, kind: str, platform: str, music_metadata: dict[str, str]): + from nonebot_plugin_alconna import UniMessage + from nonebot_plugin_alconna.builtins.uniseg.music_share import MusicShare, MusicShareKind + + card = MusicShare(kind=MusicShareKind(kind), id="123", **music_metadata) + message = await UniMessage(card).export(adapter="OneBot V11", fallback=False) + + assert message == Message(MessageSegment.music(platform, 123)) + + +@pytest.mark.asyncio() +@pytest.mark.parametrize("kind", ["Custom", "custom", "Unknown"]) +async def test_onebot_custom_music_share( + app: App, kind: str, music_metadata: dict[str, str], music_data: dict[str, str] +): + from nonebot_plugin_alconna import UniMessage + from nonebot_plugin_alconna.builtins.uniseg.music_share import MusicShare, MusicShareKind + + card = MusicShare(kind=MusicShareKind(kind), **music_metadata) + message = await UniMessage(card).export(adapter="OneBot V11", fallback=False) + + assert message == Message(MessageSegment("music", {"type": "custom", "subtype": "qq", **music_data})) + + +def test_music_share_requires_kind(app: App, music_metadata: dict[str, str]): + from nonebot_plugin_alconna.builtins.uniseg.music_share import MusicShare + + with pytest.raises(TypeError, match="kind"): + MusicShare(**music_metadata) + + +@pytest.mark.asyncio() +@pytest.mark.parametrize(("url", "audio"), [(None, "https://example.com/song.mp3"), ("https://example.com/song", None)]) +async def test_onebot_music_share_metadata_fallback(app: App, url: str | None, audio: str | None): + from nonebot_plugin_alconna import UniMessage + from nonebot_plugin_alconna.builtins.uniseg.music_share import MusicShare, MusicShareKind + + card = MusicShare(kind=MusicShareKind.KuwoMusic, url=url, audio=audio, summary="示例摘要") + message = await UniMessage(card).export(adapter="OneBot V11", fallback=False) + + assert message == Message( + MessageSegment( + "music", + { + "type": "kuwo", + "url": url or audio, + "audio": audio or url, + "title": "", + "content": "示例摘要", + "image": "", + }, + ) + ) + + +@pytest.mark.asyncio() +async def test_onebot_music_share_finish(app: App, music_metadata: dict[str, str], music_data: dict[str, str]): + from nonebot_plugin_alconna import UniMessage, on_alconna + from nonebot_plugin_alconna.builtins.uniseg.music_share import MusicShare, MusicShareKind + + card = MusicShare(kind=MusicShareKind.KuwoMusic, **music_metadata) + matcher = on_alconna("music") + + @matcher.handle() + async def _(): + await matcher.finish(UniMessage([card])) + + async with app.test_matcher(matcher) as ctx: + bot = ctx.create_bot(base=Bot, adapter=get_adapter(Adapter)) + event = fake_group_message_event_v11(message=Message("music"), user_id=123) + ctx.receive_event(bot, event) + ctx.should_call_send(event, Message(MessageSegment("music", {"type": "kuwo", **music_data}))) + ctx.should_finished()