// Copyright (c) 2026 Gordon Bolton. MIT License. // Helper function to extract poster/cover art URL from a movie or series object function getCoverArt(item) { if (!item || !item.images) return null; const poster = item.images.find(img => img.coverType === 'poster'); if (poster) return poster.remoteUrl || poster.url || null; // Fallback to fanart if no poster const fanart = item.images.find(img => img.coverType === 'fanart'); return fanart ? (fanart.remoteUrl || fanart.url || null) : null; } // Extract import issues from a Sonarr/Radarr queue record function getImportIssues(queueRecord) { if (!queueRecord) return null; const state = queueRecord.trackedDownloadState; const status = queueRecord.trackedDownloadStatus; if (state !== 'importPending' && status !== 'warning' && status !== 'error') return null; const messages = []; if (queueRecord.statusMessages && queueRecord.statusMessages.length > 0) { for (const sm of queueRecord.statusMessages) { if (sm.messages && sm.messages.length > 0) { messages.push(...sm.messages); } else if (sm.title) { messages.push(sm.title); } } } if (queueRecord.errorMessage) { messages.push(queueRecord.errorMessage); } if (messages.length === 0) return null; return messages; } // Helper to build Sonarr web UI link for a series function getSonarrLink(series) { if (!series || !series._instanceUrl || !series.titleSlug) return null; return `${series._instanceUrl}/series/${series.titleSlug}`; } // Helper to build Radarr web UI link for a movie function getRadarrLink(movie) { if (!movie || !movie._instanceUrl || !movie.titleSlug) return null; return `${movie._instanceUrl}/movie/${movie.titleSlug}`; } // Determine if a download can be blocklisted by the current user // Admins: always true (they have arrQueueId) // Non-admins: true if importIssues OR (torrent >1h old AND availability<100%) function canBlocklist(download, isAdmin) { if (isAdmin) return true; if (download.importIssues && download.importIssues.length > 0) return true; if (download.qbittorrent && download.addedOn && download.availability) { const oneHourAgo = Date.now() - 3600000; // 1 hour in ms const addedOn = new Date(download.addedOn).getTime(); const isOldEnough = addedOn < oneHourAgo; const availability = parseFloat(download.availability); const isLowAvailability = availability < 100; return isOldEnough && isLowAvailability; } return false; } // Extract episode info from a Sonarr queue/history record. // Returns { season, episode, title } or null if data is missing. function extractEpisode(record) { if (!record) return null; const ep = record.episode || {}; const s = ep.seasonNumber != null ? ep.seasonNumber : record.seasonNumber; const e = ep.episodeNumber != null ? ep.episodeNumber : record.episodeNumber; if (s == null || e == null) return null; const title = ep.title || null; return { season: s, episode: e, title }; } // Find all episodes associated with a download by matching all queue/history records // that share the same title string. Returns sorted array of { season, episode, title }. function gatherEpisodes(titleLower, sonarrRecords) { const episodes = []; const seen = new Set(); for (const r of sonarrRecords) { const rTitle = (r.title || r.sourceTitle || '').toLowerCase(); if (rTitle && (rTitle.includes(titleLower) || titleLower.includes(rTitle))) { const ep = extractEpisode(r); if (ep) { const key = `${ep.season}x${ep.episode}`; if (!seen.has(key)) { seen.add(key); episodes.push(ep); } } } } episodes.sort((a, b) => a.season - b.season || a.episode - b.episode); return episodes; } module.exports = { getCoverArt, getImportIssues, getSonarrLink, getRadarrLink, canBlocklist, extractEpisode, gatherEpisodes };