You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
overseerr/server/api/tautulli.ts

295 lines
7.1 KiB

import type { User } from '@server/entity/User';
import type { TautulliSettings } from '@server/lib/settings';
import logger from '@server/logger';
import type { AxiosInstance } from 'axios';
import axios from 'axios';
import { uniqWith } from 'lodash';
export interface TautulliHistoryRecord {
date: number;
duration: number;
friendly_name: string;
full_title: string;
grandparent_rating_key: number;
grandparent_title: string;
original_title: string;
group_count: number;
group_ids?: string;
guid: string;
ip_address: string;
live: number;
machine_id: string;
media_index: number;
media_type: string;
originally_available_at: string;
parent_media_index: number;
parent_rating_key: number;
parent_title: string;
paused_counter: number;
percent_complete: number;
platform: string;
product: string;
player: string;
rating_key: number;
reference_id?: number;
row_id?: number;
session_key?: string;
started: number;
state?: string;
stopped: number;
thumb: string;
title: string;
transcode_decision: string;
user: string;
user_id: number;
watched_status: number;
year: number;
}
interface TautulliHistoryResponse {
response: {
result: string;
message?: string;
data: {
draw: number;
recordsTotal: number;
recordsFiltered: number;
total_duration: string;
filter_duration: string;
data: TautulliHistoryRecord[];
};
};
}
interface TautulliWatchStats {
query_days: number;
total_time: number;
total_plays: number;
}
interface TautulliWatchStatsResponse {
response: {
result: string;
message?: string;
data: TautulliWatchStats[];
};
}
interface TautulliWatchUser {
friendly_name: string;
user_id: number;
user_thumb: string;
username: string;
total_plays: number;
total_time: number;
}
interface TautulliWatchUsersResponse {
response: {
result: string;
message?: string;
data: TautulliWatchUser[];
};
}
interface TautulliInfo {
tautulli_install_type: string;
tautulli_version: string;
tautulli_branch: string;
tautulli_commit: string;
tautulli_platform: string;
tautulli_platform_release: string;
tautulli_platform_version: string;
tautulli_platform_linux_distro: string;
tautulli_platform_device_name: string;
tautulli_python_version: string;
}
interface TautulliInfoResponse {
response: {
result: string;
message?: string;
data: TautulliInfo;
};
}
class TautulliAPI {
private axios: AxiosInstance;
constructor(settings: TautulliSettings) {
this.axios = axios.create({
baseURL: `${settings.useSsl ? 'https' : 'http'}://${settings.hostname}:${
settings.port
}${settings.urlBase ?? ''}`,
params: { apikey: settings.apiKey },
});
}
public async getInfo(): Promise<TautulliInfo> {
try {
return (
await this.axios.get<TautulliInfoResponse>('/api/v2', {
params: { cmd: 'get_tautulli_info' },
})
).data.response.data;
} catch (e) {
logger.error('Something went wrong fetching Tautulli server info', {
label: 'Tautulli API',
errorMessage: e.message,
});
throw new Error(
`[Tautulli] Failed to fetch Tautulli server info: ${e.message}`
);
}
}
public async getMediaWatchStats(
ratingKey: string
): Promise<TautulliWatchStats[]> {
try {
return (
await this.axios.get<TautulliWatchStatsResponse>('/api/v2', {
params: {
cmd: 'get_item_watch_time_stats',
rating_key: ratingKey,
grouping: 1,
},
})
).data.response.data;
} catch (e) {
logger.error(
'Something went wrong fetching media watch stats from Tautulli',
{
label: 'Tautulli API',
errorMessage: e.message,
ratingKey,
}
);
throw new Error(
`[Tautulli] Failed to fetch media watch stats: ${e.message}`
);
}
}
public async getMediaWatchUsers(
ratingKey: string
): Promise<TautulliWatchUser[]> {
try {
return (
await this.axios.get<TautulliWatchUsersResponse>('/api/v2', {
params: {
cmd: 'get_item_user_stats',
rating_key: ratingKey,
grouping: 1,
},
})
).data.response.data;
} catch (e) {
logger.error(
'Something went wrong fetching media watch users from Tautulli',
{
label: 'Tautulli API',
errorMessage: e.message,
ratingKey,
}
);
throw new Error(
`[Tautulli] Failed to fetch media watch users: ${e.message}`
);
}
}
public async getUserWatchStats(user: User): Promise<TautulliWatchStats> {
try {
if (!user.plexId) {
throw new Error('User does not have an associated Plex ID');
}
return (
await this.axios.get<TautulliWatchStatsResponse>('/api/v2', {
params: {
cmd: 'get_user_watch_time_stats',
user_id: user.plexId,
query_days: 0,
grouping: 1,
},
})
).data.response.data[0];
} catch (e) {
logger.error(
'Something went wrong fetching user watch stats from Tautulli',
{
label: 'Tautulli API',
errorMessage: e.message,
user: user.displayName,
}
);
throw new Error(
`[Tautulli] Failed to fetch user watch stats: ${e.message}`
);
}
}
public async getUserWatchHistory(
user: User
): Promise<TautulliHistoryRecord[]> {
let results: TautulliHistoryRecord[] = [];
try {
if (!user.plexId) {
throw new Error('User does not have an associated Plex ID');
}
const take = 100;
let start = 0;
while (results.length < 20) {
const tautulliData = (
await this.axios.get<TautulliHistoryResponse>('/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',
{
label: 'Tautulli API',
errorMessage: e.message,
user: user.displayName,
}
);
throw new Error(
`[Tautulli] Failed to fetch user watch history: ${e.message}`
);
}
}
}
export default TautulliAPI;