diff --git a/fileflows/scripts/Sonarr_-_Trigger_Manual_Import.js b/fileflows/scripts/Sonarr_-_Trigger_Manual_Import.js index 1adfe02..7dd870b 100644 --- a/fileflows/scripts/Sonarr_-_Trigger_Manual_Import.js +++ b/fileflows/scripts/Sonarr_-_Trigger_Manual_Import.js @@ -2,7 +2,7 @@ * @name Sonarr - Trigger Manual Import * @description Safely trigger Sonarr ManualImport for exactly one processed episode file. * @author FileFlows + Sascha - * @revision 11 + * @revision 12 * @output Import erfolgreich * @output Fehler * @param {string} URL Sonarr root URL and port @@ -57,8 +57,8 @@ function Script(URL, ApiKey, ImportPath, UseUnmappedPath, MoveMode, TimeOut) { if (!(seriesId && episodeIds)) { let trackedEpisode = null; for (const pattern of searchPatterns) { - trackedEpisode = findEpisodeFromQueue(pattern, sonarr) - ?? findEpisodeFromGrabHistory(pattern, sonarr); + trackedEpisode = findEpisodeFromQueue(pattern, sonarr, filePattern) + ?? findEpisodeFromGrabHistory(pattern, sonarr, filePattern); if (trackedEpisode) break; } if (!trackedEpisode) { @@ -226,7 +226,7 @@ function sendManualImportCommand(sonarr, cmdBody) { /*────────────────────────────── helpers ──────────────────────────────────*/ -function findEpisodeFromQueue(searchPattern, sonarr) { +function findEpisodeFromQueue(searchPattern, sonarr, episodePattern) { if (!searchPattern) return null; try { const query = buildQueryParams({ page: 1, pageSize: 200, includeUnknownSeriesItems: true }); @@ -240,7 +240,11 @@ function findEpisodeFromQueue(searchPattern, sonarr) { Logger.ILog(`Matched Sonarr queue seriesId ${matches[0].seriesId}, episodeId ${matches[0].episodeId}.`); return { id: matches[0].seriesId, episodeIds: [matches[0].episodeId] }; } - if (matches.length > 1) Logger.WLog(`Ambiguous Sonarr queue match (${matches.length} records).`); + if (matches.length > 1) { + const resolved = resolvePackEpisode(matches, episodePattern, sonarr); + if (resolved) return resolved; + Logger.WLog(`Ambiguous Sonarr queue match (${matches.length} records).`); + } return null; } catch (e) { Logger.ELog(`Error fetching Sonarr queue: ${e.message}`); @@ -248,7 +252,7 @@ function findEpisodeFromQueue(searchPattern, sonarr) { } } -function findEpisodeFromGrabHistory(searchPattern, sonarr) { +function findEpisodeFromGrabHistory(searchPattern, sonarr, episodePattern) { if (!searchPattern) return null; try { const query = buildQueryParams({ @@ -269,7 +273,11 @@ function findEpisodeFromGrabHistory(searchPattern, sonarr) { Logger.ILog(`Matched Sonarr grab history seriesId ${seriesId}, episodeId ${episodeId}.`); return { id: seriesId, episodeIds: [episodeId] }; } - if (keys.length > 1) Logger.WLog(`Ambiguous Sonarr grab history match (${keys.length} episodes).`); + if (keys.length > 1) { + const resolved = resolvePackEpisode(matches, episodePattern, sonarr); + if (resolved) return resolved; + Logger.WLog(`Ambiguous Sonarr grab history match (${keys.length} episodes).`); + } return null; } catch (e) { Logger.ELog(`Error fetching Sonarr history: ${e.message}`); @@ -277,6 +285,47 @@ function findEpisodeFromGrabHistory(searchPattern, sonarr) { } } +function resolvePackEpisode(matches, episodePattern, sonarr) { + const coordinates = extractEpisodeCoordinates(episodePattern); + if (!coordinates) return null; + + const seriesIds = matches + .map(item => Number(item?.seriesId)) + .filter(Boolean) + .filter((value, index, values) => values.indexOf(value) === index); + if (seriesIds.length !== 1) return null; + + try { + const seriesId = seriesIds[0]; + const query = buildQueryParams({ seriesId }); + const episodes = sonarr.fetchJson('episode', query) || []; + const candidates = episodes.filter(item => + Number(item?.seasonNumber) === coordinates.seasonNumber + && Number(item?.episodeNumber) === coordinates.episodeNumber + ); + if (candidates.length !== 1) return null; + + const episodeId = Number(candidates[0]?.id); + const trackedIds = matches.map(item => Number(item?.episodeId)).filter(Boolean); + if (!episodeId || !trackedIds.includes(episodeId)) return null; + + Logger.ILog( + `Resolved season-pack match via exact filename S${coordinates.seasonNumber}` + + `E${coordinates.episodeNumber}: seriesId ${seriesId}, episodeId ${episodeId}.` + ); + return { id: seriesId, episodeIds: [episodeId] }; + } catch (e) { + Logger.WLog(`Unable to resolve season-pack episode: ${e.message}`); + return null; + } +} + +function extractEpisodeCoordinates(value) { + const match = String(value || '').match(/(?:^|[^a-z0-9])s(\d{1,2})e(\d{1,3})(?:[^a-z0-9]|$)/i); + if (!match) return null; + return { seasonNumber: Number(match[1]), episodeNumber: Number(match[2]) }; +} + function normalizeReleaseTitle(value) { return (value || '').toLowerCase().replace(/[^a-z0-9]+/g, ''); }