// 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; } } } }); } module.exports = { extractRequestedUser, filterRequestsByUser, decorateRequestsWithArrLinks };