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
@@ -233,6 +233,47 @@ app.delete('/api/romaji-overrides/:kanji', (req, res) => {
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// ─── ARTIST OVERRIDES (pin a local artist → a specific Deezer artist; shared) ──
|
||||
// Fixes same-name collisions where NovaSonic would otherwise guess the wrong Deezer artist
|
||||
// (fans / top songs / albums / image). Keyed by the Navidrome artist id.
|
||||
const _artistOverridesFile = path.join(CONFIG.dataPath, 'deezer-artist-overrides.json');
|
||||
function _loadArtistOverrides() {
|
||||
try { return JSON.parse(fs.readFileSync(_artistOverridesFile, 'utf8')); } catch { return {}; }
|
||||
}
|
||||
function _saveArtistOverrides(map) {
|
||||
try {
|
||||
fs.mkdirSync(CONFIG.dataPath, { recursive: true });
|
||||
fs.writeFileSync(_artistOverridesFile, JSON.stringify(map, null, 2), 'utf8');
|
||||
} catch (e) { console.error('Failed to save artist overrides:', e.message); }
|
||||
}
|
||||
|
||||
// GET — full map { ndArtistId: { deezerArtistId, name, image, fanCount } }
|
||||
app.get('/api/artist-overrides', (_req, res) => res.json(_loadArtistOverrides()));
|
||||
|
||||
// POST — upsert a pin { ndArtistId, deezerArtistId, name, image, fanCount }
|
||||
app.post('/api/artist-overrides', (req, res) => {
|
||||
const { ndArtistId, deezerArtistId, name, image, fanCount } = req.body || {};
|
||||
if (!ndArtistId || !deezerArtistId) return res.status(400).json({ error: 'ndArtistId and deezerArtistId required' });
|
||||
const map = _loadArtistOverrides();
|
||||
map[String(ndArtistId)] = {
|
||||
deezerArtistId: String(deezerArtistId),
|
||||
name: name || '',
|
||||
image: image || '',
|
||||
fanCount: (fanCount ?? null),
|
||||
};
|
||||
_saveArtistOverrides(map);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// DELETE /api/artist-overrides/:ndArtistId — remove a pin
|
||||
app.delete('/api/artist-overrides/:ndArtistId', (req, res) => {
|
||||
const id = decodeURIComponent(req.params.ndArtistId);
|
||||
const map = _loadArtistOverrides();
|
||||
delete map[id];
|
||||
_saveArtistOverrides(map);
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// POST /api/romaji-convert — convert an array of strings to romaji
|
||||
app.post('/api/romaji-convert', (req, res) => {
|
||||
const { texts } = req.body;
|
||||
|
||||
Reference in new issue
Block a user