From bb60137ac72f820bd2cfba26bf510e0cb64e11d9 Mon Sep 17 00:00:00 2001 From: snowy-yang <102860110+snowy-yang@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:54:24 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20B=E7=AB=99=E7=82=B9=E6=AD=8C=E6=97=A0?= =?UTF-8?q?=E5=8F=8D=E5=BA=94=E2=80=94=E2=80=94songmid=E8=87=AA=E5=B8=A6?= =?UTF-8?q?=E7=AB=96=E7=BA=BF=E6=8B=86=E6=95=A3=E6=8C=89=E9=92=AE=E5=9B=9E?= =?UTF-8?q?=E8=B0=83data?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bili 的 songmid 形如「BV号|cid」,与回调 data 的分隔符撞车:拆段 从 5 变 6,handle_button 判为非本 bot 按钮直接丢弃,且发生在回应 互动之前——客户端按钮一直转圈、日志无任何记录,表象即「点歌卡死」。 _song_data 给歌曲 id 一并 % 转义(_q),_song_of_data 对应 unquote; 旧按钮 id 不含 %,unquote 幂等不受影响。附 bili 往返回归测试。 --- musicbot/music.py | 13 ++++++++----- tests/test_music.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/musicbot/music.py b/musicbot/music.py index 48aeedd..0a47bb3 100644 --- a/musicbot/music.py +++ b/musicbot/music.py @@ -534,10 +534,11 @@ async def _swap_message( def _song_data(action: str, song: Song) -> str: - """回调按钮携带的歌曲数据:自包含(歌名/歌手/封面 % 转义,避免撞上 - 分隔符),bot 收到回调直接还原 Song,无需记忆菜单快照。""" + """回调按钮携带的歌曲数据:自包含(歌曲 id/歌名/歌手/封面 % 转义, + 避免撞上分隔符),bot 收到回调直接还原 Song,无需记忆菜单快照。 + id 也要转义:bili 的 songmid 是「BV号|cid」,裸拼会把 data 拆散。""" return ( - f"音乐|{action}|{song.source}|{song.song_id}" + f"音乐|{action}|{song.source}|{_q(song.song_id)}" f"|{_q(song.name)}|{_q(song.singer)}|{_q(song.img or '')}" ) @@ -545,12 +546,14 @@ def _song_data(action: str, song: Song) -> str: def _song_of_data(args: list[str]) -> Song: """从回调 data 的 [source, song_id, name, singer, img?] 还原 Song。 - img 段是后加的:旧按钮的 data 只有 4 段,缺省按无封面处理。""" + img 段是后加的:旧按钮的 data 只有 4 段,缺省按无封面处理。 + song_id 段的 % 转义也是后加的(bili songmid 带 |):旧按钮 id 不含 + %,unquote 幂等,直接解不影响。""" source, song_id, name, singer = args[:4] img = unquote(args[4]) if len(args) > 4 and args[4] else None return Song( source=source, - song_id=song_id, + song_id=unquote(song_id), name=unquote(name), singer=unquote(singer), img=img, diff --git a/tests/test_music.py b/tests/test_music.py index 825e0cb..f4709c7 100644 --- a/tests/test_music.py +++ b/tests/test_music.py @@ -510,6 +510,25 @@ async def test_card_for_qs_song_shows_source_and_cover(env): assert f"![封面 #240px #240px]({cover})" in md +async def test_bili_songmid_pipe_round_trips(env): + """bili 的 songmid 是「BV号|cid」,自带分隔符:按钮 data 里的 id 必须 + % 转义,否则拆段变 6 段、整个回调被当成非本 bot 按钮静默丢弃(点歌 + 无反应)。转义后 pick 正常弹卡片,播放/下载按钮携带完整 id。""" + http, config = env + api, calls = make_api() + data = music._song_data( + "pick", + music.Song("bili", "BV1vg2AYdE4J|26216173455", "听", "UP主"), + ) + assert data.count("|") == 6 # 7 段:头+action+source+id+name+singer+img + await handle_button(api, http, config, "G1", "I1", data) + card = posts(calls)[0]["json"] + assert "## 听 - UP主" in card["markdown"]["content"] + assert "来源:哔哩哔哩" in card["markdown"]["content"] + datas = [b["action"]["data"] for b in buttons(card)[0]["buttons"]] + assert "BV1vg2AYdE4J%7C26216173455" in datas[0] # ▶ 播放带完整 id + + async def test_card_cover_url_is_encoded(env): """卡片封面 URL 走标准百分号编码:空格/中文转义、Markdown 语法里的 括号也要转(否则会截断 ![]() 链接),查询串结构保留。"""