mirror of
https://github.com/ZHANGTIANYAO1/teamspeak-music-bot.git
synced 2026-10-02 04:52:50 +08:00
fix: corner-case audit — distinguish failures, recover from blips
Three small but real correctness fixes from auditing recent commits: 1. getPlayableSongIds returned an empty Set for both "endpoint failed" and "succeeded but all unplayable" — the caller couldn't tell which. Return Set | null now: null = error (fall through to sequential retry), empty Set = authoritative "all unplayable" (short-circuit to a clear message instead of wasting 20+ retries). 2. Web store: fetchHomeData unconditionally wrote lastFetchTime even when every fetch rejected (network blip, server down). That cached the failure for 5 minutes — user had to hard-reload to recover. Now only commit lastFetchTime if at least one auth-status call succeeded. 3. Settings profile section: if GET /profile failed, profileConfigs stayed undefined and the row showed "加载中..." forever. Track a per-bot error state and render an inline "加载失败 / 重试" link so the user can recover without page reload. Out of scope but documented: - ein=29 hardcoded in fetchCollectedPlaylists (no pagination yet — users with 30+ collected QQ playlists get truncated). - /play-song single-failure UX (returns "Cannot play" message but frontend ignores; needs a global toast/notification primitive). - NetEase has no analogous batch precheck (could surface same "click and wait silent" issue if user has region-restricted NetEase playlists). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
3d0aca52d0
commit
892d9f7959
4 files changed
+57
-15
No files matched your search
+17
-6
@@ -124,24 +124,35 @@ export class QQMusicProvider implements MusicProvider {
|
||||
* The wrapper's /getMusicPlay accepts a comma-separated songmid list
|
||||
* and resolves all of them in a single upstream call (~2-3s for 100+
|
||||
* songs), so this is much cheaper than per-song probing.
|
||||
*
|
||||
* Returns:
|
||||
* - non-null Set: authoritative result. Empty Set means all songs are
|
||||
* unplayable; non-empty means filter to those mids.
|
||||
* - null: the batch endpoint failed (timeout/exception). Caller
|
||||
* should fall back to sequential retry rather than treating as
|
||||
* "all unplayable", since we don't actually know.
|
||||
*
|
||||
* TODO: songIds with 1000+ entries may exceed URL length; chunk if
|
||||
* we ever support that scale.
|
||||
*/
|
||||
async getPlayableSongIds(songIds: string[]): Promise<Set<string>> {
|
||||
async getPlayableSongIds(songIds: string[]): Promise<Set<string> | null> {
|
||||
if (songIds.length === 0) return new Set();
|
||||
try {
|
||||
const res = await this.api.get("/getMusicPlay", {
|
||||
params: { songmid: songIds.join(","), quality: this.quality, ...this.cookieParams },
|
||||
});
|
||||
const playUrlMap: Record<string, { url?: string }> =
|
||||
res.data?.data?.playUrl ?? {};
|
||||
const playUrlMap: Record<string, { url?: string }> | undefined =
|
||||
res.data?.data?.playUrl;
|
||||
// Distinguish "endpoint returned no playUrl object at all" (treat
|
||||
// as failure → null) from "returned an empty/all-unplayable map".
|
||||
if (!playUrlMap) return null;
|
||||
const playable = new Set<string>();
|
||||
for (const [mid, info] of Object.entries(playUrlMap)) {
|
||||
if (info?.url) playable.add(mid);
|
||||
}
|
||||
return playable;
|
||||
} catch {
|
||||
// On any error, fall through to per-song retry path — return empty
|
||||
// and let the caller try songs sequentially via getSongUrl.
|
||||
return new Set();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user