fix(images): allow the CDNs artwork actually lives on, add Deezer artist photos
The image proxy allowlist covered none of the hosts enrichment writes, so every album cover answered 403: 305 covers sit on coverartarchive.org (which 302s to archive.org and on to an ia*.us.archive.org node), 126 on mzstatic, 115 on dzcdn, 26 on discogs. Artist images were empty for a different reason — no working source. Fanart needs a key the worker does not have, TheAudioDB and Discogs 404 on most names, Wikidata needs an MBID that 638 of 734 artists lack, and Last.fm stopped serving photos. Deezer needs no auth and its host is already allowlisted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,10 @@ interface DeezerSearchResponse {
|
||||
data?: DeezerSearchResult[];
|
||||
}
|
||||
|
||||
/** Lowercase and strip diacritics, so a query for "Bjork" matches "Björk". */
|
||||
const foldName = (name: string) =>
|
||||
name.trim().toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '');
|
||||
|
||||
export class DeezerClient {
|
||||
private readonly baseUrl = 'https://api.deezer.com';
|
||||
private readonly userAgent: string;
|
||||
@@ -81,4 +85,35 @@ export class DeezerClient {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Artist photo (1000×1000 `picture_xl`) by name, or null.
|
||||
*
|
||||
* ponytail: exact case-insensitive name match only. Deezer's artist search is
|
||||
* fuzzy and happily returns a tribute band for a near miss, so a wrong photo
|
||||
* is worse than the placeholder. Loosen it only if real artists get skipped.
|
||||
*/
|
||||
async searchArtistImage(artist: string): Promise<string | null> {
|
||||
if (artist.trim() === '') return null;
|
||||
const qs = new URLSearchParams({ q: artist });
|
||||
try {
|
||||
const data = await requestJson<{ data?: { name?: string; picture_xl?: string; picture_big?: string }[] }>(
|
||||
`${this.baseUrl}/search/artist?${qs.toString()}`,
|
||||
{ userAgent: this.userAgent, minIntervalMs: this.minIntervalMs }
|
||||
);
|
||||
const want = foldName(artist);
|
||||
for (const a of data.data ?? []) {
|
||||
if (foldName(a.name ?? '') !== want) continue;
|
||||
// Deezer serves a blank grey tile for artists it has no photo for: the
|
||||
// path is either empty or the md5 of the empty string. Both are useless,
|
||||
// and the same name can appear twice with only the second one real.
|
||||
const url = a.picture_xl ?? a.picture_big ?? '';
|
||||
if (url && !url.includes('d41d8cd98f00b204e9800998ecf8427e') && !url.includes('/artist//')) return url;
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.warn('[Deezer] searchArtistImage failed:', (err as Error).message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user