From 5dfe0b1216c7d747b0193c2b4ce26b2c0163182c Mon Sep 17 00:00:00 2001 From: Gronod Date: Tue, 19 May 2026 22:13:43 +0100 Subject: [PATCH] fix(matching): Match SAB to Sonarr by downloadId first Sonarr tracks the exact SAB download ID (nzo_id). Now tries to match by downloadId first, then falls back to title matching. Also adds debug to show if matches are via downloadId vs title, and logs downloadIds in history to verify the link exists. --- server/routes/dashboard.js | 63 +++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/server/routes/dashboard.js b/server/routes/dashboard.js index e999e83..8e0122f 100644 --- a/server/routes/dashboard.js +++ b/server/routes/dashboard.js @@ -1016,21 +1016,38 @@ router.get('/stream', requireAuth, async (req, res) => { // Normalize SAB name (dots to spaces) for better matching const nzbNameNormalized = nzbNameLower.replace(/\./g, ' '); - // Check Sonarr/Radarr QUEUE (active downloads) - let sonarrMatch = sonarrQueue.data.records.find(r => { - const rTitle = (r.title || r.sourceTitle || '').toLowerCase(); - return rTitle && ( - rTitle.includes(nzbNameLower) || nzbNameLower.includes(rTitle) || - rTitle.includes(nzbNameNormalized) || nzbNameNormalized.includes(rTitle) - ); - }); - let radarrMatch = radarrQueue.data.records.find(r => { - const rTitle = (r.title || r.sourceTitle || '').toLowerCase(); - return rTitle && ( - rTitle.includes(nzbNameLower) || nzbNameLower.includes(rTitle) || - rTitle.includes(nzbNameNormalized) || nzbNameNormalized.includes(rTitle) - ); - }); + // Try to match by downloadId first (most reliable) + const sabDownloadId = slot.nzo_id || slot.id; + let sonarrMatch = sabDownloadId ? sonarrQueue.data.records.find(r => r.downloadId === sabDownloadId) : null; + let radarrMatch = sabDownloadId ? radarrQueue.data.records.find(r => r.downloadId === sabDownloadId) : null; + + // Also check HISTORY by downloadId + if (!sonarrMatch && sabDownloadId) { + sonarrMatch = sonarrHistory.data.records.find(r => r.downloadId === sabDownloadId); + } + if (!radarrMatch && sabDownloadId) { + radarrMatch = radarrHistory.data.records.find(r => r.downloadId === sabDownloadId); + } + + // Fallback: Check by title matching + if (!sonarrMatch) { + sonarrMatch = sonarrQueue.data.records.find(r => { + const rTitle = (r.title || r.sourceTitle || '').toLowerCase(); + return rTitle && ( + rTitle.includes(nzbNameLower) || nzbNameLower.includes(rTitle) || + rTitle.includes(nzbNameNormalized) || nzbNameNormalized.includes(rTitle) + ); + }); + } + if (!radarrMatch) { + radarrMatch = radarrQueue.data.records.find(r => { + const rTitle = (r.title || r.sourceTitle || '').toLowerCase(); + return rTitle && ( + rTitle.includes(nzbNameLower) || nzbNameLower.includes(rTitle) || + rTitle.includes(nzbNameNormalized) || nzbNameNormalized.includes(rTitle) + ); + }); + } // Also check HISTORY (completed downloads) if no queue match if (!sonarrMatch) { @@ -1055,19 +1072,29 @@ router.get('/stream', requireAuth, async (req, res) => { if (sabSlotsChecked <= 5) { if (sonarrMatch) { const source = sonarrQueue.data.records.includes(sonarrMatch) ? 'queue' : 'history'; - console.log(`[SSE] ✓ Sonarr ${source} match: SAB:"${nzbNameLower.substring(0, 50)}" → Sonarr:"${(sonarrMatch.title || sonarrMatch.sourceTitle || '').substring(0, 50)}"`); + const matchType = (sonarrMatch.downloadId === sabDownloadId) ? 'downloadId' : 'title'; + console.log(`[SSE] ✓ Sonarr ${source} ${matchType} match: SAB:"${nzbNameLower.substring(0, 40)}" → Sonarr:"${(sonarrMatch.title || sonarrMatch.sourceTitle || '').substring(0, 40)}"`); } else if (radarrMatch) { const source = radarrQueue.data.records.includes(radarrMatch) ? 'queue' : 'history'; - console.log(`[SSE] ✓ Radarr ${source} match: SAB:"${nzbNameLower.substring(0, 50)}" → Radarr:"${(radarrMatch.title || radarrMatch.sourceTitle || '').substring(0, 50)}"`); + const matchType = (radarrMatch.downloadId === sabDownloadId) ? 'downloadId' : 'title'; + console.log(`[SSE] ✓ Radarr ${source} ${matchType} match: SAB:"${nzbNameLower.substring(0, 40)}" → Radarr:"${(radarrMatch.title || radarrMatch.sourceTitle || '').substring(0, 40)}"`); } else { console.log(`[SSE] ✗ No match for SAB: "${nzbNameLower.substring(0, 60)}"`); // Show counts console.log(`[SSE] Queue: ${sonarrQueue.data.records.length}, History: ${sonarrHistory.data.records.length}`); // Show history titles if there are any if (sonarrHistory.data.records.length > 0) { - const histTitles = sonarrHistory.data.records.slice(0, 3).map(r => (r.title || r.sourceTitle || 'NO_TITLE').substring(0, 40)); + const histTitles = sonarrHistory.data.records.slice(0, 3).map(r => { + const title = (r.title || r.sourceTitle || 'NO_TITLE').substring(0, 35); + const dlId = r.downloadId ? r.downloadId.substring(0, 15) : 'no-dl-id'; + return `${title}[${dlId}]`; + }); console.log(`[SSE] History titles: ${histTitles.join(' | ')}`); } + // Also check if SAB slots have nzo_id we could use + if (slot.nzo_id) { + console.log(`[SSE] SAB nzo_id: ${slot.nzo_id.substring(0, 20)}...`); + } } } if (sonarrMatch && sonarrMatch.seriesId) {