const express = require('express'); const axios = require('axios'); const router = express.Router(); const SABNZBD_URL = process.env.SABNZBD_URL; const SABNZBD_API_KEY = process.env.SABNZBD_API_KEY; const SONARR_URL = process.env.SONARR_URL; const SONARR_API_KEY = process.env.SONARR_API_KEY; const RADARR_URL = process.env.RADARR_URL; const RADARR_API_KEY = process.env.RADARR_API_KEY; const EMBY_URL = process.env.EMBY_URL; const EMBY_API_KEY = process.env.EMBY_API_KEY; // Helper function to extract user tag from series/movie function extractUserTag(tags) { if (!tags) return null; const userTag = tags.find(tag => tag.label && tag.label.startsWith('user:')); return userTag ? userTag.label.replace('user:', '') : null; } // Get user downloads for current session router.get('/user-downloads/:sessionId', async (req, res) => { try { // Get current user from Emby session const sessionResponse = await axios.get(`${EMBY_URL}/Sessions`, { headers: { 'X-MediaBrowser-Token': EMBY_API_KEY } }); const session = sessionResponse.data.find(s => s.Id === req.params.sessionId); if (!session) { return res.status(404).json({ error: 'Session not found' }); } const userResponse = await axios.get(`${EMBY_URL}/Users/${session.UserId}`, { headers: { 'X-MediaBrowser-Token': EMBY_API_KEY } }); const currentUser = userResponse.data; const username = currentUser.Name.toLowerCase(); // Get SABnzbd queue and history const [sabnzbdQueue, sabnzbdHistory, sonarrQueue, sonarrHistory, radarrQueue, radarrHistory, sonarrSeries, radarrMovies] = await Promise.all([ axios.get(`${SABNZBD_URL}/api`, { params: { mode: 'queue', apikey: SABNZBD_API_KEY, output: 'json' } }), axios.get(`${SABNZBD_URL}/api`, { params: { mode: 'history', apikey: SABNZBD_API_KEY, output: 'json', limit: 100 } }), axios.get(`${SONARR_URL}/api/v3/queue`, { headers: { 'X-Api-Key': SONARR_API_KEY } }).catch(() => ({ data: { records: [] } })), axios.get(`${SONARR_URL}/api/v3/history`, { headers: { 'X-Api-Key': SONARR_API_KEY }, params: { pageSize: 100 } }).catch(() => ({ data: { records: [] } })), axios.get(`${RADARR_URL}/api/v3/queue`, { headers: { 'X-Api-Key': RADARR_API_KEY } }).catch(() => ({ data: { records: [] } })), axios.get(`${RADARR_URL}/api/v3/history`, { headers: { 'X-Api-Key': RADARR_API_KEY }, params: { pageSize: 100 } }).catch(() => ({ data: { records: [] } })), axios.get(`${SONARR_URL}/api/v3/series`, { headers: { 'X-Api-Key': SONARR_API_KEY } }).catch(() => ({ data: [] })), axios.get(`${RADARR_URL}/api/v3/movie`, { headers: { 'X-Api-Key': RADARR_API_KEY } }).catch(() => ({ data: [] })) ]); // Create maps for quick lookup const seriesMap = new Map(sonarrSeries.data.map(s => [s.tvdbId || s.id, s])); const moviesMap = new Map(radarrMovies.data.map(m => [m.tmdbId || m.id, m])); // Match SABnzbd downloads to Sonarr/Radarr activity const userDownloads = []; // Process SABnzbd queue if (sabnzbdQueue.data.queue && sabnzbdQueue.data.queue.slots) { for (const slot of sabnznzbdQueue.data.queue.slots) { const nzbName = slot.nzbname.toLowerCase(); // Try to match with Sonarr const sonarrMatch = sonarrQueue.data.records.find(r => r.title.toLowerCase().includes(nzbName) || nzbName.includes(r.title.toLowerCase()) ); if (sonarrMatch && sonarrMatch.seriesId) { const series = seriesMap.get(sonarrMatch.seriesId); if (series) { const userTag = extractUserTag(series.tags); if (userTag && userTag.toLowerCase() === username) { userDownloads.push({ type: 'series', title: slot.nzbname, status: 'downloading', progress: slot.percentage, size: slot.size, speed: slot.speed, eta: slot.eta, seriesName: series.title, episodeInfo: sonarrMatch }); } } } // Try to match with Radarr const radarrMatch = radarrQueue.data.records.find(r => r.title.toLowerCase().includes(nzbName) || nzbName.includes(r.title.toLowerCase()) ); if (radarrMatch && radarrMatch.movieId) { const movie = moviesMap.get(radarrMatch.movieId); if (movie) { const userTag = extractUserTag(movie.tags); if (userTag && userTag.toLowerCase() === username) { userDownloads.push({ type: 'movie', title: slot.nzbname, status: 'downloading', progress: slot.percentage, size: slot.size, speed: slot.speed, eta: slot.eta, movieName: movie.title, movieInfo: radarrMatch }); } } } } } // Process SABnzbd history if (sabnzbdHistory.data.history && sabnzbdHistory.data.history.slots) { for (const slot of sabnzbdHistory.data.history.slots) { const nzbName = slot.nzbname.toLowerCase(); // Try to match with Sonarr history const sonarrMatch = sonarrHistory.data.records.find(r => r.title.toLowerCase().includes(nzbName) || nzbName.includes(r.title.toLowerCase()) ); if (sonarrMatch && sonarrMatch.seriesId) { const series = seriesMap.get(sonarrMatch.seriesId); if (series) { const userTag = extractUserTag(series.tags); if (userTag && userTag.toLowerCase() === username) { userDownloads.push({ type: 'series', title: slot.nzbname, status: slot.status, size: slot.size, completedAt: slot.completed_time, seriesName: series.title, episodeInfo: sonarrMatch }); } } } // Try to match with Radarr history const radarrMatch = radarrHistory.data.records.find(r => r.title.toLowerCase().includes(nzbName) || nzbName.includes(r.title.toLowerCase()) ); if (radarrMatch && radarrMatch.movieId) { const movie = moviesMap.get(radarrMatch.movieId); if (movie) { const userTag = extractUserTag(movie.tags); if (userTag && userTag.toLowerCase() === username) { userDownloads.push({ type: 'movie', title: slot.nzbname, status: slot.status, size: slot.size, completedAt: slot.completed_time, movieName: movie.title, movieInfo: radarrMatch }); } } } } } res.json({ user: currentUser.Name, downloads: userDownloads }); } catch (error) { res.status(500).json({ error: 'Failed to fetch user downloads', details: error.message }); } }); // Get all users with their download counts router.get('/user-summary', async (req, res) => { try { // Get all Emby users const usersResponse = await axios.get(`${EMBY_URL}/Users`, { headers: { 'X-MediaBrowser-Token': EMBY_API_KEY } }); // Get all series and movies with tags const [sonarrSeries, radarrMovies] = await Promise.all([ axios.get(`${SONARR_URL}/api/v3/series`, { headers: { 'X-Api-Key': SONARR_API_KEY } }).catch(() => ({ data: [] })), axios.get(`${RADARR_URL}/api/v3/movie`, { headers: { 'X-Api-Key': RADARR_API_KEY } }).catch(() => ({ data: [] })) ]); // Count downloads per user const userDownloads = {}; usersResponse.data.forEach(user => { userDownloads[user.Name.toLowerCase()] = { username: user.Name, seriesCount: 0, movieCount: 0 }; }); // Process series tags sonarrSeries.data.forEach(series => { const userTag = extractUserTag(series.tags); if (userTag) { const username = userTag.toLowerCase(); if (userDownloads[username]) { userDownloads[username].seriesCount++; } } }); // Process movie tags radarrMovies.data.forEach(movie => { const userTag = extractUserTag(movie.tags); if (userTag) { const username = userTag.toLowerCase(); if (userDownloads[username]) { userDownloads[username].movieCount++; } } }); res.json(Object.values(userDownloads)); } catch (error) { res.status(500).json({ error: 'Failed to fetch user summary', details: error.message }); } }); module.exports = router;