fix(enrich): coverFast — очищенный iTunes + Deezer (не множить лимит)

Прошлый вариант делал 2 запроса iTunes на каждый now-playing-трек без обложки
(170+ играющих) → упирался в лимит iTunes ~20/мин, обложки не наливались.
coverFast теперь: 1 запрос iTunes по ОЧИЩЕННОМУ названию (чаще матчит ремиксы/
«(Original Mix)») → на промахе/429 фолбэк Deezer (отдельный лимит). Полный
fetchItunes (full→clean→deezer) остаётся для фонового enrichOne.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
nk
2026-06-04 16:06:43 +03:00
parent ba9b4054e8
commit 59aa23ff77

View File

@@ -103,7 +103,10 @@ export class EnrichmentService {
} }
} }
// Только обложка через iTunes (без Discogs) — для быстрого покрытия эфира // Только обложка — для быстрого покрытия эфира. Чтобы не множить нагрузку на
// iTunes (лимит ~20/мин на 170+ играющих треков), делаем ОДИН запрос iTunes по
// очищенному названию (он чаще матчит ремиксы/«(Original Mix)»), а на промахе/
// лимите идём в Deezer (отдельный лимит, хорошее покрытие электроники).
private async coverFast(t: { private async coverFast(t: {
id: string; id: string;
artist: string; artist: string;
@@ -111,24 +114,42 @@ export class EnrichmentService {
normKey: string; normKey: string;
}): Promise<void> { }): Promise<void> {
try { try {
const itunes = await this.fetchItunes(t.artist, t.song); const cover = await this.fetchCover(t.artist, t.song);
const candidate = itunes?.coverUrl; if (!cover?.coverUrl) return;
if (!candidate) return; const stored = await this.covers.store(cover.coverUrl, t.normKey);
const stored = await this.covers.store(candidate, t.normKey);
if (!stored) return; if (!stored) return;
await this.prisma.track.update({ await this.prisma.track.update({
where: { id: t.id }, where: { id: t.id },
data: { data: {
coverUrl: stored, coverUrl: stored,
genre: itunes?.genre ?? undefined, genre: cover.genre ?? undefined,
album: itunes?.album ?? undefined, album: cover.album ?? undefined,
}, },
}); });
} catch { } catch {
// сбой iTunes (429/сеть) — добёрём на следующем тике // сбой — добёрём на следующем тике
} }
} }
/** Только обложка: один iTunes (очищенный) → Deezer. Не бросает. */
private async fetchCover(
artist: string,
song: string,
): Promise<{ coverUrl: string | null; genre: string | null; album: string | null } | null> {
const cleaned =
`${this.stripNoise(artist)} ${this.stripNoise(song)}`.replace(/\s+/g, ' ').trim() ||
`${artist} ${song}`;
try {
const r = await this.itunesSearch(cleaned);
if (r?.coverUrl) return { coverUrl: r.coverUrl, genre: r.genre, album: r.album };
} catch {
// iTunes 429/сеть — попробуем Deezer
}
const dz = await this.fetchDeezerCover(artist, song);
if (dz) return { coverUrl: dz, genre: null, album: null };
return null;
}
// Нормализованный ключ — как в ChartsService.recordPlay // Нормализованный ключ — как в ChartsService.recordPlay
private buildNormKey(artist: string, song: string): string { private buildNormKey(artist: string, song: string): string {
return ( return (