diff --git a/backend/novasonic-server.js b/backend/novasonic-server.js index 9c2f7e3..a5279b8 100644 --- a/backend/novasonic-server.js +++ b/backend/novasonic-server.js @@ -94,33 +94,47 @@ async function fetchSpotifyToken() { async function _spApi(path, token) { const url = path.startsWith('http') ? path : `https://api.spotify.com/v1${path}`; - const r = await fetch(url, { - headers: { Authorization: `Bearer ${token}` }, - signal: AbortSignal.timeout(10000), - }); - if (!r.ok) throw new Error(`Spotify API Fehler ${r.status}: ${path}`); - return r.json(); + for (let attempt = 0; attempt < 4; attempt++) { + const r = await fetch(url, { + headers: { Authorization: `Bearer ${token}` }, + signal: AbortSignal.timeout(10000), + }); + 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) { const tracks = []; + let skippedLocal = 0; let path = `/playlists/${id}/tracks?limit=100`; - while (path) { + let pages = 0; + while (path && pages++ < 100) { const page = await _spApi(path, token); 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; } - return tracks; + return { tracks, skippedLocal }; } async function _spAllAlbumTracks(id, token) { const tracks = []; let path = `/albums/${id}/tracks?limit=50`; - while (path) { + let pages = 0; + while (path && pages++ < 100) { const page = await _spApi(path, token); - tracks.push(...(page.items || [])); + tracks.push(...(page.items || []).filter(t => t?.id)); path = page.next || null; } 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 owner = pl.owner?.display_name || ''; const artUrl = _spArtwork(pl); - const trackItems = await _spAllPlaylistTracks(id, token); + const { tracks: trackItems, skippedLocal } = await _spAllPlaylistTracks(id, token); return res.json({ type: 'playlist', name: pl.name || '', artist: owner, artworkUrl: artUrl, description: pl.description || '', + skippedLocal, tracks: trackItems.map(t => _spTrackItem(t, null, null, null)), }); } diff --git a/src/spotify.js b/src/spotify.js index fe507e3..f28ca2a 100644 --- a/src/spotify.js +++ b/src/spotify.js @@ -51,6 +51,7 @@ export async function resolveSpotifyLink() { } const data = await res.json(); window._spotifyResolved = data; + if (data.skippedLocal > 0) toast(`${data.skippedLocal} lokale Datei(en) übersprungen (kein Stream verfügbar)`); _renderSpotifyPreview(data); } catch(e) { inner.innerHTML = `