mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2025-12-24 02:39:18 -05:00
This PR fixes the "Delete from *arr" button in the request list. It checks from the API whether the *arr server corresponding to the request still exists before displaying the remove button, and fixes a cache removal issue that could cause problems when deleting recently added media. This PR also reverts #1476, which introduced problems during removal. fix #1494
289 lines
7.4 KiB
TypeScript
289 lines
7.4 KiB
TypeScript
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;
|
|
tags: number[];
|
|
movieFile?: {
|
|
id: number;
|
|
movieId: number;
|
|
relativePath?: string;
|
|
path?: string;
|
|
size: number;
|
|
dateAdded: string;
|
|
sceneName?: string;
|
|
releaseGroup?: string;
|
|
edition?: string;
|
|
indexerFlags?: number;
|
|
mediaInfo: {
|
|
id: number;
|
|
audioBitrate: number;
|
|
audioChannels: number;
|
|
audioCodec?: string;
|
|
audioLanguages?: string;
|
|
audioStreamCount: number;
|
|
videoBitDepth: number;
|
|
videoBitrate: number;
|
|
videoCodec?: string;
|
|
videoFps: number;
|
|
videoDynamicRange?: string;
|
|
videoDynamicRangeType?: string;
|
|
resolution?: string;
|
|
runTime?: string;
|
|
scanType?: string;
|
|
subtitles?: string;
|
|
};
|
|
originalFilePath?: string;
|
|
qualityCutoffNotMet: 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<RadarrMovie[]> => {
|
|
try {
|
|
const data = await this.get<RadarrMovie[]>('/movie');
|
|
|
|
return data;
|
|
} catch (e) {
|
|
throw new Error(`[Radarr] Failed to retrieve movies: ${e.message}`);
|
|
}
|
|
};
|
|
|
|
public getMovie = async ({ id }: { id: number }): Promise<RadarrMovie> => {
|
|
try {
|
|
const data = await this.get<RadarrMovie>(`/movie/${id}`);
|
|
|
|
return data;
|
|
} catch (e) {
|
|
throw new Error(`[Radarr] Failed to retrieve movie: ${e.message}`);
|
|
}
|
|
};
|
|
|
|
public async getMovieByTmdbId(id: number): Promise<RadarrMovie> {
|
|
try {
|
|
const data = await this.get<RadarrMovie[]>('/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<RadarrMovie> => {
|
|
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<RadarrMovie>(`/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: Array.from(new Set([...movie.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<RadarrMovie>(`/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<void> {
|
|
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<void> => {
|
|
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}`);
|
|
}
|
|
};
|
|
|
|
public clearCache = ({
|
|
tmdbId,
|
|
externalId,
|
|
}: {
|
|
tmdbId?: number | null;
|
|
externalId?: number | null;
|
|
}) => {
|
|
if (tmdbId) {
|
|
this.removeCache('/movie/lookup', {
|
|
term: `tmdb:${tmdbId}`,
|
|
headers: this.defaultHeaders,
|
|
});
|
|
}
|
|
if (externalId) {
|
|
this.removeCache(`/movie/${externalId}`, {
|
|
headers: this.defaultHeaders,
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
export default RadarrAPI;
|