feat(artist): pin a local artist to a specific Deezer artist (same-name fix, shared)
Fixes same-name collisions where NovaSonic guessed the wrong Deezer artist (e.g. 'Nana' → a random DJ): fans, top songs, missing albums and the hero image were all off. A '⚙ Falscher Künstler?' button on the artist page opens a picker (same-name Deezer candidates with image + fan count); the chosen mapping is stored SERVER-SIDE (shared across users, like the romaji overrides) in /data/deezer-artist-overrides.json. Once pinned, the artist page short-circuits the name search to the pinned Deezer id (drives albums/top-songs/fans) and overrides the hero image with the pinned artist's Deezer picture (NovaSonic-side, so it works even for tag-merged same-name artists — Navidrome can't disambiguate them without an MBID). New endpoints GET/POST/DELETE /api/artist-overrides; api.js load/get/set/remove; loaded at startup. SW cache -> ns-v74. Needs on-device runtime test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
b788fedec3
commit
02960cf886
6 files changed
+204
-7
No files matched your search
+40
@@ -194,6 +194,46 @@ export async function removeRomajiOverride(kanji) {
|
||||
} catch (e) { console.warn('removeRomajiOverride failed:', e); }
|
||||
}
|
||||
|
||||
// ─── ARTIST OVERRIDES (pin a local artist → a specific Deezer artist; shared) ───
|
||||
// Fixes same-name collisions on the artist page (fans / top songs / albums / image).
|
||||
let _artistOverridesCache = null;
|
||||
export function getArtistOverrides() { return _artistOverridesCache || {}; }
|
||||
export function getArtistOverride(ndArtistId) { return (_artistOverridesCache || {})[String(ndArtistId)] || null; }
|
||||
|
||||
export async function loadArtistOverrides() {
|
||||
if (!S.cfg?.backendUrl) { _artistOverridesCache = {}; return; }
|
||||
try {
|
||||
const r = await fetch(`${S.cfg.backendUrl}/api/artist-overrides`);
|
||||
_artistOverridesCache = r.ok ? await r.json() : {};
|
||||
} catch { _artistOverridesCache = {}; }
|
||||
}
|
||||
|
||||
export async function setArtistOverride(ndArtistId, deezer) {
|
||||
if (!S.cfg?.backendUrl || !ndArtistId || !deezer?.deezerArtistId) return;
|
||||
const entry = {
|
||||
deezerArtistId: String(deezer.deezerArtistId),
|
||||
name: deezer.name || '',
|
||||
image: deezer.image || '',
|
||||
fanCount: deezer.fanCount ?? null,
|
||||
};
|
||||
try {
|
||||
await fetch(`${S.cfg.backendUrl}/api/artist-overrides`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ndArtistId: String(ndArtistId), ...entry }),
|
||||
});
|
||||
if (!_artistOverridesCache) _artistOverridesCache = {};
|
||||
_artistOverridesCache[String(ndArtistId)] = entry;
|
||||
} catch (e) { console.warn('setArtistOverride failed:', e); }
|
||||
}
|
||||
|
||||
export async function removeArtistOverride(ndArtistId) {
|
||||
if (!S.cfg?.backendUrl || !ndArtistId) return;
|
||||
try {
|
||||
await fetch(`${S.cfg.backendUrl}/api/artist-overrides/${encodeURIComponent(ndArtistId)}`, { method: 'DELETE' });
|
||||
if (_artistOverridesCache) delete _artistOverridesCache[String(ndArtistId)];
|
||||
} catch (e) { console.warn('removeArtistOverride failed:', e); }
|
||||
}
|
||||
|
||||
export function dispArtistById(id, name) {
|
||||
if (!S.cfg.showRomaji) return name || '';
|
||||
const ov = getRomajiOverrides();
|
||||
|
||||
Reference in new issue
Block a user