feat: plex watchlist sync integration (#2885)

This commit is contained in:
Ryan Cohen
2022-08-21 16:33:49 +09:00
committed by GitHub
parent 7943e0c339
commit 301f2bf7ab
35 changed files with 1325 additions and 320 deletions

View File

@@ -1,9 +1,9 @@
import type { AxiosInstance } from 'axios';
import axios from 'axios';
import xml2js from 'xml2js';
import type { PlexDevice } from '../interfaces/api/plexInterfaces';
import cacheManager from '../lib/cache';
import { getSettings } from '../lib/settings';
import logger from '../logger';
import ExternalAPI from './externalapi';
interface PlexAccountResponse {
user: PlexUser;
@@ -112,20 +112,54 @@ interface UsersResponse {
};
}
class PlexTvAPI {
interface WatchlistResponse {
MediaContainer: {
totalSize: number;
Metadata?: {
ratingKey: string;
}[];
};
}
interface MetadataResponse {
MediaContainer: {
Metadata: {
ratingKey: string;
type: 'movie' | 'show';
title: string;
Guid: {
id: `imdb://tt${number}` | `tmdb://${number}` | `tvdb://${number}`;
}[];
}[];
};
}
export interface PlexWatchlistItem {
ratingKey: string;
tmdbId: number;
tvdbId?: number;
type: 'movie' | 'show';
title: string;
}
class PlexTvAPI extends ExternalAPI {
private authToken: string;
private axios: AxiosInstance;
constructor(authToken: string) {
super(
'https://plex.tv',
{},
{
headers: {
'X-Plex-Token': authToken,
'Content-Type': 'application/json',
Accept: 'application/json',
},
nodeCache: cacheManager.getCache('plextv').data,
}
);
this.authToken = authToken;
this.axios = axios.create({
baseURL: 'https://plex.tv',
headers: {
'X-Plex-Token': this.authToken,
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
}
public async getDevices(): Promise<PlexDevice[]> {
@@ -253,6 +287,83 @@ class PlexTvAPI {
)) as UsersResponse;
return parsedXml;
}
public async getWatchlist({
offset = 0,
size = 20,
}: { offset?: number; size?: number } = {}): Promise<{
offset: number;
size: number;
totalSize: number;
items: PlexWatchlistItem[];
}> {
try {
const response = await this.axios.get<WatchlistResponse>(
'/library/sections/watchlist/all',
{
params: {
'X-Plex-Container-Start': offset,
'X-Plex-Container-Size': size,
},
baseURL: 'https://metadata.provider.plex.tv',
}
);
const watchlistDetails = await Promise.all(
(response.data.MediaContainer.Metadata ?? []).map(
async (watchlistItem) => {
const detailedResponse = await this.getRolling<MetadataResponse>(
`/library/metadata/${watchlistItem.ratingKey}`,
{
baseURL: 'https://metadata.provider.plex.tv',
}
);
const metadata = detailedResponse.MediaContainer.Metadata[0];
const tmdbString = metadata.Guid.find((guid) =>
guid.id.startsWith('tmdb')
);
const tvdbString = metadata.Guid.find((guid) =>
guid.id.startsWith('tvdb')
);
return {
ratingKey: metadata.ratingKey,
// This should always be set? But I guess it also cannot be?
// We will filter out the 0's afterwards
tmdbId: tmdbString ? Number(tmdbString.id.split('//')[1]) : 0,
tvdbId: tvdbString
? Number(tvdbString.id.split('//')[1])
: undefined,
title: metadata.title,
type: metadata.type,
};
}
)
);
const filteredList = watchlistDetails.filter((detail) => detail.tmdbId);
return {
offset,
size,
totalSize: response.data.MediaContainer.totalSize,
items: filteredList,
};
} catch (e) {
logger.error('Failed to retrieve watchlist items', {
label: 'Plex.TV Metadata API',
errorMessage: e.message,
});
return {
offset,
size,
totalSize: 0,
items: [],
};
}
}
}
export default PlexTvAPI;