diff --git a/server/api/radarr.ts b/server/api/radarr.ts new file mode 100644 index 00000000..dbebe6f3 --- /dev/null +++ b/server/api/radarr.ts @@ -0,0 +1,122 @@ +import Axios, { AxiosInstance } from 'axios'; + +interface RadarrMovieOptions { + title: string; + qualityProfileId: number; + profileId: number; + year: number; + rootFolderPath: string; + tmdbId: number; + monitored?: boolean; + searchNow?: boolean; +} + +interface RadarrMovie { + id: number; + title: string; + isAvailable: boolean; + monitored: boolean; + tmdbId: number; + titleSlug: string; + folderName: string; + path: string; + profileId: number; + qualityProfileId: number; + added: string; + downloaded: boolean; + hasFile: boolean; +} + +interface RadarrRootFolder { + id: number; + path: string; + freeSpace: number; + totalSpace: number; + unmappedFolders: { + name: string; + path: string; + }[]; +} + +interface RadarrProfile { + id: number; + name: string; +} + +class RadarrAPI { + private axios: AxiosInstance; + constructor({ url, apiKey }: { url: string; apiKey: string }) { + this.axios = Axios.create({ + baseURL: url, + params: { + apikey: apiKey, + }, + }); + } + + public getMovies = async (): Promise => { + try { + const response = await this.axios.get('/movie'); + + return response.data; + } catch (e) { + throw new Error(`[Radarr] Failed to retrieve movies: ${e.message}`); + } + }; + + public getMovie = async ({ id }: { id: number }): Promise => { + try { + const response = await this.axios.get(`/movie/${id}`); + + return response.data; + } catch (e) { + throw new Error(`[Radarr] Failed to retrieve movie: ${e.message}`); + } + }; + + public addMovie = async ( + options: RadarrMovieOptions + ): Promise => { + try { + const response = await this.axios.post(`/movie`, { + title: options.title, + qualityProfileId: options.qualityProfileId, + profileId: options.profileId, + titleSlug: options.tmdbId.toString(), + tmdbId: options.tmdbId, + year: options.year, + rootFolderPath: options.rootFolderPath, + monitored: options.monitored, + addOptions: { + searchForMovie: options.searchNow, + }, + }); + + return response.data; + } catch (e) { + throw new Error(`[Radarr] Failed to add movie: ${e.message}`); + } + }; + + public getProfiles = async (): Promise => { + try { + const response = await this.axios.get(`/profile`); + + return response.data; + } catch (e) { + throw new Error(`[Radarr] Failed to retrieve profiles: ${e.message}`); + } + }; + + public getRootFolders = async (): Promise => { + try { + const response = await this.axios.get(`/rootfolder`); + + return response.data; + } catch (e) { + throw new Error(`[Radarr] Failed to retrieve root folders: ${e.message}`); + } + }; +} + +export default RadarrAPI; diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index d086249d..bf1c537e 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -8,8 +8,13 @@ import { getRepository, In, Index, + AfterUpdate, + AfterInsert, } from 'typeorm'; import { User } from './User'; +import RadarrAPI from '../api/radarr'; +import { getSettings } from '../lib/settings'; +import TheMovieDb from '../api/themoviedb'; export enum MediaRequestStatus { PENDING = 1, @@ -60,7 +65,6 @@ export class MediaRequest { @ManyToOne(() => User, { nullable: true }) public modifiedBy?: User; - @CreateDateColumn() public createdAt: Date; @@ -70,4 +74,49 @@ export class MediaRequest { constructor(init?: Partial) { Object.assign(this, init); } + + @AfterUpdate() + @AfterInsert() + private async sendToRadarr() { + if ( + this.mediaType === 'movie' && + this.status === MediaRequestStatus.APPROVED + ) { + try { + const settings = getSettings(); + if (settings.radarr.length === 0 && !settings.radarr[0]) { + console.log( + '[MediaRequest] Skipped radarr request as there is no radarr configured' + ); + return; + } + + const tmdb = new TheMovieDb(); + const radarrSettings = settings.radarr[0]; + const radarr = new RadarrAPI({ + apiKey: radarrSettings.apiKey, + url: `${radarrSettings.useSsl ? 'https' : 'http'}://${ + radarrSettings.hostname + }:${radarrSettings.port}/api`, + }); + const movie = await tmdb.getMovie({ movieId: this.mediaId }); + + await radarr.addMovie({ + profileId: radarrSettings.activeProfileId, + qualityProfileId: radarrSettings.activeProfileId, + rootFolderPath: radarrSettings.activeDirectory, + title: movie.title, + tmdbId: movie.id, + year: Number(movie.release_date.slice(0, 4)), + monitored: true, + searchNow: true, + }); + console.log('[MediaRequest] Sent request to Radarr'); + } catch (e) { + throw new Error( + `[MediaRequest] Request failed to send to radarr: ${e.message}` + ); + } + } + } } diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 31dd1dad..7625be2c 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -23,7 +23,7 @@ interface DVRSettings { apiKey: string; useSsl: boolean; baseUrl?: string; - activeProfile: string; + activeProfileId: number; activeDirectory: string; is4k: boolean; } diff --git a/server/overseerr-api.yml b/server/overseerr-api.yml index 587e6a68..8f1c0d08 100644 --- a/server/overseerr-api.yml +++ b/server/overseerr-api.yml @@ -110,9 +110,9 @@ components: example: false baseUrl: type: string - activeProfile: - type: string - example: '1080p' + activeProfileId: + type: number + example: 1 activeDirectory: type: string example: '/movies'