mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
fix(tautulli): fetch additional user history as necessary to return 20 unique media (#2446)
* fix(tautulli): fetch additional user history as necessary to return 20 unique media * refactor: rename var for clarity * refactor: make single DB query for recently watched media * fix: resolve query builder weirdness * refactor: use find instead of qb * refactor: minor refactor * fix: also find 4K rating keys
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
|
import { uniqWith } from 'lodash';
|
||||||
import { User } from '../entity/User';
|
import { User } from '../entity/User';
|
||||||
import { TautulliSettings } from '../lib/settings';
|
import { TautulliSettings } from '../lib/settings';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
@@ -231,12 +232,18 @@ class TautulliAPI {
|
|||||||
public async getUserWatchHistory(
|
public async getUserWatchHistory(
|
||||||
user: User
|
user: User
|
||||||
): Promise<TautulliHistoryRecord[]> {
|
): Promise<TautulliHistoryRecord[]> {
|
||||||
|
let results: TautulliHistoryRecord[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!user.plexId) {
|
if (!user.plexId) {
|
||||||
throw new Error('User does not have an associated Plex ID');
|
throw new Error('User does not have an associated Plex ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const take = 100;
|
||||||
|
let start = 0;
|
||||||
|
|
||||||
|
while (results.length < 20) {
|
||||||
|
const tautulliData = (
|
||||||
await this.axios.get<TautulliHistoryResponse>('/api/v2', {
|
await this.axios.get<TautulliHistoryResponse>('/api/v2', {
|
||||||
params: {
|
params: {
|
||||||
cmd: 'get_history',
|
cmd: 'get_history',
|
||||||
@@ -244,10 +251,29 @@ class TautulliAPI {
|
|||||||
order_column: 'date',
|
order_column: 'date',
|
||||||
order_dir: 'desc',
|
order_dir: 'desc',
|
||||||
user_id: user.plexId,
|
user_id: user.plexId,
|
||||||
length: 100,
|
media_type: 'movie,episode',
|
||||||
|
length: take,
|
||||||
|
start,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
).data.response.data.data;
|
).data.response.data.data;
|
||||||
|
|
||||||
|
if (!tautulliData.length) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
results = uniqWith(results.concat(tautulliData), (recordA, recordB) =>
|
||||||
|
recordA.grandparent_rating_key && recordB.grandparent_rating_key
|
||||||
|
? recordA.grandparent_rating_key === recordB.grandparent_rating_key
|
||||||
|
: recordA.parent_rating_key && recordB.parent_rating_key
|
||||||
|
? recordA.parent_rating_key === recordB.parent_rating_key
|
||||||
|
: recordA.rating_key === recordB.rating_key
|
||||||
|
);
|
||||||
|
|
||||||
|
start += take;
|
||||||
|
}
|
||||||
|
|
||||||
|
return results.slice(0, 20);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(
|
logger.error(
|
||||||
'Something went wrong fetching user watch history from Tautulli',
|
'Something went wrong fetching user watch history from Tautulli',
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import gravatarUrl from 'gravatar-url';
|
import gravatarUrl from 'gravatar-url';
|
||||||
import { uniqWith } from 'lodash';
|
import { findIndex, sortBy } from 'lodash';
|
||||||
import { getRepository, Not } from 'typeorm';
|
import { getRepository, In, Not } from 'typeorm';
|
||||||
import PlexTvAPI from '../../api/plextv';
|
import PlexTvAPI from '../../api/plextv';
|
||||||
import TautulliAPI from '../../api/tautulli';
|
import TautulliAPI from '../../api/tautulli';
|
||||||
|
import { MediaType } from '../../constants/media';
|
||||||
import { UserType } from '../../constants/user';
|
import { UserType } from '../../constants/user';
|
||||||
import Media from '../../entity/Media';
|
import Media from '../../entity/Media';
|
||||||
import { MediaRequest } from '../../entity/MediaRequest';
|
import { MediaRequest } from '../../entity/MediaRequest';
|
||||||
@@ -521,7 +522,6 @@ router.get<{ id: string }, UserWatchDataResponse>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const mediaRepository = getRepository(Media);
|
|
||||||
const user = await getRepository(User).findOneOrFail({
|
const user = await getRepository(User).findOneOrFail({
|
||||||
where: { id: Number(req.params.id) },
|
where: { id: Number(req.params.id) },
|
||||||
select: ['id', 'plexId'],
|
select: ['id', 'plexId'],
|
||||||
@@ -532,33 +532,64 @@ router.get<{ id: string }, UserWatchDataResponse>(
|
|||||||
const watchStats = await tautulli.getUserWatchStats(user);
|
const watchStats = await tautulli.getUserWatchStats(user);
|
||||||
const watchHistory = await tautulli.getUserWatchHistory(user);
|
const watchHistory = await tautulli.getUserWatchHistory(user);
|
||||||
|
|
||||||
const media = (
|
const recentlyWatched = sortBy(
|
||||||
await Promise.all(
|
await getRepository(Media).find({
|
||||||
uniqWith(watchHistory, (recordA, recordB) =>
|
where: [
|
||||||
recordA.grandparent_rating_key && recordB.grandparent_rating_key
|
{
|
||||||
? recordA.grandparent_rating_key ===
|
mediaType: MediaType.MOVIE,
|
||||||
recordB.grandparent_rating_key
|
ratingKey: In(
|
||||||
: recordA.parent_rating_key && recordB.parent_rating_key
|
watchHistory
|
||||||
? recordA.parent_rating_key === recordB.parent_rating_key
|
.filter((record) => record.media_type === 'movie')
|
||||||
: recordA.rating_key === recordB.rating_key
|
.map((record) => record.rating_key)
|
||||||
)
|
),
|
||||||
.slice(0, 20)
|
|
||||||
.map(
|
|
||||||
async (record) =>
|
|
||||||
await mediaRepository.findOne({
|
|
||||||
where: {
|
|
||||||
ratingKey:
|
|
||||||
record.media_type === 'movie'
|
|
||||||
? record.rating_key
|
|
||||||
: record.grandparent_rating_key,
|
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
)
|
mediaType: MediaType.MOVIE,
|
||||||
)
|
ratingKey4k: In(
|
||||||
).filter((media) => !!media) as Media[];
|
watchHistory
|
||||||
|
.filter((record) => record.media_type === 'movie')
|
||||||
|
.map((record) => record.rating_key)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mediaType: MediaType.TV,
|
||||||
|
ratingKey: In(
|
||||||
|
watchHistory
|
||||||
|
.filter((record) => record.media_type === 'episode')
|
||||||
|
.map((record) => record.grandparent_rating_key)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
mediaType: MediaType.TV,
|
||||||
|
ratingKey4k: In(
|
||||||
|
watchHistory
|
||||||
|
.filter((record) => record.media_type === 'episode')
|
||||||
|
.map((record) => record.grandparent_rating_key)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
(media) =>
|
||||||
|
findIndex(
|
||||||
|
watchHistory,
|
||||||
|
(record) =>
|
||||||
|
(!!media.ratingKey &&
|
||||||
|
parseInt(media.ratingKey) ===
|
||||||
|
(record.media_type === 'movie'
|
||||||
|
? record.rating_key
|
||||||
|
: record.grandparent_rating_key)) ||
|
||||||
|
(!!media.ratingKey4k &&
|
||||||
|
parseInt(media.ratingKey4k) ===
|
||||||
|
(record.media_type === 'movie'
|
||||||
|
? record.rating_key
|
||||||
|
: record.grandparent_rating_key))
|
||||||
|
),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
return res.status(200).json({
|
return res.status(200).json({
|
||||||
recentlyWatched: media,
|
recentlyWatched,
|
||||||
playCount: watchStats.total_plays,
|
playCount: watchStats.total_plays,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
Reference in New Issue
Block a user