fix: 429 retry, safety counter, local file filter in Spotify pagination
This commit is contained in:
1 parent
b7f09058a9
commit
7c1c444bf2
2 files changed
+28
-12
No files matched your search
+27
-12
@@ -94,33 +94,47 @@ async function fetchSpotifyToken() {
|
|||||||
|
|
||||||
async function _spApi(path, token) {
|
async function _spApi(path, token) {
|
||||||
const url = path.startsWith('http') ? path : `https://api.spotify.com/v1${path}`;
|
const url = path.startsWith('http') ? path : `https://api.spotify.com/v1${path}`;
|
||||||
const r = await fetch(url, {
|
for (let attempt = 0; attempt < 4; attempt++) {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
const r = await fetch(url, {
|
||||||
signal: AbortSignal.timeout(10000),
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
});
|
signal: AbortSignal.timeout(10000),
|
||||||
if (!r.ok) throw new Error(`Spotify API Fehler ${r.status}: ${path}`);
|
});
|
||||||
return r.json();
|
if (r.status === 429) {
|
||||||
|
const wait = (parseInt(r.headers.get('Retry-After') || '5', 10) + 1) * 1000;
|
||||||
|
await new Promise(res => setTimeout(res, wait));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!r.ok) throw new Error(`Spotify API Fehler ${r.status}: ${path}`);
|
||||||
|
return r.json();
|
||||||
|
}
|
||||||
|
throw new Error('Spotify API rate-limited — bitte kurz warten und erneut versuchen');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _spAllPlaylistTracks(id, token) {
|
async function _spAllPlaylistTracks(id, token) {
|
||||||
const tracks = [];
|
const tracks = [];
|
||||||
|
let skippedLocal = 0;
|
||||||
let path = `/playlists/${id}/tracks?limit=100`;
|
let path = `/playlists/${id}/tracks?limit=100`;
|
||||||
while (path) {
|
let pages = 0;
|
||||||
|
while (path && pages++ < 100) {
|
||||||
const page = await _spApi(path, token);
|
const page = await _spApi(path, token);
|
||||||
for (const item of page.items || []) {
|
for (const item of page.items || []) {
|
||||||
if (item.track?.name) tracks.push(item.track);
|
const t = item.track;
|
||||||
|
if (!t || !t.id) continue;
|
||||||
|
if (t.is_local) { skippedLocal++; continue; }
|
||||||
|
tracks.push(t);
|
||||||
}
|
}
|
||||||
path = page.next || null;
|
path = page.next || null;
|
||||||
}
|
}
|
||||||
return tracks;
|
return { tracks, skippedLocal };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _spAllAlbumTracks(id, token) {
|
async function _spAllAlbumTracks(id, token) {
|
||||||
const tracks = [];
|
const tracks = [];
|
||||||
let path = `/albums/${id}/tracks?limit=50`;
|
let path = `/albums/${id}/tracks?limit=50`;
|
||||||
while (path) {
|
let pages = 0;
|
||||||
|
while (path && pages++ < 100) {
|
||||||
const page = await _spApi(path, token);
|
const page = await _spApi(path, token);
|
||||||
tracks.push(...(page.items || []));
|
tracks.push(...(page.items || []).filter(t => t?.id));
|
||||||
path = page.next || null;
|
path = page.next || null;
|
||||||
}
|
}
|
||||||
return tracks;
|
return tracks;
|
||||||
@@ -232,13 +246,14 @@ app.post('/api/spotify/resolve', async (req, res) => {
|
|||||||
const pl = await _spApi(`/playlists/${id}?fields=name,description,owner,images`, token);
|
const pl = await _spApi(`/playlists/${id}?fields=name,description,owner,images`, token);
|
||||||
const owner = pl.owner?.display_name || '';
|
const owner = pl.owner?.display_name || '';
|
||||||
const artUrl = _spArtwork(pl);
|
const artUrl = _spArtwork(pl);
|
||||||
const trackItems = await _spAllPlaylistTracks(id, token);
|
const { tracks: trackItems, skippedLocal } = await _spAllPlaylistTracks(id, token);
|
||||||
return res.json({
|
return res.json({
|
||||||
type: 'playlist',
|
type: 'playlist',
|
||||||
name: pl.name || '',
|
name: pl.name || '',
|
||||||
artist: owner,
|
artist: owner,
|
||||||
artworkUrl: artUrl,
|
artworkUrl: artUrl,
|
||||||
description: pl.description || '',
|
description: pl.description || '',
|
||||||
|
skippedLocal,
|
||||||
tracks: trackItems.map(t => _spTrackItem(t, null, null, null)),
|
tracks: trackItems.map(t => _spTrackItem(t, null, null, null)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export async function resolveSpotifyLink() {
|
|||||||
}
|
}
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
window._spotifyResolved = data;
|
window._spotifyResolved = data;
|
||||||
|
if (data.skippedLocal > 0) toast(`${data.skippedLocal} lokale Datei(en) übersprungen (kein Stream verfügbar)`);
|
||||||
_renderSpotifyPreview(data);
|
_renderSpotifyPreview(data);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
inner.innerHTML = `<div style="color:var(--danger);padding:16px;background:var(--surface);border-radius:10px">${esc(e.message)}</div>`;
|
inner.innerHTML = `<div style="color:var(--danger);padding:16px;background:var(--surface);border-radius:10px">${esc(e.message)}</div>`;
|
||||||
|
|||||||
Reference in new issue
Block a user