feat(artist pin): pin by Deezer link/ID when search can't surface the artist

Deezer's /search/artist is a ranked, incomplete fuzzy match — low-profile
same-name artists (e.g. Nana id 80239872) never appear in results. Add a
"paste Deezer link or ID" field to the pin picker that looks the artist up
directly via /artist/{id} (new deezerLookupArtist) and pins it. Same reliable
route as the importer direct link vs yt-dlp search.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
HoneyandClaude Opus 4.8 committed 2026-08-13 00:38:33 +02:00
1 parent 5f251d555f
commit ba2306656e
3 files changed
+62 -6

No files matched your search

+47 -5
View File
@@ -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
? `<button onclick="clearArtistPin()" style="width:100%;padding:10px;margin-bottom:10px;border-radius:9px;border:1px solid var(--border);background:transparent;color:var(--danger);font-family:var(--font);font-weight:600;cursor:pointer">Pin entfernen (aktuell: ${esc(cur.name || 'gepinnt')})</button>`
: '';
body.innerHTML = clearBtn + `<div style="text-align:center;color:var(--text2);padding:24px 0">Suche…</div>`;
// 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 = `
<div style="display:flex;gap:8px;margin-bottom:12px">
<input id="artist-pin-link" type="text" placeholder="Deezer-Link oder ID einfügen…"
style="flex:1;min-width:0;padding:10px 12px;border-radius:9px;border:1px solid var(--border);background:var(--surface2);color:var(--text);font-family:var(--font);font-size:13px">
<button id="artist-pin-link-btn" style="padding:0 16px;border-radius:9px;border:none;background:var(--accent);color:#fff;font-family:var(--font);font-weight:700;cursor:pointer;white-space:nowrap">Pinnen</button>
</div>
<div style="font-size:11px;color:var(--text2);margin:-6px 2px 12px">Nicht in der Liste? Öffne den Künstler auf deezer.com und füge den Link ein.</div>`;
// 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 + `<div id="artist-pin-results"><div style="text-align:center;color:var(--text2);padding:24px 0">Suche…</div></div>`;
_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() {
</div>
</div>`).join('');
body.innerHTML = clearBtn + (rows || `<div style="text-align:center;color:var(--text2);padding:24px 0">Keine Deezer-Künstler gefunden.</div>`);
body.querySelectorAll('.artist-pin-cand').forEach(el => el.addEventListener('click', () => _pick(el.dataset)));
resultsEl.innerHTML = rows || `<div style="text-align:center;color:var(--text2);padding:24px 0">Keine Deezer-Künstler gefunden.</div>`;
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) {