diff --git a/backend/novasonic-server.js b/backend/novasonic-server.js index e607278..e19cf24 100644 --- a/backend/novasonic-server.js +++ b/backend/novasonic-server.js @@ -483,48 +483,6 @@ function _spTrackItem(t, defaultAlbum, defaultArt, defaultAlbumArtist) { }; } -// ─── ITUNES PROXY ──────────────────────────────────────────────────────────── -// Browser origin is blocked by Apple (403). All iTunes/Apple RSS calls go through here. - -async function itunesProxy(url) { - return new Promise((resolve, reject) => { - https.get(url, res => { - let d = ''; - res.on('data', c => d += c); - res.on('end', () => { try { resolve(JSON.parse(d)); } catch { resolve({}); } }); - }).on('error', reject); - }); -} - -app.get('/api/itunes/search', async (req, res) => { - try { - const { term, entity = 'musicTrack', limit = 25, country = 'us' } = req.query; - if (!term) return res.json({ results: [] }); - const url = `https://itunes.apple.com/search?term=${encodeURIComponent(term)}&media=music&entity=${entity}&limit=${limit}&country=${country}&explicit=Yes`; - const d = await itunesProxy(url); - res.json({ results: d.results || [] }); - } catch { res.json({ results: [] }); } -}); - -app.get('/api/itunes/lookup', async (req, res) => { - try { - const { id, entity = 'song', limit = 200, country = 'us' } = req.query; - if (!id) return res.json({ results: [] }); - const url = `https://itunes.apple.com/lookup?id=${id}&entity=${entity}&limit=${limit}&country=${country}&explicit=Yes`; - const d = await itunesProxy(url); - res.json({ results: d.results || [] }); - } catch { res.json({ results: [] }); } -}); - -app.get('/api/itunes/charts', async (req, res) => { - try { - const { type = 'albums', country = 'de' } = req.query; - const url = `https://rss.applemarketingtools.com/api/v2/${country}/music/most-played/50/${type}.json`; - const d = await itunesProxy(url); - res.json(d.feed ? { results: d.feed.results || [] } : { results: [] }); - } catch { res.json({ results: [] }); } -}); - // ─── ROUTES ────────────────────────────────────────────────────────────────── app.post('/api/spotify/track-artwork', async (req, res) => { diff --git a/src/api.js b/src/api.js index 49c801d..33c9024 100644 --- a/src/api.js +++ b/src/api.js @@ -40,71 +40,75 @@ export async function ndAuth() { } // ─── ITUNES ─── -// Try backend proxy first (avoids Apple's 403 browser-origin block). -// Fall back to direct Apple API if no backend is configured or proxy fails. const ITUNES = 'https://itunes.apple.com'; -const APPLE_RSS = 'https://rss.applemarketingtools.com'; - -async function _fetch(url) { - try { - const r = await fetch(url); - const text = await r.text(); - if (!text) return {}; - return JSON.parse(text); - } catch { return {}; } -} - -async function _iGet(proxyPath, directUrl) { - const base = S.cfg.backendUrl; - if (base) { - const d = await _fetch(base + proxyPath); - if (d?.results?.length) return d; // proxy returned real results - if (d?.results !== undefined) return d; // proxy responded but iTunes had no results (don't double-fetch) - } - return directUrl ? _fetch(directUrl) : {}; -} export async function itunesSearch(query, entity='musicTrack', limit=25) { - const qs = `term=${encodeURIComponent(query)}&media=music&entity=${entity}&limit=${limit}&country=us&explicit=Yes`; - const d = await _iGet(`/api/itunes/search?term=${encodeURIComponent(query)}&entity=${entity}&limit=${limit}`, - `${ITUNES}/search?${qs}`); - return d.results || []; + try { + const r = await fetch(`${ITUNES}/search?term=${encodeURIComponent(query)}&media=music&entity=${entity}&limit=${limit}&country=us&explicit=Yes`); + const text = await r.text(); + if (!text) return []; + return JSON.parse(text).results || []; + } catch { return []; } } export async function itunesLookupAlbum(collectionId) { - const d = await _iGet(`/api/itunes/lookup?id=${collectionId}&entity=song`, - `${ITUNES}/lookup?id=${collectionId}&entity=song&explicit=Yes`); + const r = await fetch(`${ITUNES}/lookup?id=${collectionId}&entity=song&explicit=Yes`); + const d = await r.json(); return (d.results || []).filter(x => x.wrapperType === 'track'); } +// Returns actual explicit tracks for an artist via lookup (search endpoint only returns cleaned versions) export async function itunesLookupArtistSongs(artistId, limit=15) { - const d = await _iGet(`/api/itunes/lookup?id=${artistId}&entity=song&limit=${limit}`, - `${ITUNES}/lookup?id=${artistId}&entity=song&limit=${limit}&country=us&explicit=Yes`); + const r = await fetch(`${ITUNES}/lookup?id=${artistId}&entity=song&limit=${limit}&country=us&explicit=Yes`); + const d = await r.json(); return (d.results || []).filter(x => x.wrapperType === 'track'); } export async function itunesLookupArtistAlbums(artistId, limit=200) { - const d = await _iGet(`/api/itunes/lookup?id=${artistId}&entity=album&limit=${limit}`, - `${ITUNES}/lookup?id=${artistId}&entity=album&limit=${limit}&country=us`); + const r = await fetch(`${ITUNES}/lookup?id=${artistId}&entity=album&limit=${limit}&country=us`); + const d = await r.json(); return (d.results || []).filter(x => x.wrapperType === 'collection' && x.collectionType === 'Album'); } export async function itunesTopCharts() { - for (const country of ['de', 'us']) { - const d = await _iGet(`/api/itunes/charts?type=albums&country=${country}`, - `${APPLE_RSS}/api/v2/${country}/music/most-played/50/albums.json`); - const results = d.results || d.feed?.results; - if (results?.length) return results; + const urls = [ + 'https://rss.applemarketingtools.com/api/v2/de/music/most-played/50/albums.json', + 'https://rss.applemarketingtools.com/api/v2/us/music/most-played/50/albums.json', + ]; + for(const url of urls) { + try { + const r = await fetch(url, { signal: AbortSignal.timeout(6000) }); + if(!r.ok) continue; + const d = await r.json(); + if(d.feed?.results?.length) return d.feed.results; + } catch {} } - return []; + try { + const r = await fetch('https://itunes.apple.com/search?term=new+music&media=music&entity=album&limit=50&country=de'); + const d = await r.json(); + return (d.results||[]).map(a => ({ + name: a.collectionName, artistName: a.artistName, + artworkUrl: a.artworkUrl100?.replace('100x100','300x300'), artworkUrl100: a.artworkUrl100, + })); + } catch { return []; } } export async function itunesTopSongs() { - for (const country of ['de', 'us']) { - const d = await _iGet(`/api/itunes/charts?type=songs&country=${country}`, - `${APPLE_RSS}/api/v2/${country}/music/most-played/50/songs.json`); - const results = d.results || d.feed?.results; - if (results?.length) return results; + const urls = [ + 'https://rss.applemarketingtools.com/api/v2/de/music/most-played/50/songs.json', + 'https://rss.applemarketingtools.com/api/v2/us/music/most-played/50/songs.json', + ]; + for(const url of urls) { + try { + const r = await fetch(url, { signal: AbortSignal.timeout(6000) }); + if(!r.ok) continue; + const d = await r.json(); + if(d.feed?.results?.length) return d.feed.results; + } catch {} } - return []; + try { + const r = await fetch('https://itunes.apple.com/search?term=top+hits&media=music&entity=musicTrack&limit=50&country=de'); + const d = await r.json(); + return (d.results||[]).map(t => ({ name: t.trackName, artistName: t.artistName, artworkUrl: t.artworkUrl100 })); + } catch { return []; } }