fix: rewrite _createNdPlaylist to remove problematic Unicode regex
The normD regex used actual Unicode combining chars (U+0300-U+036F) in the source which some build tools mangle, causing a SyntaxError at runtime and silently killing playlist creation. Replaced with normA that strips non-word/non-space chars via plain ASCII regex, which is safe and achieves the same loose artist-first-word comparison. Also fixed race condition in _pollAndCreatePlaylist — save lastStatus before job is auto-deleted so the done check still works. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d1c9029c5
commit
5e7e700d6a
1 file changed
+20
-18
@@ -338,12 +338,16 @@ export async function executeSpotifyDownload({ type, title, artist, artworkUrl,
|
||||
}
|
||||
|
||||
async function _pollAndCreatePlaylist(jobId, tracks, playlistName) {
|
||||
// Track last known status separately — job is deleted from S.jobs 5s after completion,
|
||||
// so S.jobs[jobId] may already be gone by the time we check after the loop breaks.
|
||||
let lastStatus = 'pending';
|
||||
while (true) {
|
||||
await new Promise(r => setTimeout(r, 2500));
|
||||
const job = S.jobs[jobId];
|
||||
if (job) lastStatus = job.status;
|
||||
if (!job || job.status === 'done' || job.status === 'error') break;
|
||||
}
|
||||
if (S.jobs[jobId]?.status !== 'done') return;
|
||||
if (lastStatus !== 'done') return;
|
||||
|
||||
// Trigger Navidrome library scan so newly downloaded files are indexed before searching.
|
||||
// Initial 3s delay lets the scan actually start before we poll.
|
||||
@@ -371,30 +375,28 @@ async function _createNdPlaylist(tracks, name) {
|
||||
toast('🎵 Erstelle Navidrome-Playlist…');
|
||||
const songIds = [];
|
||||
const missing = [];
|
||||
// Normalize: lowercase, trim, strip diacritics (ø→o, ä→a, etc.) for fuzzy matching
|
||||
const norm = s => s?.toLowerCase().trim() || '';
|
||||
const normD = s => norm(s).normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/[^\w\s]/g, '');
|
||||
// Strip non-word, non-space chars for loose artist first-word comparison
|
||||
const normA = s => norm(s).replace(/[^\w\s]/g, '');
|
||||
for (const t of tracks) {
|
||||
if (!t.trackName) continue;
|
||||
const missingLabel = t.artistName ? `${t.artistName} – ${t.trackName}` : t.trackName;
|
||||
const missingLabel = t.artistName ? t.artistName + ' – ' + t.trackName : t.trackName;
|
||||
try {
|
||||
// Use first artist only for search to avoid comma/multi-artist confusion
|
||||
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 artistQuery = firstArtist ? firstArtist + ' ' + t.trackName : t.trackName;
|
||||
const r = await api('search3', '&query=' + encodeURIComponent(artistQuery) + '&songCount=20&albumCount=0&artistCount=0');
|
||||
let songs = r.searchResult3?.song || [];
|
||||
// If artist+title query returned no title match (e.g. special chars in artist like "Kløn"),
|
||||
// If 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`);
|
||||
const r2 = await api('search3', '&query=' + encodeURIComponent(t.trackName) + '&songCount=20&albumCount=0&artistCount=0');
|
||||
songs = r2.searchResult3?.song || [];
|
||||
} catch { /* keep original songs */ }
|
||||
}
|
||||
// firstWordD: first word of artist, diacritics stripped — "Kløn" and "Klon" both → "klon"
|
||||
const firstWordD = s => normD(s).split(/\s+/)[0];
|
||||
const firstWord = s => normA(s).split(/\s+/)[0];
|
||||
const titleMatch = s => norm(s.title) === norm(t.trackName);
|
||||
const artistMatch = s => firstWordD(s.artist) === firstWordD(firstArtist || t.artistName);
|
||||
const artistMatch = s => firstWord(s.artist) === firstWord(firstArtist || t.artistName);
|
||||
const match = songs.find(s => titleMatch(s) && artistMatch(s))
|
||||
|| songs.find(s => titleMatch(s));
|
||||
if (match) songIds.push(match.id);
|
||||
@@ -403,15 +405,15 @@ async function _createNdPlaylist(tracks, name) {
|
||||
}
|
||||
if (!songIds.length) {
|
||||
toast('⚠ Keine Tracks in Navidrome gefunden — zuerst Library scannen, dann Playlist manuell anlegen');
|
||||
_addNotif({ id: 'pl_' + Date.now(), title: `Playlist: ${name}`, artist: '', status: 'error', message: 'Keine Tracks in Navidrome gefunden — Library scan nötig', missing, timestamp: Date.now(), read: false });
|
||||
_addNotif({ id: 'pl_' + Date.now(), title: 'Playlist: ' + name, artist: '', status: 'error', message: 'Keine Tracks in Navidrome gefunden — Library scan nötig', missing, timestamp: Date.now(), read: false });
|
||||
return;
|
||||
}
|
||||
const songParams = songIds.map(id => `&songId=${encodeURIComponent(id)}`).join('');
|
||||
const plRes = await api('createPlaylist', `&name=${encodeURIComponent(name)}${songParams}`);
|
||||
const songParams = songIds.map(id => '&songId=' + encodeURIComponent(id)).join('');
|
||||
const plRes = await api('createPlaylist', '&name=' + encodeURIComponent(name) + songParams);
|
||||
const plId = plRes.playlist?.id || null;
|
||||
const msg = `${songIds.length}/${tracks.length} Tracks hinzugefügt${missing.length ? `, ${missing.length} nicht gefunden` : ''}`;
|
||||
toast(`✅ Playlist "${name}" mit ${songIds.length}/${tracks.length} Tracks erstellt`);
|
||||
_addNotif({ id: 'pl_' + Date.now(), title: `Playlist: ${name}`, artist: '', status: 'done', message: msg, missing, timestamp: Date.now(), read: false, navType: 'playlist', navId: plId, navName: name });
|
||||
const msg = songIds.length + '/' + tracks.length + ' Tracks hinzugefügt' + (missing.length ? ', ' + missing.length + ' nicht gefunden' : '');
|
||||
toast('✅ Playlist "' + name + '" mit ' + songIds.length + '/' + tracks.length + ' Tracks erstellt');
|
||||
_addNotif({ id: 'pl_' + Date.now(), title: 'Playlist: ' + name, artist: '', status: 'done', message: msg, missing, timestamp: Date.now(), read: false, navType: 'playlist', navId: plId, navName: name });
|
||||
} catch(e) {
|
||||
toast('Playlist-Erstellung fehlgeschlagen: ' + e.message);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user