diff --git a/public/sw.js b/public/sw.js index 36c522e..e656c12 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'ns-v75'; +const CACHE = 'ns-v76'; const PRECACHE = ['/', 'index.html', 'manifest.json', 'icon.svg']; const SKIP_PATHS = ['/rest/stream', '/rest/download', '/config.js']; // Auth token params stripped from cache keys so artwork survives across sessions/token refreshes. diff --git a/src/api.js b/src/api.js index d2cc7e0..2d4accb 100644 --- a/src/api.js +++ b/src/api.js @@ -425,6 +425,20 @@ export async function deezerLookupArtistAlbums(artistId, limit=100, artistName=' } catch { return []; } } +// Direct artist lookup by Deezer id — the reliable route when /search/artist doesn't surface a +// low-profile same-name artist (Deezer search is a ranked, incomplete fuzzy match; e.g. the browser +// finds Nana id 80239872 but the search API never returns it). Used by the artist-pin "paste link" +// path. Returns the full _dzArtist shape (or null on miss / bad id). +export async function deezerLookupArtist(artistId) { + try { + const r = await fetch(`${_deezerBase()}/artist/${artistId}`); + if (!r.ok) return null; + const a = await r.json(); + if (!a || a.error || !a.id) return null; + return _dzArtist(a); + } catch { return null; } +} + // Deezer artist photo (for the hero on non-local artist pages). iTunes artist entities had none. export async function deezerArtistImage(artistId) { try { diff --git a/src/ui/artistPin.js b/src/ui/artistPin.js index b6ed00f..5cf0f4e 100644 --- a/src/ui/artistPin.js +++ b/src/ui/artistPin.js @@ -4,8 +4,19 @@ // NovaSonic would otherwise guess the wrong one (fans / top songs / albums / image). // The mapping is stored server-side (shared across users) — see api.js. -import { deezerSearch, setArtistOverride, removeArtistOverride, getArtistOverride } from '../api.js'; -import { esc, fmtCount } from '../utils.js'; +import { deezerSearch, deezerLookupArtist, setArtistOverride, removeArtistOverride, getArtistOverride } from '../api.js'; +import { esc, fmtCount, toast } from '../utils.js'; + +// Pull a Deezer artist id out of whatever the user pastes: a full URL +// (deezer.com/en/artist/80239872, /de/artist/…?…), or a bare id ("80239872"). +function _parseDeezerArtistId(raw) { + const s = String(raw || '').trim(); + if (!s) return null; + const m = s.match(/artist\/(\d+)/i); + if (m) return m[1]; + if (/^\d+$/.test(s)) return s; + return null; +} let _overlay = null; let _ctx = null; // { ndArtistId, ndName } @@ -50,7 +61,20 @@ async function _render() { const clearBtn = cur ? `` : ''; - body.innerHTML = clearBtn + `
Suche…
`; + // Deezer's /search/artist is a ranked, incomplete fuzzy match — low-profile same-name artists + // (e.g. Nana id 80239872) never surface. Paste the Deezer link/id to pin directly (reliable route). + const linkBox = ` +
+ + +
+
Nicht in der Liste? Öffne den Künstler auf deezer.com und füge den Link ein.
`; + // Render the shell once (clear button + link box stay put); only the results list re-renders, + // so a value the user is typing into the link field survives the async search resolving. + body.innerHTML = clearBtn + linkBox + `
Suche…
`; + _wireLinkBox(); + const resultsEl = body.querySelector('#artist-pin-results'); let results = []; try { results = (await deezerSearch(_ctx.ndName, 'musicArtist', 8)) || []; } catch {} @@ -67,8 +91,26 @@ async function _render() { `).join(''); - body.innerHTML = clearBtn + (rows || `
Keine Deezer-Künstler gefunden.
`); - body.querySelectorAll('.artist-pin-cand').forEach(el => el.addEventListener('click', () => _pick(el.dataset))); + resultsEl.innerHTML = rows || `
Keine Deezer-Künstler gefunden.
`; + resultsEl.querySelectorAll('.artist-pin-cand').forEach(el => el.addEventListener('click', () => _pick(el.dataset))); +} + +// Wire the "paste a Deezer link/id" box → look the artist up directly and pin it. +function _wireLinkBox() { + const input = _overlay.querySelector('#artist-pin-link'); + const btn = _overlay.querySelector('#artist-pin-link-btn'); + if (!input || !btn) return; + const go = async () => { + const id = _parseDeezerArtistId(input.value); + if (!id) { toast('Kein gültiger Deezer-Link / keine ID'); return; } + btn.disabled = true; btn.textContent = '…'; + const a = await deezerLookupArtist(id); + btn.disabled = false; btn.textContent = 'Pinnen'; + if (!a) { toast('Deezer-Künstler nicht gefunden'); return; } + await _pick({ id: String(a.artistId), name: a.artistName || _ctx.ndName, img: a.artworkUrl100 || '', fans: a.fanCount != null ? String(a.fanCount) : '' }); + }; + btn.addEventListener('click', go); + input.addEventListener('keydown', e => { if (e.key === 'Enter') go(); }); } async function _pick(ds) {