fix: address all known follow-up issues except NetEase batch precheck

Surface failures + tighten edges:

1. Toast for /play-song & /play-playlist failures. Backend now returns
   {ok, message} (localized in Chinese to match the rest of the UI).
   Store stashes a notification on ok=false; new Toast.vue mounted in
   App.vue shows it for 3-5s then fades. Clicking the X dismisses
   immediately. Sits above the player on desktop and above the mobile
   tabbar on phones.

2. QQ collected playlists pagination. fcg_get_profile_order_asset.fcg
   returns max 30 per call; we now loop using has_more / short-page
   detection up to a 300-playlist hard cap. Single-call users (typical)
   exit the loop on the first iteration so no extra requests.

3. getPlayableSongIds chunking. 100 mids per request keeps URL well
   under 8KB; chunk-level errors are isolated so a transient blip on
   one chunk doesn't poison the whole batch. Returns null only when
   every chunk failed (caller falls back to sequential retry).

4. SourceTabs single-source mode now renders a small subdued "网易云"
   or "QQ" label instead of vanishing entirely, so the user always
   knows which platform's data they're looking at.

5. Hide the "我的歌单 N" count badge when N=0 — Home and Library no
   longer show "我的歌单 0" with an empty grid.

6. Auth state change invalidates fetchHomeData cache. Previously, a
   user who logged out as account A and into account B within 5
   minutes would see A's playlists. Now we always re-check auth at
   the top of fetchHomeData and bypass the TTL cache when authStatus
   has changed.

Out of scope:
- NetEase analogous batch precheck (per request).
- 60s TS3 UDP idle disconnect — that's the bundled @honeybbq/teamspeak-
  client UDP layer kicking when no server packet arrives in 60s. It's
  baked in (constant `v=6e4`) and not exposed as an option, and root
  cause is server-side or network-layer behavior we can't reach from
  here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
saopig1andClaude Opus 4.7 committed 2026-05-06 16:00:48 +08:00
1 parent 221079b89c
commit 59fb3ee3bd
8 files changed
+245 -76

No files matched your search

+7 -7
View File
@@ -268,7 +268,7 @@ export function createPlayerRouter(
// the sequential retry path, which still has a chance.
}
if (queueable.length === 0) {
res.json({ message: `Loaded ${totalCount} songs but none were playable (likely copyright/region restrictions).` });
res.json({ ok: false, message: `歌单 ${totalCount} 首歌曲均无版权可播放(区域/版权限制)` });
return;
}
@@ -300,12 +300,12 @@ export function createPlayerRouter(
const playing = queue.current();
const loadedMsg = queueable.length < totalCount
? `Loaded ${queueable.length} of ${totalCount} songs (rest are copyright/region restricted)`
: `Loaded ${queueable.length} songs`;
? `已加载 ${queueable.length}/${totalCount} 首(其余区域/版权限制)`
: `已加载 ${queueable.length} 首`;
if (started && playing) {
res.json({ message: `${loadedMsg}. Now playing: ${playing.name}` });
res.json({ ok: true, message: `${loadedMsg},正在播放:${playing.name}` });
} else {
res.json({ message: `${loadedMsg}, but couldn't start playback.` });
res.json({ ok: false, message: `${loadedMsg},但无法开始播放。` });
}
} catch (err) {
logger.error({ err }, "Play playlist failed");
@@ -330,11 +330,11 @@ export function createPlayerRouter(
bot.getPlayer().resetFailures();
const ok = await bot.resolveAndPlay(queue.current()!);
if (!ok) {
res.json({ message: `Cannot play: ${song.name || song.id}` });
res.json({ ok: false, message: `无法播放「${song.name || song.id}」(区域/版权限制)` });
return;
}
res.json({ message: `Now playing: ${song.name || 'Unknown'} - ${song.artist || 'Unknown'}` });
res.json({ ok: true, message: `正在播放:${song.name || 'Unknown'} - ${song.artist || 'Unknown'}` });
} catch (err) {
res.status(500).json({ error: (err as Error).message });
}