feat: move the whole metadata/search stack from iTunes to Deezer (1.3)
Apple's iTunes /search returns 403 from server/datacenter IPs, which had broken global search, charts, album/artist/single downloads and the metadata-editor autofill in production. Everything now sources metadata from Deezer (free, no key, more requests, returns explicit versions + real artist images + durations). Web + Android share one source, so an artist resolves to one consistent name everywhere. Deezer results are mapped to the legacy "ItunesItem" shape, so the UI + download flow are unchanged. Backend: - New backend/lib/deezer.js: lookupTrackMeta, fetchAlbumTracks, searchAlbumTracks, fetchArtistAlbums (+ pure pickTrackMatch/mapAlbumTrack for tests). - metadata.js drops the iTunes lookups; novasonic-server download paths (track, album, artist, playlist enrich, spotify artwork) + autofill now use Deezer. - Artist download uses Deezer discography (real albums only), not the iTunes album search. - Editor autofill endpoint /api/itunes/lookup -> /api/meta/lookup; removed the dead iTunes /search proxy and the Apple charts RSS proxy. - backend/test/deezer.test.js: unit tests for numbering, mixed-artist album-artist stability, explicit flags, the Japanese-title match guard (npm test; RUN_LIVE=1 for live). Web: - api.js search/lookups already on Deezer; charts now use Deezer /chart. Artist cards show the real Deezer photo. Visible "iTunes"/"Apple Music" labels -> "Deezer" (search badge, charts badge, preview bar, editor button, artist heading). SW ns-v51. Android: - New DeezerModels.kt with Dz* models + mappers to ItunesItem/ItunesRssEntry. - ItunesClient is Deezer-backed (search = tracks+albums+artists in parallel; album/artist lookups; /chart charts), same method signatures. Visible strings -> Deezer. Docs: README, CLAUDE.md and CHANGELOG (1.3) updated; Platform Parity Matrix reflects the shared Deezer source + Android non-local artist page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
a65613b5a1
commit
1e69831dd8
19 files changed
+628
-350
No files matched your search
+17
-41
@@ -267,23 +267,11 @@ export async function romajiConvert(texts) {
|
||||
} catch { return texts; }
|
||||
}
|
||||
|
||||
// ─── ITUNES ───
|
||||
// Route through backend proxy to avoid CORS; fall back to direct if no backend configured.
|
||||
function _itunesBase() {
|
||||
return S.cfg.backendUrl ? `${S.cfg.backendUrl}/api/itunes` : 'https://itunes.apple.com';
|
||||
}
|
||||
// Apple charts RSS — proxied through the backend (browsers can't fetch rss.applemarketingtools.com;
|
||||
// no CORS header). kind = albums | songs.
|
||||
function _chartsUrl(country, kind) {
|
||||
return S.cfg.backendUrl
|
||||
? `${S.cfg.backendUrl}/api/itunes-charts/${kind}?country=${country}`
|
||||
: `https://rss.applemarketingtools.com/api/v2/${country}/music/most-played/50/${kind}.json`;
|
||||
}
|
||||
|
||||
// ─── DEEZER (search source) ───
|
||||
// Apple's iTunes /search returns 403 from server IPs, so Deezer is the search backend. Free, no key,
|
||||
// returns explicit versions + artist images + track durations. Proxied through the backend to avoid
|
||||
// any CORS surprises. All results are mapped to the iTunes "ItunesItem" shape so the UI + download
|
||||
// ─── DEEZER (search + charts source) ───
|
||||
// Apple's iTunes /search returns 403 from server IPs, so Deezer is the metadata backend for
|
||||
// everything: search, album/artist lookups AND the "Neu & Beliebt" charts. Free, no key, returns
|
||||
// explicit versions + artist images + track durations. Proxied through the backend to avoid any
|
||||
// CORS surprises. All results are mapped to the legacy "ItunesItem" shape so the UI + download
|
||||
// flow stay unchanged.
|
||||
function _deezerBase() {
|
||||
return S.cfg.backendUrl ? `${S.cfg.backendUrl}/api/deezer` : 'https://api.deezer.com';
|
||||
@@ -380,39 +368,27 @@ export async function deezerArtistImage(artistId) {
|
||||
} catch { return null; }
|
||||
}
|
||||
|
||||
// "Neu & Beliebt" — Deezer's global chart (Apple's charts RSS needed a proxy and the search
|
||||
// fallback is 403 from server IPs). Mapped to { name, artistName, artworkUrl, artworkUrl100 }.
|
||||
export async function itunesTopCharts() {
|
||||
const urls = [_chartsUrl('de', 'albums'), _chartsUrl('us', 'albums')];
|
||||
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 {}
|
||||
}
|
||||
try {
|
||||
const r = await fetch(`${_itunesBase()}/search?term=new+music&media=music&entity=album&limit=50&country=de`);
|
||||
const r = await fetch(`${_deezerBase()}/chart/0/albums?limit=50`);
|
||||
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,
|
||||
return (d.data || []).map(a => ({
|
||||
name: a.title, artistName: a.artist?.name || '',
|
||||
artworkUrl: a.cover_big || a.cover_medium || '', artworkUrl100: a.cover_medium || '',
|
||||
}));
|
||||
} catch { return []; }
|
||||
}
|
||||
|
||||
export async function itunesTopSongs() {
|
||||
const urls = [_chartsUrl('de', 'songs'), _chartsUrl('us', 'songs')];
|
||||
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 {}
|
||||
}
|
||||
try {
|
||||
const r = await fetch(`${_itunesBase()}/search?term=top+hits&media=music&entity=musicTrack&limit=50&country=de`);
|
||||
const r = await fetch(`${_deezerBase()}/chart/0/tracks?limit=50`);
|
||||
const d = await r.json();
|
||||
return (d.results||[]).map(t => ({ name: t.trackName, artistName: t.artistName, artworkUrl: t.artworkUrl100 }));
|
||||
return (d.data || []).map(t => ({
|
||||
name: t.title, artistName: t.artist?.name || '',
|
||||
artworkUrl: t.album?.cover_medium || t.album?.cover_big || '',
|
||||
previewUrl: t.preview || '',
|
||||
}));
|
||||
} catch { return []; }
|
||||
}
|
||||
Reference in new issue
Block a user