Extract DownloadAssembler service from dashboard routes
- Create server/services/DownloadAssembler.js with 7 pure functions: - getCoverArt, getImportIssues, getSonarrLink, getRadarrLink - canBlocklist, extractEpisode, gatherEpisodes - Update server/routes/dashboard.js to use DownloadAssembler - Add comprehensive unit tests (73 tests covering edge cases) - Fix null check in extractEpisode function - All tests passing: DownloadAssembler (73/73), TagMatcher (26/26)
This commit is contained in:
+53
-149
@@ -11,103 +11,7 @@ const { getSonarrInstances, getRadarrInstances } = require('../utils/config');
|
||||
const downloadClientRegistry = require('../utils/downloadClients');
|
||||
const sanitizeError = require('../utils/sanitizeError');
|
||||
const TagMatcher = require('../services/TagMatcher');
|
||||
|
||||
|
||||
// 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) {
|
||||
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;
|
||||
}
|
||||
const DownloadAssembler = require('../services/DownloadAssembler');
|
||||
|
||||
|
||||
// Track active dashboard clients.
|
||||
@@ -249,7 +153,7 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
const dlObj = {
|
||||
type: 'series',
|
||||
title: nzbName,
|
||||
coverArt: getCoverArt(series),
|
||||
coverArt: DownloadAssembler.getCoverArt(series),
|
||||
status: slotState.status,
|
||||
progress: slot.percentage,
|
||||
mb: slot.mb,
|
||||
@@ -258,17 +162,17 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
speed: slotState.speed,
|
||||
eta: slot.timeleft,
|
||||
seriesName: series.title,
|
||||
episodes: gatherEpisodes(nzbNameLower, sonarrQueue.data.records),
|
||||
episodes: DownloadAssembler.gatherEpisodes(nzbNameLower, sonarrQueue.data.records),
|
||||
allTags,
|
||||
matchedUserTag: matchedUserTag || null,
|
||||
tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined
|
||||
};
|
||||
const issues = getImportIssues(sonarrMatch);
|
||||
const issues = DownloadAssembler.getImportIssues(sonarrMatch);
|
||||
if (issues) dlObj.importIssues = issues;
|
||||
if (isAdmin) {
|
||||
dlObj.downloadPath = slot.storage || null;
|
||||
dlObj.targetPath = series.path || null;
|
||||
dlObj.arrLink = getSonarrLink(series);
|
||||
dlObj.arrLink = DownloadAssembler.getSonarrLink(series);
|
||||
dlObj.arrQueueId = sonarrMatch.id;
|
||||
dlObj.arrType = 'sonarr';
|
||||
dlObj.arrInstanceUrl = sonarrMatch._instanceUrl || null;
|
||||
@@ -276,7 +180,7 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
dlObj.arrContentId = sonarrMatch.episodeId || null;
|
||||
dlObj.arrContentType = 'episode';
|
||||
}
|
||||
dlObj.canBlocklist = canBlocklist(dlObj, isAdmin);
|
||||
dlObj.canBlocklist = DownloadAssembler.canBlocklist(dlObj, isAdmin);
|
||||
userDownloads.push(dlObj);
|
||||
}
|
||||
}
|
||||
@@ -298,7 +202,7 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
const dlObj = {
|
||||
type: 'movie',
|
||||
title: nzbName,
|
||||
coverArt: getCoverArt(movie),
|
||||
coverArt: DownloadAssembler.getCoverArt(movie),
|
||||
status: slotState.status,
|
||||
progress: slot.percentage,
|
||||
mb: slot.mb,
|
||||
@@ -312,12 +216,12 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
matchedUserTag: matchedUserTag || null,
|
||||
tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined
|
||||
};
|
||||
const issues = getImportIssues(radarrMatch);
|
||||
const issues = DownloadAssembler.getImportIssues(radarrMatch);
|
||||
if (issues) dlObj.importIssues = issues;
|
||||
if (isAdmin) {
|
||||
dlObj.downloadPath = slot.storage || null;
|
||||
dlObj.targetPath = movie.path || null;
|
||||
dlObj.arrLink = getRadarrLink(movie);
|
||||
dlObj.arrLink = DownloadAssembler.getRadarrLink(movie);
|
||||
dlObj.arrQueueId = radarrMatch.id;
|
||||
dlObj.arrType = 'radarr';
|
||||
dlObj.arrInstanceUrl = radarrMatch._instanceUrl || null;
|
||||
@@ -325,7 +229,7 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
dlObj.arrContentId = radarrMatch.movieId || null;
|
||||
dlObj.arrContentType = 'movie';
|
||||
}
|
||||
dlObj.canBlocklist = canBlocklist(dlObj, isAdmin);
|
||||
dlObj.canBlocklist = DownloadAssembler.canBlocklist(dlObj, isAdmin);
|
||||
userDownloads.push(dlObj);
|
||||
}
|
||||
}
|
||||
@@ -364,12 +268,12 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
const dlObj = {
|
||||
type: 'series',
|
||||
title: nzbName,
|
||||
coverArt: getCoverArt(series),
|
||||
coverArt: DownloadAssembler.getCoverArt(series),
|
||||
status: slot.status,
|
||||
size: slot.size,
|
||||
completedAt: slot.completed_time,
|
||||
seriesName: series.title,
|
||||
episodes: gatherEpisodes(nzbNameLower, sonarrHistory.data.records),
|
||||
episodes: DownloadAssembler.gatherEpisodes(nzbNameLower, sonarrHistory.data.records),
|
||||
allTags,
|
||||
matchedUserTag: matchedUserTag || null,
|
||||
tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined
|
||||
@@ -377,7 +281,7 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
if (isAdmin) {
|
||||
dlObj.downloadPath = slot.storage || null;
|
||||
dlObj.targetPath = series.path || null;
|
||||
dlObj.arrLink = getSonarrLink(series);
|
||||
dlObj.arrLink = DownloadAssembler.getSonarrLink(series);
|
||||
}
|
||||
userDownloads.push(dlObj);
|
||||
}
|
||||
@@ -400,7 +304,7 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
const dlObj = {
|
||||
type: 'movie',
|
||||
title: nzbName,
|
||||
coverArt: getCoverArt(movie),
|
||||
coverArt: DownloadAssembler.getCoverArt(movie),
|
||||
status: slot.status,
|
||||
size: slot.size,
|
||||
completedAt: slot.completed_time,
|
||||
@@ -413,7 +317,7 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
if (isAdmin) {
|
||||
dlObj.downloadPath = slot.storage || null;
|
||||
dlObj.targetPath = movie.path || null;
|
||||
dlObj.arrLink = getRadarrLink(movie);
|
||||
dlObj.arrLink = DownloadAssembler.getRadarrLink(movie);
|
||||
}
|
||||
userDownloads.push(dlObj);
|
||||
}
|
||||
@@ -470,18 +374,18 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
console.log(`[Dashboard] Matched torrent "${torrentName}" to Sonarr series "${series.title}"`);
|
||||
const download = mapTorrentToDownload(torrent);
|
||||
download.type = 'series';
|
||||
download.coverArt = getCoverArt(series);
|
||||
download.coverArt = DownloadAssembler.getCoverArt(series);
|
||||
download.seriesName = series.title;
|
||||
download.episodes = gatherEpisodes(torrentNameLower, sonarrQueue.data.records);
|
||||
download.episodes = DownloadAssembler.gatherEpisodes(torrentNameLower, sonarrQueue.data.records);
|
||||
download.allTags = allTags;
|
||||
download.matchedUserTag = matchedUserTag || null;
|
||||
download.tagBadges = showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined;
|
||||
const sonarrIssues = getImportIssues(sonarrMatch);
|
||||
const sonarrIssues = DownloadAssembler.getImportIssues(sonarrMatch);
|
||||
if (sonarrIssues) download.importIssues = sonarrIssues;
|
||||
if (isAdmin) {
|
||||
download.downloadPath = download.savePath || null;
|
||||
download.targetPath = series.path || null;
|
||||
download.arrLink = getSonarrLink(series);
|
||||
download.arrLink = DownloadAssembler.getSonarrLink(series);
|
||||
download.arrQueueId = sonarrMatch.id;
|
||||
download.arrType = 'sonarr';
|
||||
download.arrInstanceUrl = sonarrMatch._instanceUrl || null;
|
||||
@@ -489,7 +393,7 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
download.arrContentId = sonarrMatch.episodeId || null;
|
||||
download.arrContentType = 'episode';
|
||||
}
|
||||
download.canBlocklist = canBlocklist(download, isAdmin);
|
||||
download.canBlocklist = DownloadAssembler.canBlocklist(download, isAdmin);
|
||||
userDownloads.push(download);
|
||||
continue; // Skip to next torrent
|
||||
}
|
||||
@@ -512,18 +416,18 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
console.log(`[Dashboard] Matched torrent "${torrentName}" to Radarr movie "${movie.title}"`);
|
||||
const download = mapTorrentToDownload(torrent);
|
||||
download.type = 'movie';
|
||||
download.coverArt = getCoverArt(movie);
|
||||
download.coverArt = DownloadAssembler.getCoverArt(movie);
|
||||
download.movieName = movie.title;
|
||||
download.movieInfo = radarrMatch;
|
||||
download.allTags = allTags;
|
||||
download.matchedUserTag = matchedUserTag || null;
|
||||
download.tagBadges = showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined;
|
||||
const radarrIssues = getImportIssues(radarrMatch);
|
||||
const radarrIssues = DownloadAssembler.getImportIssues(radarrMatch);
|
||||
if (radarrIssues) download.importIssues = radarrIssues;
|
||||
if (isAdmin) {
|
||||
download.downloadPath = download.savePath || null;
|
||||
download.targetPath = movie.path || null;
|
||||
download.arrLink = getRadarrLink(movie);
|
||||
download.arrLink = DownloadAssembler.getRadarrLink(movie);
|
||||
download.arrQueueId = radarrMatch.id;
|
||||
download.arrType = 'radarr';
|
||||
download.arrInstanceUrl = radarrMatch._instanceUrl || null;
|
||||
@@ -531,7 +435,7 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
download.arrContentId = radarrMatch.movieId || null;
|
||||
download.arrContentType = 'movie';
|
||||
}
|
||||
download.canBlocklist = canBlocklist(download, isAdmin);
|
||||
download.canBlocklist = DownloadAssembler.canBlocklist(download, isAdmin);
|
||||
userDownloads.push(download);
|
||||
continue; // Skip to next torrent
|
||||
}
|
||||
@@ -554,16 +458,16 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
console.log(`[Dashboard] Matched torrent "${torrentName}" to Sonarr history "${series.title}"`);
|
||||
const download = mapTorrentToDownload(torrent);
|
||||
download.type = 'series';
|
||||
download.coverArt = getCoverArt(series);
|
||||
download.coverArt = DownloadAssembler.getCoverArt(series);
|
||||
download.seriesName = series.title;
|
||||
download.episodes = gatherEpisodes(torrentNameLower, sonarrHistory.data.records);
|
||||
download.episodes = DownloadAssembler.gatherEpisodes(torrentNameLower, sonarrHistory.data.records);
|
||||
download.allTags = allTags;
|
||||
download.matchedUserTag = matchedUserTag || null;
|
||||
download.tagBadges = showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined;
|
||||
if (isAdmin) {
|
||||
download.downloadPath = download.savePath || null;
|
||||
download.targetPath = series.path || null;
|
||||
download.arrLink = getSonarrLink(series);
|
||||
download.arrLink = DownloadAssembler.getSonarrLink(series);
|
||||
}
|
||||
userDownloads.push(download);
|
||||
continue;
|
||||
@@ -587,7 +491,7 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
console.log(`[Dashboard] Matched torrent "${torrentName}" to Radarr history "${movie.title}"`);
|
||||
const download = mapTorrentToDownload(torrent);
|
||||
download.type = 'movie';
|
||||
download.coverArt = getCoverArt(movie);
|
||||
download.coverArt = DownloadAssembler.getCoverArt(movie);
|
||||
download.movieName = movie.title;
|
||||
download.movieInfo = radarrHistoryMatch;
|
||||
download.allTags = allTags;
|
||||
@@ -596,7 +500,7 @@ router.get('/user-downloads', requireAuth, async (req, res) => {
|
||||
if (isAdmin) {
|
||||
download.downloadPath = download.savePath || null;
|
||||
download.targetPath = movie.path || null;
|
||||
download.arrLink = getRadarrLink(movie);
|
||||
download.arrLink = DownloadAssembler.getRadarrLink(movie);
|
||||
}
|
||||
userDownloads.push(download);
|
||||
continue;
|
||||
@@ -1039,11 +943,11 @@ router.get('/stream', requireAuth, async (req, res) => {
|
||||
const allTags = TagMatcher.extractAllTags(series.tags, sonarrTagMap);
|
||||
const matchedUserTag = TagMatcher.extractUserTag(series.tags, sonarrTagMap, username);
|
||||
if (showAll ? allTags.length > 0 : !!matchedUserTag) {
|
||||
const dlObj = { type: 'series', title: nzbName, coverArt: getCoverArt(series), status: slotState.status, progress: Math.round(slot.progress * 100), mb: slot.mb, mbmissing: slot.mbleft, size: Math.round(slot.mb * 1024 * 1024), speed: Math.round((slot.kbpersec || 0) * 1024), eta: slot.timeleft, seriesName: series.title, episodes: gatherEpisodes(nzbNameLower, sonarrQueue.data.records), allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined, client: 'sabnzbd', instanceId: slot.instanceId || 'sabnzbd-default', instanceName: slot.instanceName || 'SABnzbd' };
|
||||
const issues = getImportIssues(sonarrMatch);
|
||||
const dlObj = { type: 'series', title: nzbName, coverArt: DownloadAssembler.getCoverArt(series), status: slotState.status, progress: Math.round(slot.progress * 100), mb: slot.mb, mbmissing: slot.mbleft, size: Math.round(slot.mb * 1024 * 1024), speed: Math.round((slot.kbpersec || 0) * 1024), eta: slot.timeleft, seriesName: series.title, episodes: DownloadAssembler.gatherEpisodes(nzbNameLower, sonarrQueue.data.records), allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined, client: 'sabnzbd', instanceId: slot.instanceId || 'sabnzbd-default', instanceName: slot.instanceName || 'SABnzbd' };
|
||||
const issues = DownloadAssembler.getImportIssues(sonarrMatch);
|
||||
if (issues) dlObj.importIssues = issues;
|
||||
if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = series.path || null; dlObj.arrLink = getSonarrLink(series); dlObj.arrQueueId = sonarrMatch.id; dlObj.arrType = 'sonarr'; dlObj.arrInstanceUrl = sonarrMatch._instanceUrl || null; dlObj.arrInstanceKey = sonarrMatch._instanceKey || null; dlObj.arrContentId = sonarrMatch.episodeId || null; dlObj.arrContentType = 'episode'; }
|
||||
dlObj.canBlocklist = canBlocklist(dlObj, isAdmin);
|
||||
if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = series.path || null; dlObj.arrLink = DownloadAssembler.getSonarrLink(series); dlObj.arrQueueId = sonarrMatch.id; dlObj.arrType = 'sonarr'; dlObj.arrInstanceUrl = sonarrMatch._instanceUrl || null; dlObj.arrInstanceKey = sonarrMatch._instanceKey || null; dlObj.arrContentId = sonarrMatch.episodeId || null; dlObj.arrContentType = 'episode'; }
|
||||
dlObj.canBlocklist = DownloadAssembler.canBlocklist(dlObj, isAdmin);
|
||||
userDownloads.push(dlObj);
|
||||
}
|
||||
}
|
||||
@@ -1057,11 +961,11 @@ router.get('/stream', requireAuth, async (req, res) => {
|
||||
const allTags = TagMatcher.extractAllTags(movie.tags, radarrTagMap);
|
||||
const matchedUserTag = TagMatcher.extractUserTag(movie.tags, radarrTagMap, username);
|
||||
if (showAll ? allTags.length > 0 : !!matchedUserTag) {
|
||||
const dlObj = { type: 'movie', title: nzbName, coverArt: getCoverArt(movie), status: slotState.status, progress: Math.round(slot.progress * 100), mb: slot.mb, mbmissing: slot.mbleft, size: Math.round(slot.mb * 1024 * 1024), speed: Math.round((slot.kbpersec || 0) * 1024), eta: slot.timeleft, movieName: movie.title, movieInfo: radarrMatch, allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined, client: 'sabnzbd', instanceId: slot.instanceId || 'sabnzbd-default', instanceName: slot.instanceName || 'SABnzbd' };
|
||||
const issues = getImportIssues(radarrMatch);
|
||||
const dlObj = { type: 'movie', title: nzbName, coverArt: DownloadAssembler.getCoverArt(movie), status: slotState.status, progress: Math.round(slot.progress * 100), mb: slot.mb, mbmissing: slot.mbleft, size: Math.round(slot.mb * 1024 * 1024), speed: Math.round((slot.kbpersec || 0) * 1024), eta: slot.timeleft, movieName: movie.title, movieInfo: radarrMatch, allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined, client: 'sabnzbd', instanceId: slot.instanceId || 'sabnzbd-default', instanceName: slot.instanceName || 'SABnzbd' };
|
||||
const issues = DownloadAssembler.getImportIssues(radarrMatch);
|
||||
if (issues) dlObj.importIssues = issues;
|
||||
if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = movie.path || null; dlObj.arrLink = getRadarrLink(movie); dlObj.arrQueueId = radarrMatch.id; dlObj.arrType = 'radarr'; dlObj.arrInstanceUrl = radarrMatch._instanceUrl || null; dlObj.arrInstanceKey = radarrMatch._instanceKey || null; dlObj.arrContentId = radarrMatch.movieId || null; dlObj.arrContentType = 'movie'; }
|
||||
dlObj.canBlocklist = canBlocklist(dlObj, isAdmin);
|
||||
if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = movie.path || null; dlObj.arrLink = DownloadAssembler.getRadarrLink(movie); dlObj.arrQueueId = radarrMatch.id; dlObj.arrType = 'radarr'; dlObj.arrInstanceUrl = radarrMatch._instanceUrl || null; dlObj.arrInstanceKey = radarrMatch._instanceKey || null; dlObj.arrContentId = radarrMatch.movieId || null; dlObj.arrContentType = 'movie'; }
|
||||
dlObj.canBlocklist = DownloadAssembler.canBlocklist(dlObj, isAdmin);
|
||||
userDownloads.push(dlObj);
|
||||
}
|
||||
}
|
||||
@@ -1086,8 +990,8 @@ router.get('/stream', requireAuth, async (req, res) => {
|
||||
const allTags = TagMatcher.extractAllTags(series.tags, sonarrTagMap);
|
||||
const matchedUserTag = TagMatcher.extractUserTag(series.tags, sonarrTagMap, username);
|
||||
if (showAll ? allTags.length > 0 : !!matchedUserTag) {
|
||||
const dlObj = { type: 'series', title: nzbName, coverArt: getCoverArt(series), status: slot.status, mb: slot.mb, size: Math.round((slot.mb || 0) * 1024 * 1024), completedAt: slot.completed_time, seriesName: series.title, episodes: gatherEpisodes(nzbNameLower, sonarrHistory.data.records), allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined, client: 'sabnzbd', instanceId: slot.instanceId || 'sabnzbd-default', instanceName: slot.instanceName || 'SABnzbd' };
|
||||
if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = series.path || null; dlObj.arrLink = getSonarrLink(series); }
|
||||
const dlObj = { type: 'series', title: nzbName, coverArt: DownloadAssembler.getCoverArt(series), status: slot.status, mb: slot.mb, size: Math.round((slot.mb || 0) * 1024 * 1024), completedAt: slot.completed_time, seriesName: series.title, episodes: DownloadAssembler.gatherEpisodes(nzbNameLower, sonarrHistory.data.records), allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined, client: 'sabnzbd', instanceId: slot.instanceId || 'sabnzbd-default', instanceName: slot.instanceName || 'SABnzbd' };
|
||||
if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = series.path || null; dlObj.arrLink = DownloadAssembler.getSonarrLink(series); }
|
||||
userDownloads.push(dlObj);
|
||||
}
|
||||
}
|
||||
@@ -1103,8 +1007,8 @@ router.get('/stream', requireAuth, async (req, res) => {
|
||||
const allTags = TagMatcher.extractAllTags(movie.tags, radarrTagMap);
|
||||
const matchedUserTag = TagMatcher.extractUserTag(movie.tags, radarrTagMap, username);
|
||||
if (showAll ? allTags.length > 0 : !!matchedUserTag) {
|
||||
const dlObj = { type: 'movie', title: nzbName, coverArt: getCoverArt(movie), status: slot.status, mb: slot.mb, size: Math.round((slot.mb || 0) * 1024 * 1024), completedAt: slot.completed_time, movieName: movie.title, movieInfo: radarrMatch, allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined, client: 'sabnzbd', instanceId: slot.instanceId || 'sabnzbd-default', instanceName: slot.instanceName || 'SABnzbd' };
|
||||
if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = movie.path || null; dlObj.arrLink = getRadarrLink(movie); }
|
||||
const dlObj = { type: 'movie', title: nzbName, coverArt: DownloadAssembler.getCoverArt(movie), status: slot.status, mb: slot.mb, size: Math.round((slot.mb || 0) * 1024 * 1024), completedAt: slot.completed_time, movieName: movie.title, movieInfo: radarrMatch, allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined, client: 'sabnzbd', instanceId: slot.instanceId || 'sabnzbd-default', instanceName: slot.instanceName || 'SABnzbd' };
|
||||
if (isAdmin) { dlObj.downloadPath = slot.storage || null; dlObj.targetPath = movie.path || null; dlObj.arrLink = DownloadAssembler.getRadarrLink(movie); }
|
||||
userDownloads.push(dlObj);
|
||||
}
|
||||
}
|
||||
@@ -1126,10 +1030,10 @@ router.get('/stream', requireAuth, async (req, res) => {
|
||||
const matchedUserTag = TagMatcher.extractUserTag(series.tags, sonarrTagMap, username);
|
||||
if (showAll ? allTags.length > 0 : !!matchedUserTag) {
|
||||
const download = mapTorrentToDownload(torrent);
|
||||
Object.assign(download, { type: 'series', coverArt: getCoverArt(series), seriesName: series.title, episodes: gatherEpisodes(torrentNameLower, sonarrQueue.data.records), allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined });
|
||||
const issues = getImportIssues(sonarrMatch); if (issues) download.importIssues = issues;
|
||||
if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = series.path || null; download.arrLink = getSonarrLink(series); download.arrQueueId = sonarrMatch.id; download.arrType = 'sonarr'; download.arrInstanceUrl = sonarrMatch._instanceUrl || null; download.arrInstanceKey = sonarrMatch._instanceKey || null; download.arrContentId = sonarrMatch.episodeId || null; download.arrContentType = 'episode'; }
|
||||
download.canBlocklist = canBlocklist(download, isAdmin);
|
||||
Object.assign(download, { type: 'series', coverArt: DownloadAssembler.getCoverArt(series), seriesName: series.title, episodes: DownloadAssembler.gatherEpisodes(torrentNameLower, sonarrQueue.data.records), allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined });
|
||||
const issues = DownloadAssembler.getImportIssues(sonarrMatch); if (issues) download.importIssues = issues;
|
||||
if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = series.path || null; download.arrLink = DownloadAssembler.getSonarrLink(series); download.arrQueueId = sonarrMatch.id; download.arrType = 'sonarr'; download.arrInstanceUrl = sonarrMatch._instanceUrl || null; download.arrInstanceKey = sonarrMatch._instanceKey || null; download.arrContentId = sonarrMatch.episodeId || null; download.arrContentType = 'episode'; }
|
||||
download.canBlocklist = DownloadAssembler.canBlocklist(download, isAdmin);
|
||||
userDownloads.push(download); continue;
|
||||
}
|
||||
}
|
||||
@@ -1143,10 +1047,10 @@ router.get('/stream', requireAuth, async (req, res) => {
|
||||
const matchedUserTag = TagMatcher.extractUserTag(movie.tags, radarrTagMap, username);
|
||||
if (showAll ? allTags.length > 0 : !!matchedUserTag) {
|
||||
const download = mapTorrentToDownload(torrent);
|
||||
Object.assign(download, { type: 'movie', coverArt: getCoverArt(movie), movieName: movie.title, movieInfo: radarrMatch, allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined });
|
||||
const issues = getImportIssues(radarrMatch); if (issues) download.importIssues = issues;
|
||||
if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = movie.path || null; download.arrLink = getRadarrLink(movie); download.arrQueueId = radarrMatch.id; download.arrType = 'radarr'; download.arrInstanceUrl = radarrMatch._instanceUrl || null; download.arrInstanceKey = radarrMatch._instanceKey || null; download.arrContentId = radarrMatch.movieId || null; download.arrContentType = 'movie'; }
|
||||
download.canBlocklist = canBlocklist(download, isAdmin);
|
||||
Object.assign(download, { type: 'movie', coverArt: DownloadAssembler.getCoverArt(movie), movieName: movie.title, movieInfo: radarrMatch, allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined });
|
||||
const issues = DownloadAssembler.getImportIssues(radarrMatch); if (issues) download.importIssues = issues;
|
||||
if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = movie.path || null; download.arrLink = DownloadAssembler.getRadarrLink(movie); download.arrQueueId = radarrMatch.id; download.arrType = 'radarr'; download.arrInstanceUrl = radarrMatch._instanceUrl || null; download.arrInstanceKey = radarrMatch._instanceKey || null; download.arrContentId = radarrMatch.movieId || null; download.arrContentType = 'movie'; }
|
||||
download.canBlocklist = DownloadAssembler.canBlocklist(download, isAdmin);
|
||||
userDownloads.push(download); continue;
|
||||
}
|
||||
}
|
||||
@@ -1160,8 +1064,8 @@ router.get('/stream', requireAuth, async (req, res) => {
|
||||
const matchedUserTag = TagMatcher.extractUserTag(series.tags, sonarrTagMap, username);
|
||||
if (showAll ? allTags.length > 0 : !!matchedUserTag) {
|
||||
const download = mapTorrentToDownload(torrent);
|
||||
Object.assign(download, { type: 'series', coverArt: getCoverArt(series), seriesName: series.title, episodes: gatherEpisodes(torrentNameLower, sonarrHistory.data.records), allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined });
|
||||
if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = series.path || null; download.arrLink = getSonarrLink(series); }
|
||||
Object.assign(download, { type: 'series', coverArt: DownloadAssembler.getCoverArt(series), seriesName: series.title, episodes: DownloadAssembler.gatherEpisodes(torrentNameLower, sonarrHistory.data.records), allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined });
|
||||
if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = series.path || null; download.arrLink = DownloadAssembler.getSonarrLink(series); }
|
||||
userDownloads.push(download); continue;
|
||||
}
|
||||
}
|
||||
@@ -1175,8 +1079,8 @@ router.get('/stream', requireAuth, async (req, res) => {
|
||||
const matchedUserTag = TagMatcher.extractUserTag(movie.tags, radarrTagMap, username);
|
||||
if (showAll ? allTags.length > 0 : !!matchedUserTag) {
|
||||
const download = mapTorrentToDownload(torrent);
|
||||
Object.assign(download, { type: 'movie', coverArt: getCoverArt(movie), movieName: movie.title, movieInfo: radarrHistoryMatch, allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined });
|
||||
if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = movie.path || null; download.arrLink = getRadarrLink(movie); }
|
||||
Object.assign(download, { type: 'movie', coverArt: DownloadAssembler.getCoverArt(movie), movieName: movie.title, movieInfo: radarrHistoryMatch, allTags, matchedUserTag: matchedUserTag || null, tagBadges: showAll ? TagMatcher.buildTagBadges(allTags, embyUserMap) : undefined });
|
||||
if (isAdmin) { download.downloadPath = download.savePath || null; download.targetPath = movie.path || null; download.arrLink = DownloadAssembler.getRadarrLink(movie); }
|
||||
userDownloads.push(download);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user