// Copyright (c) 2026 Gordon Bolton. MIT License. /** * DownloadBuilder - Aggregates and matches download data from multiple sources. * This service processes cached data from SABnzbd, qBittorrent, Sonarr, and Radarr to build * a unified view of downloads for each user, matching downloads to media metadata via tags. */ const DownloadMatcher = require('./DownloadMatcher'); /** * Builds a unified list of downloads for a user from multiple download clients. * Matches SABnzbd and qBittorrent downloads to Sonarr/Radarr activity using tags. * @param {Object} cacheSnapshot - Cached data from all services * @param {Object} options - User context and metadata maps * @param {string} options.username - Lowercase username for tag matching * @param {string} options.usernameSanitized - Original username * @param {boolean} options.isAdmin - Whether user is admin * @param {boolean} options.showAll - Whether to show all users' downloads * @param {Map} options.seriesMap - Map of seriesId to series object * @param {Map} options.moviesMap - Map of movieId to movie object * @param {Map} options.sonarrTagMap - Map of Sonarr tag IDs to labels * @param {Map} options.radarrTagMap - Map of Radarr tag IDs to labels * @param {Map} options.embyUserMap - Map of Emby users for admin view * @param {OmbiRetriever} options.ombiRetriever - Ombi data retriever instance (optional) * @param {string} options.ombiBaseUrl - Ombi base URL for link generation (optional) * @returns {Array} Array of download objects for the user */ async function buildUserDownloads(cacheSnapshot, { username, usernameSanitized, isAdmin, showAll, seriesMap, moviesMap, sonarrTagMap, radarrTagMap, embyUserMap, ombiRetriever, ombiBaseUrl }) { // Input validation if (!cacheSnapshot || typeof cacheSnapshot !== 'object') { console.error('[DownloadBuilder] Invalid cacheSnapshot provided'); return []; } try { // Handle null/undefined cache data const sabnzbdQueue = cacheSnapshot.sabnzbdQueue || { data: { queue: { slots: [] } } }; const sabnzbdHistory = cacheSnapshot.sabnzbdHistory || { data: { history: { slots: [] } } }; const sonarrQueue = cacheSnapshot.sonarrQueue || { data: { records: [] } }; const sonarrHistory = cacheSnapshot.sonarrHistory || { data: { records: [] } }; const radarrQueue = cacheSnapshot.radarrQueue || { data: { records: [] } }; const radarrHistory = cacheSnapshot.radarrHistory || { data: { records: [] } }; const qbittorrentTorrents = cacheSnapshot.qbittorrentTorrents || []; // Get queue status for SABnzbd const queueStatus = sabnzbdQueue.data?.queue?.status || null; const queueSpeed = sabnzbdQueue.data?.queue?.speed || null; const queueKbpersec = sabnzbdQueue.data?.queue?.kbpersec || null; // Build context for matching functions const context = { sonarrQueueRecords: sonarrQueue.data?.records || [], sonarrHistoryRecords: sonarrHistory.data?.records || [], radarrQueueRecords: radarrQueue.data?.records || [], radarrHistoryRecords: radarrHistory.data?.records || [], seriesMap: seriesMap || new Map(), moviesMap: moviesMap || new Map(), sonarrTagMap: sonarrTagMap || new Map(), radarrTagMap: radarrTagMap || new Map(), username, isAdmin, showAll, embyUserMap: embyUserMap || new Map(), queueStatus, queueSpeed, queueKbpersec, ombiRetriever, ombiBaseUrl }; // Match all download sources const userDownloads = []; const seenDownloadKeys = new Set(); if (sabnzbdQueue.data?.queue?.slots) { const sabMatches = await DownloadMatcher.matchSabSlots(sabnzbdQueue.data.queue.slots, context); for (const dl of sabMatches) { const key = `${dl.type}:${dl.title}`; if (!seenDownloadKeys.has(key)) { seenDownloadKeys.add(key); userDownloads.push(dl); } } } if (sabnzbdHistory.data?.history?.slots) { const sabHistoryMatches = await DownloadMatcher.matchSabHistory(sabnzbdHistory.data.history.slots, context); for (const dl of sabHistoryMatches) { const key = `${dl.type}:${dl.title}`; if (!seenDownloadKeys.has(key)) { seenDownloadKeys.add(key); userDownloads.push(dl); } } } const torrentMatches = await DownloadMatcher.matchTorrents(qbittorrentTorrents, context); for (const dl of torrentMatches) { const key = `${dl.type}:${dl.title}`; if (!seenDownloadKeys.has(key)) { seenDownloadKeys.add(key); userDownloads.push(dl); } } return userDownloads; } catch (error) { console.error('[DownloadBuilder] Error building user downloads:', error.message); return []; } } module.exports = { buildUserDownloads };