253 lines
8.7 KiB
JavaScript
253 lines
8.7 KiB
JavaScript
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;
|