From 7d19de6a4af6297be18140ca59402b40f7bbb30b Mon Sep 17 00:00:00 2001 From: TheCatLady <52870424+TheCatLady@users.noreply.github.com> Date: Mon, 14 Mar 2022 13:31:11 -0400 Subject: [PATCH] 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 --- server/api/tautulli.ts | 50 ++++++++++++++++----- server/routes/user/index.ts | 87 +++++++++++++++++++++++++------------ 2 files changed, 97 insertions(+), 40 deletions(-) diff --git a/server/api/tautulli.ts b/server/api/tautulli.ts index 96caee39..bb7f3723 100644 --- a/server/api/tautulli.ts +++ b/server/api/tautulli.ts @@ -1,4 +1,5 @@ import axios, { AxiosInstance } from 'axios'; +import { uniqWith } from 'lodash'; import { User } from '../entity/User'; import { TautulliSettings } from '../lib/settings'; import logger from '../logger'; @@ -231,23 +232,48 @@ class TautulliAPI { public async getUserWatchHistory( user: User ): Promise { + let results: TautulliHistoryRecord[] = []; + try { if (!user.plexId) { throw new Error('User does not have an associated Plex ID'); } - return ( - await this.axios.get('/api/v2', { - params: { - cmd: 'get_history', - grouping: 1, - order_column: 'date', - order_dir: 'desc', - user_id: user.plexId, - length: 100, - }, - }) - ).data.response.data.data; + const take = 100; + let start = 0; + + while (results.length < 20) { + const tautulliData = ( + await this.axios.get('/api/v2', { + params: { + cmd: 'get_history', + grouping: 1, + order_column: 'date', + order_dir: 'desc', + user_id: user.plexId, + media_type: 'movie,episode', + length: take, + start, + }, + }) + ).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) { logger.error( 'Something went wrong fetching user watch history from Tautulli', diff --git a/server/routes/user/index.ts b/server/routes/user/index.ts index 9daa446a..a4e8861e 100644 --- a/server/routes/user/index.ts +++ b/server/routes/user/index.ts @@ -1,9 +1,10 @@ import { Router } from 'express'; import gravatarUrl from 'gravatar-url'; -import { uniqWith } from 'lodash'; -import { getRepository, Not } from 'typeorm'; +import { findIndex, sortBy } from 'lodash'; +import { getRepository, In, Not } from 'typeorm'; import PlexTvAPI from '../../api/plextv'; import TautulliAPI from '../../api/tautulli'; +import { MediaType } from '../../constants/media'; import { UserType } from '../../constants/user'; import Media from '../../entity/Media'; import { MediaRequest } from '../../entity/MediaRequest'; @@ -521,7 +522,6 @@ router.get<{ id: string }, UserWatchDataResponse>( } try { - const mediaRepository = getRepository(Media); const user = await getRepository(User).findOneOrFail({ where: { id: Number(req.params.id) }, select: ['id', 'plexId'], @@ -532,33 +532,64 @@ router.get<{ id: string }, UserWatchDataResponse>( const watchStats = await tautulli.getUserWatchStats(user); const watchHistory = await tautulli.getUserWatchHistory(user); - const media = ( - await Promise.all( - uniqWith(watchHistory, (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 - ) - .slice(0, 20) - .map( - async (record) => - await mediaRepository.findOne({ - where: { - ratingKey: - record.media_type === 'movie' - ? record.rating_key - : record.grandparent_rating_key, - }, - }) - ) - ) - ).filter((media) => !!media) as Media[]; + const recentlyWatched = sortBy( + await getRepository(Media).find({ + where: [ + { + mediaType: MediaType.MOVIE, + ratingKey: In( + watchHistory + .filter((record) => record.media_type === 'movie') + .map((record) => record.rating_key) + ), + }, + { + mediaType: MediaType.MOVIE, + ratingKey4k: In( + 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({ - recentlyWatched: media, + recentlyWatched, playCount: watchStats.total_plays, }); } catch (e) {