mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat: Tautulli integration (#2230)
* feat: media/user watch history data via Tautulli * fix(frontend): only display slideover cog button if there is media to manage * fix(lang): tweak permission denied messages * refactor: reorder Media section in slideover * refactor: use new Tautulli stats API * fix(frontend): do not attempt to fetch data when user lacks req perms * fix: remove unneccessary get_user requests * feat(frontend): display user avatars * feat: add external URL setting * feat: add play counts for past week/month * fix(lang): tweak strings Co-authored-by: Ryan Cohen <ryan@sct.dev>
This commit is contained in:
@@ -1,11 +1,17 @@
|
||||
import { Router } from 'express';
|
||||
import { getRepository, FindOperator, FindOneOptions, In } from 'typeorm';
|
||||
import Media from '../entity/Media';
|
||||
import { FindOneOptions, FindOperator, getRepository, In } from 'typeorm';
|
||||
import TautulliAPI from '../api/tautulli';
|
||||
import { MediaStatus, MediaType } from '../constants/media';
|
||||
import Media from '../entity/Media';
|
||||
import { User } from '../entity/User';
|
||||
import {
|
||||
MediaResultsResponse,
|
||||
MediaWatchDataResponse,
|
||||
} from '../interfaces/api/mediaInterfaces';
|
||||
import { Permission } from '../lib/permissions';
|
||||
import { getSettings } from '../lib/settings';
|
||||
import logger from '../logger';
|
||||
import { isAuthenticated } from '../middleware/auth';
|
||||
import { Permission } from '../lib/permissions';
|
||||
import { MediaResultsResponse } from '../interfaces/api/mediaInterfaces';
|
||||
|
||||
const mediaRoutes = Router();
|
||||
|
||||
@@ -161,4 +167,103 @@ mediaRoutes.delete(
|
||||
}
|
||||
);
|
||||
|
||||
mediaRoutes.get<{ id: string }, MediaWatchDataResponse>(
|
||||
'/:id/watch_data',
|
||||
isAuthenticated(Permission.ADMIN),
|
||||
async (req, res, next) => {
|
||||
const settings = getSettings().tautulli;
|
||||
|
||||
if (!settings.hostname || !settings.port || !settings.apiKey) {
|
||||
return next({
|
||||
status: 404,
|
||||
message: 'Tautulli API not configured.',
|
||||
});
|
||||
}
|
||||
|
||||
const media = await getRepository(Media).findOne({
|
||||
where: { id: Number(req.params.id) },
|
||||
});
|
||||
|
||||
if (!media) {
|
||||
return next({ status: 404, message: 'Media does not exist.' });
|
||||
}
|
||||
|
||||
try {
|
||||
const tautulli = new TautulliAPI(settings);
|
||||
const userRepository = getRepository(User);
|
||||
|
||||
const response: MediaWatchDataResponse = {};
|
||||
|
||||
if (media.ratingKey) {
|
||||
const watchStats = await tautulli.getMediaWatchStats(media.ratingKey);
|
||||
const watchUsers = await tautulli.getMediaWatchUsers(media.ratingKey);
|
||||
|
||||
const users = await userRepository
|
||||
.createQueryBuilder('user')
|
||||
.where('user.plexId IN (:...plexIds)', {
|
||||
plexIds: watchUsers.map((u) => u.user_id),
|
||||
})
|
||||
.getMany();
|
||||
|
||||
const playCount =
|
||||
watchStats.find((i) => i.query_days == 0)?.total_plays ?? 0;
|
||||
|
||||
const playCount7Days =
|
||||
watchStats.find((i) => i.query_days == 7)?.total_plays ?? 0;
|
||||
|
||||
const playCount30Days =
|
||||
watchStats.find((i) => i.query_days == 30)?.total_plays ?? 0;
|
||||
|
||||
response.data = {
|
||||
users: users,
|
||||
playCount,
|
||||
playCount7Days,
|
||||
playCount30Days,
|
||||
};
|
||||
}
|
||||
|
||||
if (media.ratingKey4k) {
|
||||
const watchStats4k = await tautulli.getMediaWatchStats(
|
||||
media.ratingKey4k
|
||||
);
|
||||
const watchUsers4k = await tautulli.getMediaWatchUsers(
|
||||
media.ratingKey4k
|
||||
);
|
||||
|
||||
const users = await userRepository
|
||||
.createQueryBuilder('user')
|
||||
.where('user.plexId IN (:...plexIds)', {
|
||||
plexIds: watchUsers4k.map((u) => u.user_id),
|
||||
})
|
||||
.getMany();
|
||||
|
||||
const playCount =
|
||||
watchStats4k.find((i) => i.query_days == 0)?.total_plays ?? 0;
|
||||
|
||||
const playCount7Days =
|
||||
watchStats4k.find((i) => i.query_days == 7)?.total_plays ?? 0;
|
||||
|
||||
const playCount30Days =
|
||||
watchStats4k.find((i) => i.query_days == 30)?.total_plays ?? 0;
|
||||
|
||||
response.data4k = {
|
||||
users,
|
||||
playCount,
|
||||
playCount7Days,
|
||||
playCount30Days,
|
||||
};
|
||||
}
|
||||
|
||||
return res.status(200).json(response);
|
||||
} catch (e) {
|
||||
logger.error('Something went wrong fetching media watch data', {
|
||||
label: 'API',
|
||||
errorMessage: e.message,
|
||||
mediaId: req.params.id,
|
||||
});
|
||||
next({ status: 500, message: 'Failed to fetch watch data.' });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default mediaRoutes;
|
||||
|
Reference in New Issue
Block a user