feat(ombi): Add Ombi PALDRA integration for request management
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

- 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
This commit is contained in:
2026-05-21 17:00:04 +01:00
parent de9a9284dc
commit ed4237debb
20 changed files with 850 additions and 33 deletions
+74 -3
View File
@@ -3,17 +3,20 @@ const { logToFile } = require('./logger');
const cache = require('./cache');
const {
getSonarrInstances,
getRadarrInstances
getRadarrInstances,
getOmbiInstances
} = require('./config');
// Import retriever classes
const PollingSonarrRetriever = require('../clients/PollingSonarrRetriever');
const PollingRadarrRetriever = require('../clients/PollingRadarrRetriever');
const OmbiRetriever = require('../clients/OmbiRetriever');
// Retriever type mapping
const retrieverClasses = {
sonarr: PollingSonarrRetriever,
radarr: PollingRadarrRetriever
radarr: PollingRadarrRetriever,
ombi: OmbiRetriever
};
/**
@@ -36,11 +39,13 @@ const arrRetrieverRegistry = {
// Get all instance configurations
const sonarrInstances = getSonarrInstances();
const radarrInstances = getRadarrInstances();
const ombiInstances = getOmbiInstances();
// Create retriever instances
const instanceConfigs = [
...sonarrInstances.map(inst => ({ ...inst, type: 'sonarr' })),
...radarrInstances.map(inst => ({ ...inst, type: 'radarr' }))
...radarrInstances.map(inst => ({ ...inst, type: 'radarr' })),
...ombiInstances.map(inst => ({ ...inst, type: 'ombi' }))
];
for (const config of instanceConfigs) {
@@ -303,6 +308,72 @@ const arrRetrieverRegistry = {
.filter(result => result.status === 'fulfilled')
.map(result => result.value)
};
},
/**
* Get Ombi retrievers
* @returns {Array<OmbiRetriever>} Array of Ombi retriever instances
*/
getOmbiRetrievers() {
return this.getRetrieversByType('ombi');
},
/**
* Get all Ombi requests
* @returns {Promise<Object>} Object with movie and TV request arrays
*/
async getOmbiRequests() {
const ombiRetrievers = this.getOmbiRetrievers();
if (ombiRetrievers.length === 0) {
return { movie: [], tv: [] };
}
// Use the first Ombi retriever (single instance expected)
const retriever = ombiRetrievers[0];
try {
const movieRequests = await retriever.getMovieRequests();
const tvRequests = await retriever.getTvRequests();
return { movie: movieRequests, tv: tvRequests };
} catch (error) {
logToFile(`[ArrRetrieverRegistry] Error fetching Ombi requests: ${error.message}`);
return { movie: [], tv: [] };
}
},
/**
* Get Ombi requests grouped by type
* @returns {Promise<Object>} Requests grouped by type (movie, tv)
*/
async getOmbiRequestsByType() {
return await this.getOmbiRequests();
},
/**
* Find Ombi request by external IDs
* @param {string} type - 'movie' or 'tv'
* @param {Object} externalIds - External IDs to search with
* @param {string} externalIds.tmdbId - TheMovieDB ID
* @param {string} externalIds.tvdbId - TheTVDB ID (for TV)
* @param {string} externalIds.imdbId - IMDB ID (for movies)
* @returns {Promise<Object|null>} Ombi request object or null if not found
*/
async findOmbiRequest(type, externalIds) {
const ombiRetrievers = this.getOmbiRetrievers();
if (ombiRetrievers.length === 0) {
return null;
}
const retriever = ombiRetrievers[0];
try {
if (type === 'movie') {
return await retriever.findMovieRequest(externalIds.tmdbId, externalIds.imdbId);
} else if (type === 'tv') {
return await retriever.findTvRequest(externalIds.tvdbId, externalIds.tmdbId);
}
} catch (error) {
logToFile(`[ArrRetrieverRegistry] Error finding Ombi request: ${error.message}`);
}
return null;
}
};
+9
View File
@@ -84,6 +84,14 @@ function getRadarrInstances() {
);
}
function getOmbiInstances() {
return parseInstances(
process.env.OMBI_INSTANCES,
process.env.OMBI_URL,
process.env.OMBI_API_KEY
);
}
function getQbittorrentInstances() {
return parseInstances(
process.env.QBITTORRENT_INSTANCES,
@@ -126,6 +134,7 @@ module.exports = {
getSABnzbdInstances,
getSonarrInstances,
getRadarrInstances,
getOmbiInstances,
getQbittorrentInstances,
getTransmissionInstances,
getRtorrentInstances,