Fix: Generate Ombi links directly from TMDB ID; show on downloads and history for all users
Build and Push Docker Image / build (push) Successful in 49s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 2m3s
CI / Security audit (push) Successful in 2m27s
CI / Swagger Validation & Coverage (push) Successful in 2m47s
CI / Tests & coverage (push) Failing after 2m49s
CI / Security audit (pull_request) Successful in 1m53s
CI / Swagger Validation & Coverage (pull_request) Successful in 2m36s
CI / Tests & coverage (pull_request) Failing after 2m46s
Build and Push Docker Image / build (push) Successful in 49s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 2m3s
CI / Security audit (push) Successful in 2m27s
CI / Swagger Validation & Coverage (push) Successful in 2m47s
CI / Tests & coverage (push) Failing after 2m49s
CI / Security audit (pull_request) Successful in 1m53s
CI / Swagger Validation & Coverage (pull_request) Successful in 2m36s
CI / Tests & coverage (pull_request) Failing after 2m46s
- Replace Ombi API-based matching with simple TMDB ID link generation
- Movies link to {ombiBaseUrl}/details/movie/{tmdbId}
- TV shows link to {ombiBaseUrl}/details/tv/{tmdbId}
- Add ombiLink to all history items (Sonarr + Radarr) for all users
- Add ombiLink to torrent history matches that were previously missing it
- addOmbiMatching is now synchronous (no Ombi API calls)
This commit is contained in:
@@ -483,8 +483,7 @@ router.get('/stream', requireAuth, async (req, res) => {
|
|||||||
ombiBaseUrl
|
ombiBaseUrl
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[SSE] userDownloads type: ${typeof userDownloads}, isArray: ${Array.isArray(userDownloads)}, value:`, userDownloads);
|
console.log(`[SSE] Sending ${userDownloads.length} downloads for ${user.name}`);
|
||||||
console.log(`[SSE] Sending ${userDownloads?.length || 0} downloads for ${user.name}`);
|
|
||||||
const downloadClients = downloadClientRegistry.getAllClients().map(c => ({
|
const downloadClients = downloadClientRegistry.getAllClients().map(c => ({
|
||||||
id: c.getInstanceId(),
|
id: c.getInstanceId(),
|
||||||
name: c.name,
|
name: c.name,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const axios = require('axios');
|
|||||||
const requireAuth = require('../middleware/requireAuth');
|
const requireAuth = require('../middleware/requireAuth');
|
||||||
const cache = require('../utils/cache');
|
const cache = require('../utils/cache');
|
||||||
const { fetchSonarrHistory, fetchRadarrHistory, classifySonarrEvent, classifyRadarrEvent } = require('../utils/historyFetcher');
|
const { fetchSonarrHistory, fetchRadarrHistory, classifySonarrEvent, classifyRadarrEvent } = require('../utils/historyFetcher');
|
||||||
const { getSonarrInstances, getRadarrInstances } = require('../utils/config');
|
const { getSonarrInstances, getRadarrInstances, getOmbiInstances } = require('../utils/config');
|
||||||
const sanitizeError = require('../utils/sanitizeError');
|
const sanitizeError = require('../utils/sanitizeError');
|
||||||
|
|
||||||
// Re-use the same tag/cover-art helpers as dashboard.js by importing them
|
// Re-use the same tag/cover-art helpers as dashboard.js by importing them
|
||||||
@@ -194,6 +194,15 @@ function getRadarrLink(movie) {
|
|||||||
return `${movie._instanceUrl}/movie/${movie.titleSlug}`;
|
return `${movie._instanceUrl}/movie/${movie.titleSlug}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOmbiDetailsLink(mediaObj, type, ombiBaseUrl) {
|
||||||
|
if (!ombiBaseUrl || !mediaObj) return null;
|
||||||
|
const tmdbId = mediaObj.tmdbId;
|
||||||
|
if (!tmdbId) return null;
|
||||||
|
if (type === 'series') return `${ombiBaseUrl}/details/tv/${tmdbId}`;
|
||||||
|
if (type === 'movie') return `${ombiBaseUrl}/details/movie/${tmdbId}`;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @openapi
|
* @openapi
|
||||||
* /api/history/recent:
|
* /api/history/recent:
|
||||||
@@ -343,6 +352,9 @@ router.get('/recent', requireAuth, async (req, res) => {
|
|||||||
const sonarrInstances = getSonarrInstances();
|
const sonarrInstances = getSonarrInstances();
|
||||||
const radarrInstances = getRadarrInstances();
|
const radarrInstances = getRadarrInstances();
|
||||||
|
|
||||||
|
const ombiInstances = getOmbiInstances();
|
||||||
|
const ombiBaseUrl = ombiInstances.length > 0 ? ombiInstances[0].url : null;
|
||||||
|
|
||||||
const [sonarrHistory, radarrHistory, embyUserMap] = await Promise.all([
|
const [sonarrHistory, radarrHistory, embyUserMap] = await Promise.all([
|
||||||
fetchSonarrHistory(since),
|
fetchSonarrHistory(since),
|
||||||
fetchRadarrHistory(since),
|
fetchRadarrHistory(since),
|
||||||
@@ -389,6 +401,8 @@ router.get('/recent', requireAuth, async (req, res) => {
|
|||||||
quality,
|
quality,
|
||||||
instanceName: record._instanceName || null,
|
instanceName: record._instanceName || null,
|
||||||
arrLink: getSonarrLink(series),
|
arrLink: getSonarrLink(series),
|
||||||
|
ombiLink: getOmbiDetailsLink(series, 'series', ombiBaseUrl),
|
||||||
|
ombiTooltip: 'View in Ombi',
|
||||||
allTags,
|
allTags,
|
||||||
matchedUserTag: matchedUserTag || null,
|
matchedUserTag: matchedUserTag || null,
|
||||||
tagBadges: showAll ? buildTagBadges(allTags, embyUserMap) : undefined,
|
tagBadges: showAll ? buildTagBadges(allTags, embyUserMap) : undefined,
|
||||||
@@ -438,6 +452,8 @@ router.get('/recent', requireAuth, async (req, res) => {
|
|||||||
quality,
|
quality,
|
||||||
instanceName: record._instanceName || null,
|
instanceName: record._instanceName || null,
|
||||||
arrLink: getRadarrLink(movie),
|
arrLink: getRadarrLink(movie),
|
||||||
|
ombiLink: getOmbiDetailsLink(movie, 'movie', ombiBaseUrl),
|
||||||
|
ombiTooltip: 'View in Ombi',
|
||||||
allTags,
|
allTags,
|
||||||
matchedUserTag: matchedUserTag || null,
|
matchedUserTag: matchedUserTag || null,
|
||||||
tagBadges: showAll ? buildTagBadges(allTags, embyUserMap) : undefined,
|
tagBadges: showAll ? buildTagBadges(allTags, embyUserMap) : undefined,
|
||||||
|
|||||||
@@ -45,19 +45,17 @@ function getRadarrLink(movie) {
|
|||||||
return `${movie._instanceUrl}/movie/${movie.titleSlug}`;
|
return `${movie._instanceUrl}/movie/${movie.titleSlug}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to build Ombi request link
|
// Helper to build Ombi details link using TMDB ID from *arr media object
|
||||||
function getOmbiLink(requestId, type, ombiBaseUrl) {
|
// Movies: {ombiBaseUrl}/details/movie/{tmdbId}
|
||||||
if (!requestId || !type || !ombiBaseUrl) return null;
|
// TV: {ombiBaseUrl}/details/tv/{tmdbId}
|
||||||
return `${ombiBaseUrl}/#/request/${type}/${requestId}`;
|
function getOmbiDetailsLink(mediaObj, type, ombiBaseUrl) {
|
||||||
}
|
if (!ombiBaseUrl || !mediaObj) return null;
|
||||||
|
const tmdbId = mediaObj.tmdbId;
|
||||||
// Helper to build Ombi search link
|
if (!tmdbId) return null;
|
||||||
function getOmbiSearchLink(searchId, type, ombiBaseUrl) {
|
|
||||||
if (!searchId || !type || !ombiBaseUrl) return null;
|
|
||||||
if (type === 'series') {
|
if (type === 'series') {
|
||||||
return `${ombiBaseUrl}/#/tv/search/${searchId}`;
|
return `${ombiBaseUrl}/details/tv/${tmdbId}`;
|
||||||
} else if (type === 'movie') {
|
} else if (type === 'movie') {
|
||||||
return `${ombiBaseUrl}/#/movie/search/${searchId}`;
|
return `${ombiBaseUrl}/details/movie/${tmdbId}`;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -118,8 +116,7 @@ module.exports = {
|
|||||||
getImportIssues,
|
getImportIssues,
|
||||||
getSonarrLink,
|
getSonarrLink,
|
||||||
getRadarrLink,
|
getRadarrLink,
|
||||||
getOmbiLink,
|
getOmbiDetailsLink,
|
||||||
getOmbiSearchLink,
|
|
||||||
canBlocklist,
|
canBlocklist,
|
||||||
extractEpisode,
|
extractEpisode,
|
||||||
gatherEpisodes
|
gatherEpisodes
|
||||||
|
|||||||
@@ -45,64 +45,18 @@ function buildMoviesMapFromRecords(queueRecords, historyRecords) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Matches a download object with Ombi requests and adds Ombi links
|
* Adds an Ombi details link to a download object using the TMDB ID from the *arr media object.
|
||||||
|
* No Ombi API call is required — the link is built directly from the TMDB ID.
|
||||||
* @param {Object} downloadObj - Download object to enhance
|
* @param {Object} downloadObj - Download object to enhance
|
||||||
* @param {Object} seriesOrMovie - Series or movie object from Sonarr/Radarr
|
* @param {Object} seriesOrMovie - Series or movie object from Sonarr/Radarr
|
||||||
* @param {Object} context - Context containing Ombi retriever and base URL
|
* @param {Object} context - Context containing ombiBaseUrl
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
*/
|
||||||
async function addOmbiMatching(downloadObj, seriesOrMovie, context) {
|
function addOmbiMatching(downloadObj, seriesOrMovie, context) {
|
||||||
const { ombiRetriever, ombiBaseUrl } = context;
|
const { ombiBaseUrl } = context;
|
||||||
|
const link = DownloadAssembler.getOmbiDetailsLink(seriesOrMovie, downloadObj.type, ombiBaseUrl);
|
||||||
if (!ombiRetriever || !ombiBaseUrl || !seriesOrMovie) {
|
if (link) {
|
||||||
return;
|
downloadObj.ombiLink = link;
|
||||||
}
|
downloadObj.ombiTooltip = 'View in Ombi';
|
||||||
|
|
||||||
try {
|
|
||||||
let ombiRequest = null;
|
|
||||||
let searchResult = null;
|
|
||||||
|
|
||||||
if (downloadObj.type === 'series') {
|
|
||||||
// For TV shows, try TVDB ID first, then TMDB ID
|
|
||||||
const tvdbId = seriesOrMovie.tvdbId;
|
|
||||||
const tmdbId = seriesOrMovie.tmdbId;
|
|
||||||
|
|
||||||
ombiRequest = await ombiRetriever.findTvRequest(tvdbId, tmdbId);
|
|
||||||
|
|
||||||
if (!ombiRequest) {
|
|
||||||
// Fallback to search
|
|
||||||
searchResult = await ombiRetriever.searchTv(tvdbId, tmdbId);
|
|
||||||
}
|
|
||||||
} else if (downloadObj.type === 'movie') {
|
|
||||||
// For movies, try TMDB ID first, then IMDB ID
|
|
||||||
const tmdbId = seriesOrMovie.tmdbId;
|
|
||||||
const imdbId = seriesOrMovie.imdbId;
|
|
||||||
|
|
||||||
ombiRequest = await ombiRetriever.findMovieRequest(tmdbId, imdbId);
|
|
||||||
|
|
||||||
if (!ombiRequest) {
|
|
||||||
// Fallback to search
|
|
||||||
searchResult = await ombiRetriever.searchMovie(tmdbId, imdbId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ombiRequest) {
|
|
||||||
// Found existing request
|
|
||||||
downloadObj.ombiLink = `${ombiBaseUrl}/#/request/${ombiRequest.type}/${ombiRequest.id}`;
|
|
||||||
downloadObj.ombiRequestId = ombiRequest.id;
|
|
||||||
downloadObj.ombiTooltip = 'Request';
|
|
||||||
} else if (searchResult) {
|
|
||||||
// No request found, but search succeeded
|
|
||||||
if (downloadObj.type === 'series') {
|
|
||||||
downloadObj.ombiLink = `${ombiBaseUrl}/#/tv/search/${searchResult.id}`;
|
|
||||||
} else {
|
|
||||||
downloadObj.ombiLink = `${ombiBaseUrl}/#/movie/search/${searchResult.id}`;
|
|
||||||
}
|
|
||||||
downloadObj.ombiTooltip = 'Search';
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
// Silently fail Ombi matching - don't break the download object creation
|
|
||||||
console.error('[DownloadMatcher] Ombi matching error:', error.message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,7 +216,7 @@ async function matchSabSlots(slots, context) {
|
|||||||
dlObj.arrContentType = 'episode';
|
dlObj.arrContentType = 'episode';
|
||||||
}
|
}
|
||||||
dlObj.canBlocklist = DownloadAssembler.canBlocklist(dlObj, isAdmin);
|
dlObj.canBlocklist = DownloadAssembler.canBlocklist(dlObj, isAdmin);
|
||||||
await addOmbiMatching(dlObj, series, context);
|
addOmbiMatching(dlObj, series, context);
|
||||||
matched.push(dlObj);
|
matched.push(dlObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,7 +269,7 @@ async function matchSabSlots(slots, context) {
|
|||||||
dlObj.arrContentType = 'movie';
|
dlObj.arrContentType = 'movie';
|
||||||
}
|
}
|
||||||
dlObj.canBlocklist = DownloadAssembler.canBlocklist(dlObj, isAdmin);
|
dlObj.canBlocklist = DownloadAssembler.canBlocklist(dlObj, isAdmin);
|
||||||
await addOmbiMatching(dlObj, movie, context);
|
addOmbiMatching(dlObj, movie, context);
|
||||||
matched.push(dlObj);
|
matched.push(dlObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -385,7 +339,7 @@ async function matchSabHistory(slots, context) {
|
|||||||
dlObj.targetPath = series.path || null;
|
dlObj.targetPath = series.path || null;
|
||||||
dlObj.arrLink = DownloadAssembler.getSonarrLink(series);
|
dlObj.arrLink = DownloadAssembler.getSonarrLink(series);
|
||||||
}
|
}
|
||||||
await addOmbiMatching(dlObj, series, context);
|
addOmbiMatching(dlObj, series, context);
|
||||||
matched.push(dlObj);
|
matched.push(dlObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -424,7 +378,7 @@ async function matchSabHistory(slots, context) {
|
|||||||
dlObj.targetPath = movie.path || null;
|
dlObj.targetPath = movie.path || null;
|
||||||
dlObj.arrLink = DownloadAssembler.getRadarrLink(movie);
|
dlObj.arrLink = DownloadAssembler.getRadarrLink(movie);
|
||||||
}
|
}
|
||||||
await addOmbiMatching(dlObj, movie, context);
|
addOmbiMatching(dlObj, movie, context);
|
||||||
matched.push(dlObj);
|
matched.push(dlObj);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,7 +456,7 @@ async function matchTorrents(torrents, context) {
|
|||||||
download.arrContentType = 'episode';
|
download.arrContentType = 'episode';
|
||||||
}
|
}
|
||||||
download.canBlocklist = DownloadAssembler.canBlocklist(download, isAdmin);
|
download.canBlocklist = DownloadAssembler.canBlocklist(download, isAdmin);
|
||||||
await addOmbiMatching(download, series, context);
|
addOmbiMatching(download, series, context);
|
||||||
matched.push(download);
|
matched.push(download);
|
||||||
matchedAny = true;
|
matchedAny = true;
|
||||||
continue;
|
continue;
|
||||||
@@ -547,7 +501,7 @@ async function matchTorrents(torrents, context) {
|
|||||||
download.arrContentType = 'movie';
|
download.arrContentType = 'movie';
|
||||||
}
|
}
|
||||||
download.canBlocklist = DownloadAssembler.canBlocklist(download, isAdmin);
|
download.canBlocklist = DownloadAssembler.canBlocklist(download, isAdmin);
|
||||||
await addOmbiMatching(download, movie, context);
|
addOmbiMatching(download, movie, context);
|
||||||
matched.push(download);
|
matched.push(download);
|
||||||
matchedAny = true;
|
matchedAny = true;
|
||||||
continue;
|
continue;
|
||||||
@@ -580,6 +534,7 @@ async function matchTorrents(torrents, context) {
|
|||||||
download.targetPath = series.path || null;
|
download.targetPath = series.path || null;
|
||||||
download.arrLink = DownloadAssembler.getSonarrLink(series);
|
download.arrLink = DownloadAssembler.getSonarrLink(series);
|
||||||
}
|
}
|
||||||
|
addOmbiMatching(download, series, context);
|
||||||
matched.push(download);
|
matched.push(download);
|
||||||
matchedAny = true;
|
matchedAny = true;
|
||||||
continue;
|
continue;
|
||||||
@@ -615,6 +570,7 @@ async function matchTorrents(torrents, context) {
|
|||||||
download.targetPath = movie.path || null;
|
download.targetPath = movie.path || null;
|
||||||
download.arrLink = DownloadAssembler.getRadarrLink(movie);
|
download.arrLink = DownloadAssembler.getRadarrLink(movie);
|
||||||
}
|
}
|
||||||
|
addOmbiMatching(download, movie, context);
|
||||||
matched.push(download);
|
matched.push(download);
|
||||||
matchedAny = true;
|
matchedAny = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user