From 971cadd8fa9395635a28621f22369e234a637004 Mon Sep 17 00:00:00 2001 From: Honey Date: Sat, 2 May 2026 14:57:51 +0200 Subject: [PATCH] fix: match-filter trailing-s optional + playlist search fallback for special chars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - buildTitleKeywords: append '?' to keywords ending in 's' so regex "Whispers?" matches both "Whisper" (YouTube title) and "Whispers" (iTunes title) — fixes NoAki Whispers downloading wrong track - _createNdPlaylist: if artist+title search returns no title match, retry with title-only query so tracks with special chars in artist name (e.g. "Kløn") are still found in Navidrome Co-Authored-By: Claude Sonnet 4.6 --- backend/novasonic-server.js | 4 +++- src/ui.js | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/novasonic-server.js b/backend/novasonic-server.js index d2338e7..9c1ff12 100644 --- a/backend/novasonic-server.js +++ b/backend/novasonic-server.js @@ -83,7 +83,9 @@ function buildTitleKeywords(title) { .filter(w => w.length >= 3 && !VARIANT_WORDS.has(w.toLowerCase()) && !STOPWORDS.has(w.toLowerCase())); const main = words.sort((a, b) => b.length - a.length)[0] || ''; const qualifier = lower.includes('mix') ? MIX_QUALIFIERS.find(q => lower.includes(q)) : null; - const result = main ? [main] : []; + // Make trailing 's' optional so "Whispers?" matches both "Whisper" and "Whispers" in yt-dlp regex + const toPattern = w => (w.length >= 4 && w.endsWith('s')) ? w + '?' : w; + const result = main ? [toPattern(main)] : []; if (qualifier && main.toLowerCase() !== qualifier) result.push(qualifier); return result; } diff --git a/src/ui.js b/src/ui.js index 8958e59..4befad5 100644 --- a/src/ui.js +++ b/src/ui.js @@ -380,7 +380,15 @@ async function _createNdPlaylist(tracks, name) { const firstArtist = t.artistName ? t.artistName.split(/[,/•]/)[0].trim() : ''; const artistQuery = firstArtist ? `${firstArtist} ${t.trackName}` : t.trackName; const r = await api('search3', `&query=${encodeURIComponent(artistQuery)}&songCount=20&albumCount=0&artistCount=0`); - const songs = r.searchResult3?.song || []; + let songs = r.searchResult3?.song || []; + // If the artist+title query returned no title match (e.g. special chars in artist like "Kløn"), + // retry with title-only so the track can still be found + if (!songs.some(s => norm(s.title) === norm(t.trackName))) { + try { + const r2 = await api('search3', `&query=${encodeURIComponent(t.trackName)}&songCount=20&albumCount=0&artistCount=0`); + songs = r2.searchResult3?.song || []; + } catch { /* keep original songs */ } + } // Strip trailing punctuation from first word so "Kløn," matches "Kløn" const firstWord = s => norm(s).split(/[\s,/•]+/)[0].replace(/\W+$/, ''); const match = songs.find(s =>