Files
sofarr/server/services/DownloadBuilder.js
T
gronod ed4237debb
Docs Check / Markdown lint (push) Successful in 1m43s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 2m1s
CI / Security audit (push) Successful in 2m48s
Docs Check / Mermaid diagram parse check (push) Successful in 3m8s
CI / Tests & coverage (push) Failing after 3m33s
CI / Swagger Validation & Coverage (push) Successful in 3m34s
Build and Push Docker Image / build (push) Successful in 4m36s
feat(ombi): Add Ombi PALDRA integration for request management
- Add OmbiRetriever extending ArrRetriever for PALDRA compliance
- Add OmbiClient for low-level Ombi API communication
- Add getOmbiInstances() to config.js following multi-instance pattern
- Register Ombi in PALDRA registry with Ombi-specific methods
- Add external ID matching (TMDB/TVDB/IMDB) to Ombi requests
- Update DownloadMatcher to be async and enrich downloads with Ombi links
- Add getOmbiLink/getOmbiSearchLink helpers to DownloadAssembler
- Implement new service icon layout (Ombi + Sonarr/Radarr icons)
- Add CSS styling for service icons
- Update dashboard routes to include Ombi configuration
- Extend OpenAPI with Ombi tag and NormalizedDownload properties
- Update documentation (README, ARCHITECTURE, SECURITY, CHANGELOG)
- Add Ombi configuration to .env.sample
2026-05-21 17:00:04 +01:00

117 lines
4.6 KiB
JavaScript

// 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
*/
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 = 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 = 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 = 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
};