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/job/plexsync.ts

183 lines
5.4 KiB

import { getRepository } from 'typeorm';
import { User } from '../entity/User';
import PlexAPI, { PlexLibraryItem } from '../api/plexapi';
import TheMovieDb from '../api/themoviedb';
import Media from '../entity/Media';
import { MediaStatus, MediaType } from '../constants/media';
import logger from '../logger';
import { getSettings, Library } from '../lib/settings';
import { resolve } from 'dns';
const BUNDLE_SIZE = 10;
const imdbRegex = new RegExp(/imdb:\/\/(tt[0-9]+)/);
const tmdbRegex = new RegExp(/tmdb:\/\/([0-9]+)/);
const plexRegex = new RegExp(/plex:\/\//);
class JobPlexSync {
private tmdb: TheMovieDb;
private plexClient: PlexAPI;
private items: PlexLibraryItem[] = [];
private progress = 0;
private libraries: Library[];
private currentLibrary: Library;
private running = false;
constructor() {
this.tmdb = new TheMovieDb();
}
private async getExisting(tmdbId: number) {
const mediaRepository = getRepository(Media);
const existing = await mediaRepository.findOne({
where: { tmdbId: tmdbId },
});
return existing;
}
private async processMovie(plexitem: PlexLibraryItem) {
const mediaRepository = getRepository(Media);
if (plexitem.guid.match(plexRegex)) {
const metadata = await this.plexClient.getMetadata(plexitem.ratingKey);
const newMedia = new Media();
metadata.Guid.forEach((ref) => {
if (ref.id.match(imdbRegex)) {
newMedia.imdbId = ref.id.match(imdbRegex)?.[1] ?? undefined;
} else if (ref.id.match(tmdbRegex)) {
const tmdbMatch = ref.id.match(tmdbRegex)?.[1];
newMedia.tmdbId = Number(tmdbMatch);
}
});
const existing = await this.getExisting(newMedia.tmdbId);
if (existing && existing.status === MediaStatus.AVAILABLE) {
this.log(`Title exists and is already available ${metadata.title}`);
} else if (existing && existing.status !== MediaStatus.AVAILABLE) {
existing.status = MediaStatus.AVAILABLE;
mediaRepository.save(existing);
this.log(
`Request for ${metadata.title} exists. Setting status AVAILABLE`
);
} else {
newMedia.status = MediaStatus.AVAILABLE;
newMedia.mediaType = MediaType.MOVIE;
await mediaRepository.save(newMedia);
this.log(`Saved ${plexitem.title}`);
}
} else {
const matchedid = plexitem.guid.match(/imdb:\/\/(tt[0-9]+)/);
if (matchedid?.[1]) {
const tmdbMovie = await this.tmdb.getMovieByImdbId({
imdbId: matchedid[1],
});
const existing = await this.getExisting(tmdbMovie.id);
if (existing && existing.status === MediaStatus.AVAILABLE) {
this.log(`Title exists and is already available ${plexitem.title}`);
} else if (existing && existing.status !== MediaStatus.AVAILABLE) {
existing.status = MediaStatus.AVAILABLE;
await mediaRepository.save(existing);
this.log(
`Request for ${plexitem.title} exists. Setting status AVAILABLE`
);
} else if (tmdbMovie) {
const newMedia = new Media();
newMedia.imdbId = tmdbMovie.external_ids.imdb_id;
newMedia.tmdbId = tmdbMovie.id;
newMedia.status = MediaStatus.AVAILABLE;
newMedia.mediaType = MediaType.MOVIE;
await mediaRepository.save(newMedia);
this.log(`Saved ${tmdbMovie.title}`);
}
}
}
}
private async processItems(slicedItems: PlexLibraryItem[]) {
await Promise.all(
slicedItems.map(async (plexitem) => {
if (plexitem.type === 'movie') {
await this.processMovie(plexitem);
}
})
);
}
private async loop({
start = 0,
end = BUNDLE_SIZE,
}: {
start?: number;
end?: number;
} = {}) {
const slicedItems = this.items.slice(start, end);
if (start < this.items.length && this.running) {
this.progress = start;
await this.processItems(slicedItems);
await new Promise((resolve) =>
setTimeout(async () => {
await this.loop({
start: start + BUNDLE_SIZE,
end: end + BUNDLE_SIZE,
});
resolve();
}, 5000)
);
}
}
private log(message: string): void {
logger.info(message, { label: 'Plex Sync' });
}
public async run(): Promise<void> {
const settings = getSettings();
if (!this.running) {
this.running = true;
const userRepository = getRepository(User);
const admin = await userRepository.findOneOrFail({
select: ['id', 'plexToken'],
order: { id: 'ASC' },
});
this.plexClient = new PlexAPI({ plexToken: admin.plexToken });
this.libraries = settings.plex.libraries.filter(
(library) => library.enabled
);
for (const library of this.libraries) {
this.currentLibrary = library;
this.log(`Beginning to process library: ${library.name}`);
this.items = await this.plexClient.getLibraryContents(library.id);
await this.loop();
}
this.running = false;
this.log('complete');
}
}
public status() {
return {
running: this.running,
progress: this.progress,
total: this.items.length,
currentLibrary: this.currentLibrary,
libraries: this.libraries,
};
}
public cancel(): void {
this.running = false;
}
}
const jobPlexSync = new JobPlexSync();
export default jobPlexSync;