import logger from '@server/logger'; import ServarrBase from './base'; export interface RadarrMovieOptions { title: string; qualityProfileId: number; minimumAvailability: string; tags: number[]; profileId: number; year: number; rootFolderPath: string; tmdbId: number; monitored?: boolean; searchNow?: boolean; } export interface RadarrMovie { id: number; title: string; isAvailable: boolean; monitored: boolean; tmdbId: number; imdbId: string; titleSlug: string; folderName: string; path: string; profileId: number; qualityProfileId: number; added: string; hasFile: boolean; } class RadarrAPI extends ServarrBase<{ movieId: number }> { constructor({ url, apiKey }: { url: string; apiKey: string }) { super({ url, apiKey, cacheName: 'radarr', apiName: 'Radarr' }); } public getMovies = async (): Promise => { try { const data = await this.get('/movie'); return data; } catch (e) { throw new Error(`[Radarr] Failed to retrieve movies: ${e.message}`); } }; public getMovie = async ({ id }: { id: number }): Promise => { try { const data = await this.get(`/movie/${id}`); return data; } catch (e) { throw new Error(`[Radarr] Failed to retrieve movie: ${e.message}`); } }; public async getMovieByTmdbId(id: number): Promise { try { const data = await this.get('/movie/lookup', { term: `tmdb:${id}`, }); if (!data[0]) { throw new Error('Movie not found'); } return data[0]; } catch (e) { logger.error('Error retrieving movie by TMDB ID', { label: 'Radarr API', errorMessage: e.message, tmdbId: id, }); throw new Error('Movie not found'); } } public addMovie = async ( options: RadarrMovieOptions ): Promise => { try { const movie = await this.getMovieByTmdbId(options.tmdbId); if (movie.hasFile) { logger.info( 'Title already exists and is available. Skipping add and returning success', { label: 'Radarr', movie, } ); return movie; } // movie exists in Radarr but is neither downloaded nor monitored if (movie.id && !movie.monitored) { const data = await this.put(`/movie`, { ...movie, title: options.title, qualityProfileId: options.qualityProfileId, profileId: options.profileId, titleSlug: options.tmdbId.toString(), minimumAvailability: options.minimumAvailability, tmdbId: options.tmdbId, year: options.year, tags: options.tags, rootFolderPath: options.rootFolderPath, monitored: options.monitored, addOptions: { searchForMovie: options.searchNow, }, }); if (data.monitored) { logger.info( 'Found existing title in Radarr and set it to monitored.', { label: 'Radarr', movieId: data.id, movieTitle: data.title, } ); logger.debug('Radarr update details', { label: 'Radarr', movie: data, }); if (options.searchNow) { this.searchMovie(data.id); } return data; } else { logger.error('Failed to update existing movie in Radarr.', { label: 'Radarr', options, }); throw new Error('Failed to update existing movie in Radarr'); } } if (movie.id) { logger.info( 'Movie is already monitored in Radarr. Skipping add and returning success', { label: 'Radarr' } ); return movie; } const data = await this.post(`/movie`, { title: options.title, qualityProfileId: options.qualityProfileId, profileId: options.profileId, titleSlug: options.tmdbId.toString(), minimumAvailability: options.minimumAvailability, tmdbId: options.tmdbId, year: options.year, rootFolderPath: options.rootFolderPath, monitored: options.monitored, tags: options.tags, addOptions: { searchForMovie: options.searchNow, }, }); if (data.id) { logger.info('Radarr accepted request', { label: 'Radarr' }); logger.debug('Radarr add details', { label: 'Radarr', movie: data, }); } else { logger.error('Failed to add movie to Radarr', { label: 'Radarr', options, }); throw new Error('Failed to add movie to Radarr'); } return data; } catch (e) { let errorData; try { errorData = await e.cause?.text(); errorData = JSON.parse(errorData); } catch { /* empty */ } logger.error( 'Failed to add movie to Radarr. This might happen if the movie already exists, in which case you can safely ignore this error.', { label: 'Radarr', errorMessage: e.message, options, response: errorData, } ); throw new Error('Failed to add movie to Radarr'); } }; public async searchMovie(movieId: number): Promise { logger.info('Executing movie search command', { label: 'Radarr API', movieId, }); try { await this.runCommand('MoviesSearch', { movieIds: [movieId] }); } catch (e) { logger.error( 'Something went wrong while executing Radarr movie search.', { label: 'Radarr API', errorMessage: e.message, movieId, } ); } } public removeMovie = async (movieId: number): Promise => { try { const { id, title } = await this.getMovieByTmdbId(movieId); await this.delete(`/movie/${id}`, { deleteFiles: 'true', addImportExclusion: 'false', }); logger.info(`[Radarr] Removed movie ${title}`); } catch (e) { throw new Error(`[Radarr] Failed to remove movie: ${e.message}`); } }; } export default RadarrAPI;