fix(qq): 按 ID 播放时回填歌曲元数据,修复 TS 显示空歌名

用 id: / play-by-id 播放 QQ 音乐时,getSongDetail 依赖的 /getSongInfo
端点已被上游废弃(返回 code 500001),会 fall through 到一个 name 为空
的兜底 stub。空歌名一路传到 currentSong,导致 TS 昵称 / 正在播放消息
显示成「♪ 正在播放:  -  []」。

修复:在返回空 stub 前新增 fetchSongDetailViaMusicu(),改用与搜索同源
且可用的 u.y.qq.com/cgi-bin/musicu.fcg(music.pf_song_detail_svr /
get_song_detail_yqq)按 mid 拉取真实 track_info(歌名/歌手/专辑/时长/
封面),无需登录;仅当它也失败时才退回空 stub。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Claude CodeandClaude Opus 4.8 committed 2026-07-07 10:35:54 +08:00
1 parent b9767a7471
commit 15190413b2
1 file changed
+47 -1
+47 -1
View File
@@ -364,8 +364,18 @@ export class QQMusicProvider implements MusicProvider {
}; };
} }
} catch { } catch {
// fall through to stub // fall through to musicu.fcg fallback
} }
// Fallback: fetch metadata via u.y.qq.com/cgi-bin/musicu.fcg. The bundled
// library's /getSongInfo route is broken (upstream code 500001), so the
// primary try above almost always falls through here for QQ. Without this,
// play-by-id songs carry an empty name → the TS nickname / now-playing
// message renders as "♪ 正在播放: - []". This endpoint (same host/shape
// as search) reliably returns full track_info without requiring login.
const detail = await this.fetchSongDetailViaMusicu(songId);
if (detail) return detail;
// Minimal stub — resolveAndPlay only needs id + platform to fetch a // Minimal stub — resolveAndPlay only needs id + platform to fetch a
// play URL. Name/artist/album will be empty in play history, but the // play URL. Name/artist/album will be empty in play history, but the
// song will actually play, which is the important part. // song will actually play, which is the important part.
@@ -380,6 +390,42 @@ export class QQMusicProvider implements MusicProvider {
}; };
} }
/** Fetch a single song's metadata via u.y.qq.com/cgi-bin/musicu.fcg
* (module music.pf_song_detail_svr / get_song_detail_yqq). This mirrors the
* search path's host + request shape and, unlike the library's /getSongInfo,
* actually works against QQ's current API. Returns null on any failure so the
* caller can fall back to the minimal stub. */
private async fetchSongDetailViaMusicu(songId: string): Promise<Song | null> {
try {
const reqData = JSON.stringify({
req_0: {
module: "music.pf_song_detail_svr",
method: "get_song_detail_yqq",
param: { song_mid: songId, song_type: 0 },
},
});
const res = await qqMusicuApi.get("/cgi-bin/musicu.fcg", {
params: { format: "json", data: reqData },
});
const t = res.data?.req_0?.data?.track_info;
if (!t) return null;
const albumMid = t.album?.mid ?? t.album?.pmid ?? "";
return {
id: String(t.mid ?? t.id ?? songId),
name: t.name ?? t.title ?? "",
artist: (t.singer ?? []).map((a: any) => a.name ?? a.title ?? "").filter(Boolean).join(" / "),
album: t.album?.name ?? t.album?.title ?? "",
duration: t.interval ?? 0,
coverUrl: albumMid
? `https://y.gtimg.cn/music/photo_new/T002R300x300M000${albumMid}.jpg`
: "",
platform: "qq",
};
} catch {
return null;
}
}
async getPlaylistSongs(playlistId: string): Promise<Song[]> { async getPlaylistSongs(playlistId: string): Promise<Song[]> {
const res = await this.api.get("/getSongListDetail", { const res = await this.api.get("/getSongListDetail", {
params: { disstid: playlistId, ...this.cookieParams }, params: { disstid: playlistId, ...this.cookieParams },