feat(web): switch the Apple/Erkunden search to Deezer

Apple's iTunes /search returns 403 from server IPs, killing the Erkunden search. Deezer
is free, needs no key, and is actually better: it returns explicit versions, artist
images, track durations and 30s previews.

- Backend: /api/deezer/* proxy (CORS-safe); album download accepts a `tracks` array in
  the request body (Deezer albums send their tracks directly, pre-mapped).
- api.js: itunesSearch / itunesLookupAlbum / itunesLookupArtistSongs|Albums now hit
  Deezer and map results to the existing ItunesItem shape, so the UI + download flow are
  unchanged. Function names kept.
- downloads.js: album downloads fetch the Deezer album's tracks and send them in the body.

iTunes /lookup (by ID) and the charts RSS proxy stay. Known follow-ups: artist-type
download still calls iTunes /search server-side; the metadata-editor iTunes autofill;
and the Android client — all still on iTunes for now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Opus 4.8 committed 2026-07-30 20:54:10 +02:00
1 parent ccf704754a
commit 68a9cdd7d1
4 files changed
+103 -22

No files matched your search

+9 -1
View File
@@ -1,6 +1,6 @@
import { S } from '../state.js';
import { esc, toast } from '../utils.js';
import { api, fetchRomajiIndex, romajiConvert, getRomajiOverrides, invalidateRomajiIndex } from '../api.js';
import { api, fetchRomajiIndex, romajiConvert, getRomajiOverrides, invalidateRomajiIndex, itunesLookupAlbum } from '../api.js';
import { _addNotif } from './notify.js';
// ─── DOWNLOAD MODAL ───
@@ -47,6 +47,14 @@ export async function executeDownload() {
...(ov[_title] && { titleSort: ov[_title] }),
...(ov[_album] && { albumSort: ov[_album] }),
};
// Album download: fetch the album's tracks from Deezer (id in collectionId) and send them in the
// request — the backend downloads that list directly (Apple's /search is 403, so no iTunes lookup).
if (S.dlType === 'album' && payload.collectionId) {
try {
const albumTracks = await itunesLookupAlbum(payload.collectionId);
if (albumTracks.length) payload.tracks = albumTracks;
} catch {}
}
addJob(jobId, payload.title, payload.artist, payload.type, payload.album);
try {
const res = await fetch(`${S.cfg.backendUrl}/api/download`, {