fix: proxy all iTunes/Apple RSS calls through backend to bypass 403 browser block

Apple now blocks direct browser requests from custom origins. Added backend proxy routes:
  GET /api/itunes/search  — iTunes search
  GET /api/itunes/lookup  — iTunes lookup by ID
  GET /api/itunes/charts  — Apple RSS most-played charts (albums/songs)

All itunesSearch/Lookup/TopCharts/TopSongs functions in api.js now route through backend.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Sonnet 4.6 committed 2026-04-28 00:57:29 +02:00
1 parent 2b6870583b
commit 5ef6a6d0ba
2 files changed
+65 -50

No files matched your search

+42
View File
@@ -483,6 +483,48 @@ 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, { headers: { 'User-Agent': 'iTunes/12.0' } }, 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) => {
+23 -50
View File
@@ -39,76 +39,49 @@ export async function ndAuth() {
return S._ndToken;
}
// ─── ITUNES ───
const ITUNES = 'https://itunes.apple.com';
// ─── ITUNES (proxied through backend to avoid Apple's browser origin block) ───
function _iBase() { return S.cfg.backendUrl || ''; }
async function _iGet(path) {
try {
const r = await fetch(_iBase() + path);
if (!r.ok) return {};
return await r.json();
} catch { return {}; }
}
export async function itunesSearch(query, entity='musicTrack', limit=25) {
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 []; }
const d = await _iGet(`/api/itunes/search?term=${encodeURIComponent(query)}&entity=${entity}&limit=${limit}`);
return d.results || [];
}
export async function itunesLookupAlbum(collectionId) {
const r = await fetch(`${ITUNES}/lookup?id=${collectionId}&entity=song&explicit=Yes`);
const d = await r.json();
const d = await _iGet(`/api/itunes/lookup?id=${collectionId}&entity=song`);
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 r = await fetch(`${ITUNES}/lookup?id=${artistId}&entity=song&limit=${limit}&country=us&explicit=Yes`);
const d = await r.json();
const d = await _iGet(`/api/itunes/lookup?id=${artistId}&entity=song&limit=${limit}`);
return (d.results || []).filter(x => x.wrapperType === 'track');
}
export async function itunesLookupArtistAlbums(artistId, limit=200) {
const r = await fetch(`${ITUNES}/lookup?id=${artistId}&entity=album&limit=${limit}&country=us`);
const d = await r.json();
const d = await _iGet(`/api/itunes/lookup?id=${artistId}&entity=album&limit=${limit}`);
return (d.results || []).filter(x => x.wrapperType === 'collection' && x.collectionType === 'Album');
}
export async function itunesTopCharts() {
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 {}
for (const country of ['de', 'us']) {
const d = await _iGet(`/api/itunes/charts?type=albums&country=${country}`);
if (d.results?.length) return d.results;
}
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 []; }
return [];
}
export async function itunesTopSongs() {
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 {}
for (const country of ['de', 'us']) {
const d = await _iGet(`/api/itunes/charts?type=songs&country=${country}`);
if (d.results?.length) return d.results;
}
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 []; }
return [];
}