240 lines
8.8 KiB
JavaScript
240 lines
8.8 KiB
JavaScript
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
|
/**
|
|
* Helper functions for extracting user information from Ombi API responses.
|
|
* The Ombi API returns requestedUser as an OmbiStore.Entities.OmbiUser object,
|
|
* not a string, so we need to extract the username from the object.
|
|
*/
|
|
|
|
const { logToFile } = require('./logger');
|
|
|
|
/**
|
|
* Extracts the username from an Ombi request object.
|
|
* Handles both the OmbiUser object format and legacy string format.
|
|
*
|
|
* @param {Object} request - The Ombi request object
|
|
* @returns {string} The extracted username, or empty string if not found
|
|
*/
|
|
function extractRequestedUser(request) {
|
|
if (!request) return '';
|
|
|
|
// Try to locate a user object or string from various fields common to Ombi Movies and TV shows
|
|
const userSource = request.requestedUser || request.RequestedUser ||
|
|
request.user || request.User ||
|
|
request.requestedBy || request.RequestedBy ||
|
|
request.ombiUser || request.OmbiUser ||
|
|
request.requestedByUser || request.RequestedByUser;
|
|
|
|
// If userSource is an object, extract key fields
|
|
if (userSource && typeof userSource === 'object') {
|
|
const username = userSource.alias || userSource.Alias ||
|
|
userSource.userAlias || userSource.UserAlias ||
|
|
userSource.userName || userSource.UserName ||
|
|
userSource.normalizedUserName || userSource.NormalizedUserName ||
|
|
userSource.displayName || userSource.DisplayName ||
|
|
userSource.email || userSource.Email;
|
|
if (username) return username;
|
|
}
|
|
|
|
// If userSource is a string and not an empty object/array
|
|
if (userSource && typeof userSource === 'string') {
|
|
return userSource;
|
|
}
|
|
|
|
// Fallbacks on the request root level
|
|
const rootFallback = request.requestedByAlias || request.RequestedByAlias ||
|
|
request.requestedByUsername || request.RequestedByUsername ||
|
|
request.requester || request.Requester ||
|
|
request.requestedByEmail || request.RequestedByEmail;
|
|
if (rootFallback) return rootFallback;
|
|
|
|
// Check seasons / childRequests nested arrays (common for Ombi TV show requests)
|
|
if (Array.isArray(request.seasons)) {
|
|
for (const season of request.seasons) {
|
|
const seasonUser = extractRequestedUser(season);
|
|
if (seasonUser) return seasonUser;
|
|
}
|
|
}
|
|
|
|
if (Array.isArray(request.childRequests)) {
|
|
for (const child of request.childRequests) {
|
|
const childUser = extractRequestedUser(child);
|
|
if (childUser) return childUser;
|
|
}
|
|
}
|
|
|
|
// Add warning log when user extraction returns empty for non-empty requests
|
|
if (Object.keys(request).length > 0 && !request.notificationType) {
|
|
logToFile(`[Ombi] WARNING: User extraction failed for request: ${JSON.stringify(request)}`);
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
function filterRequestsByUser(requests, username, showAll) {
|
|
if (!Array.isArray(requests)) return [];
|
|
if (showAll || !username) return requests;
|
|
const usernameLower = username.toLowerCase();
|
|
return requests.filter(req => {
|
|
const requestedUser = extractRequestedUser(req);
|
|
return requestedUser.toLowerCase() === usernameLower;
|
|
});
|
|
}
|
|
|
|
async function decorateRequestsWithArrLinks(requests, isAdmin) {
|
|
if (!isAdmin || !Array.isArray(requests)) return;
|
|
|
|
const arrRetrieverRegistry = require('./arrRetrievers');
|
|
await arrRetrieverRegistry.initialize();
|
|
|
|
const sonarrRetrievers = arrRetrieverRegistry.getRetrieversByType('sonarr') || [];
|
|
const radarrRetrievers = arrRetrieverRegistry.getRetrieversByType('radarr') || [];
|
|
|
|
const [sonarrData, radarrData] = await Promise.all([
|
|
Promise.all(sonarrRetrievers.map(async r => {
|
|
try {
|
|
const response = await require('axios').get(`${r.url}/api/v3/series`, {
|
|
headers: { 'X-Api-Key': r.apiKey }
|
|
});
|
|
return { instance: r, series: response.data || [] };
|
|
} catch {
|
|
return { instance: r, series: [] };
|
|
}
|
|
})),
|
|
Promise.all(radarrRetrievers.map(async r => {
|
|
try {
|
|
const response = await require('axios').get(`${r.url}/api/v3/movie`, {
|
|
headers: { 'X-Api-Key': r.apiKey }
|
|
});
|
|
return { instance: r, movies: response.data || [] };
|
|
} catch {
|
|
return { instance: r, movies: [] };
|
|
}
|
|
}))
|
|
]);
|
|
|
|
requests.forEach(req => {
|
|
// Determine if it's TV or Movie. Often `mediaType` is set, or `type === 'Tv'`
|
|
// Fallback to checking for TV specific IDs.
|
|
const isTv = req.mediaType === 'tv' || req.type === 'Tv' || req.tvDbId || req.tvdbId || req.theTvDbId || req.theTvdbId || req.TvDbId || req.TheTvDbId;
|
|
|
|
if (isTv) {
|
|
const tvdbId = req.theTvDbId || req.theTvdbId || req.tvDbId || req.tvdbId || req.TvDbId || req.TheTvDbId || req.theMovieDbId || req.theTmdbId;
|
|
if (!tvdbId) return;
|
|
|
|
for (const instData of sonarrData) {
|
|
const match = instData.series.find(s => s && (s.tvdbId === parseInt(tvdbId, 10) || s.tmdbId === parseInt(tvdbId, 10)));
|
|
if (match && match.titleSlug) {
|
|
req.arrLink = `${instData.instance.url}/series/${match.titleSlug}`;
|
|
req.arrType = 'sonarr';
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
const tmdbId = req.theMovieDbId || req.imdbId || req.theTmdbId || req.TheMovieDbId || req.ImdbId;
|
|
if (!tmdbId) return;
|
|
|
|
for (const instData of radarrData) {
|
|
const match = instData.movies.find(m => m && (m.tmdbId === parseInt(tmdbId, 10) || m.imdbId === tmdbId));
|
|
if (match && match.titleSlug) {
|
|
req.arrLink = `${instData.instance.url}/movie/${match.titleSlug}`;
|
|
req.arrType = 'radarr';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
async function decorateDownloadsWithArrLinks(downloads, isAdmin) {
|
|
if (!isAdmin || !Array.isArray(downloads)) return;
|
|
|
|
const arrRetrieverRegistry = require('./arrRetrievers');
|
|
await arrRetrieverRegistry.initialize();
|
|
|
|
const sonarrRetrievers = arrRetrieverRegistry.getRetrieversByType('sonarr') || [];
|
|
const radarrRetrievers = arrRetrieverRegistry.getRetrieversByType('radarr') || [];
|
|
|
|
const [sonarrData, radarrData] = await Promise.all([
|
|
Promise.all(sonarrRetrievers.map(async r => {
|
|
try {
|
|
const response = await require('axios').get(`${r.url}/api/v3/series`, {
|
|
headers: { 'X-Api-Key': r.apiKey }
|
|
});
|
|
return { instance: r, series: response.data || [] };
|
|
} catch {
|
|
return { instance: r, series: [] };
|
|
}
|
|
})),
|
|
Promise.all(radarrRetrievers.map(async r => {
|
|
try {
|
|
const response = await require('axios').get(`${r.url}/api/v3/movie`, {
|
|
headers: { 'X-Api-Key': r.apiKey }
|
|
});
|
|
return { instance: r, movies: response.data || [] };
|
|
} catch {
|
|
return { instance: r, movies: [] };
|
|
}
|
|
}))
|
|
]);
|
|
|
|
downloads.forEach(dl => {
|
|
// Determine if it's TV (series) or Movie
|
|
const isTv = dl.type === 'series' || dl.arrType === 'sonarr';
|
|
|
|
if (isTv) {
|
|
// Look for a match in Sonarr instances
|
|
for (const instData of sonarrData) {
|
|
const match = instData.series.find(s => {
|
|
if (!s) return false;
|
|
// Match by database series ID if the instance matches
|
|
const instanceUrlMatch = dl.arrInstanceUrl === instData.instance.url;
|
|
if (instanceUrlMatch && dl.arrSeriesId != null && s.id === parseInt(dl.arrSeriesId, 10)) {
|
|
return true;
|
|
}
|
|
// Fallback to seriesName matching
|
|
if (dl.seriesName && s.title && dl.seriesName.toLowerCase() === s.title.toLowerCase()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
if (match && match.titleSlug) {
|
|
dl.arrLink = `${instData.instance.url}/series/${match.titleSlug}`;
|
|
dl.arrType = 'sonarr';
|
|
break;
|
|
}
|
|
}
|
|
} else if (dl.type === 'movie' || dl.arrType === 'radarr') {
|
|
// Look for a match in Radarr instances
|
|
for (const instData of radarrData) {
|
|
const match = instData.movies.find(m => {
|
|
if (!m) return false;
|
|
// Match by database movie ID if instance matches
|
|
const instanceUrlMatch = dl.arrInstanceUrl === instData.instance.url;
|
|
if (instanceUrlMatch && dl.arrContentId != null && m.id === parseInt(dl.arrContentId, 10)) {
|
|
return true;
|
|
}
|
|
// Fallback to movieName matching
|
|
if (dl.movieName && m.title && dl.movieName.toLowerCase() === m.title.toLowerCase()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
if (match && match.titleSlug) {
|
|
dl.arrLink = `${instData.instance.url}/movie/${match.titleSlug}`;
|
|
dl.arrType = 'radarr';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
extractRequestedUser,
|
|
filterRequestsByUser,
|
|
decorateRequestsWithArrLinks,
|
|
decorateDownloadsWithArrLinks
|
|
};
|