mirror of
https://github.com/fallenbagel/jellyseerr.git
synced 2026-01-01 04:08:45 -05:00
feat: add a button in ManageSlideOver to remove the movie and the file from Radarr/Sonarr
This commit is contained in:
@@ -5362,6 +5362,23 @@ paths:
|
||||
responses:
|
||||
'204':
|
||||
description: Succesfully removed media item
|
||||
/media/{mediaId}/file:
|
||||
delete:
|
||||
summary: Delete media file
|
||||
description: Removes a media file from radarr/sonarr. The `ADMIN` permission is required to perform this action.
|
||||
tags:
|
||||
- media
|
||||
parameters:
|
||||
- in: path
|
||||
name: mediaId
|
||||
description: Media ID
|
||||
required: true
|
||||
example: '1'
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: Succesfully removed media item
|
||||
/media/{mediaId}/{status}:
|
||||
post:
|
||||
summary: Update media status
|
||||
|
||||
@@ -213,6 +213,20 @@ class RadarrAPI extends ServarrBase<{ movieId: number }> {
|
||||
);
|
||||
}
|
||||
}
|
||||
public removeMovie = async (movieId: number): Promise<void> => {
|
||||
try {
|
||||
const { id, title } = await this.getMovieByTmdbId(movieId);
|
||||
await this.axios.delete(`/movie/${id}`, {
|
||||
params: {
|
||||
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;
|
||||
|
||||
@@ -302,6 +302,20 @@ class SonarrAPI extends ServarrBase<{ seriesId: number; episodeId: number }> {
|
||||
|
||||
return newSeasons;
|
||||
}
|
||||
public removeSerie = async (serieId: number): Promise<void> => {
|
||||
try {
|
||||
const { id, title } = await this.getSeriesByTvdbId(serieId);
|
||||
await this.axios.delete(`/series/${id}`, {
|
||||
params: {
|
||||
deleteFiles: true,
|
||||
addImportExclusion: false,
|
||||
},
|
||||
});
|
||||
logger.info(`[Radarr] Removed serie ${title}`);
|
||||
} catch (e) {
|
||||
throw new Error(`[Radarr] Failed to remove serie: ${e.message}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default SonarrAPI;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Router } from 'express';
|
||||
import { FindOneOptions, FindOperator, getRepository, In } from 'typeorm';
|
||||
import RadarrAPI from '../api/servarr/radarr';
|
||||
import SonarrAPI from '../api/servarr/sonarr';
|
||||
import TautulliAPI from '../api/tautulli';
|
||||
import TheMovieDb from '../api/themoviedb';
|
||||
import { MediaStatus, MediaType } from '../constants/media';
|
||||
import Media from '../entity/Media';
|
||||
import { User } from '../entity/User';
|
||||
@@ -167,6 +170,100 @@ mediaRoutes.delete(
|
||||
}
|
||||
);
|
||||
|
||||
mediaRoutes.delete(
|
||||
'/:id/file',
|
||||
isAuthenticated(Permission.MANAGE_REQUESTS),
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const settings = getSettings();
|
||||
const mediaRepository = getRepository(Media);
|
||||
const media = await mediaRepository.findOneOrFail({
|
||||
where: { id: req.params.id },
|
||||
});
|
||||
const is4k = media.serviceUrl4k !== undefined;
|
||||
const isMovie = media.mediaType === MediaType.MOVIE;
|
||||
let serviceSettings;
|
||||
if (isMovie) {
|
||||
serviceSettings = settings.radarr.find(
|
||||
(radarr) => radarr.isDefault && radarr.is4k === is4k
|
||||
);
|
||||
} else {
|
||||
serviceSettings = settings.sonarr.find(
|
||||
(sonarr) => sonarr.isDefault && sonarr.is4k === is4k
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
media.serviceId &&
|
||||
media.serviceId >= 0 &&
|
||||
serviceSettings?.id !== media.serviceId
|
||||
) {
|
||||
if (isMovie) {
|
||||
serviceSettings = settings.radarr.find(
|
||||
(radarr) => radarr.id === media.serviceId
|
||||
);
|
||||
} else {
|
||||
serviceSettings = settings.sonarr.find(
|
||||
(sonarr) => sonarr.id === media.serviceId
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!serviceSettings) {
|
||||
logger.warn(
|
||||
`There is no default ${
|
||||
is4k ? '4K ' : '' + isMovie ? 'Radarr' : 'Sonarr'
|
||||
}/ server configured. Did you set any of your ${
|
||||
is4k ? '4K ' : '' + isMovie ? 'Radarr' : 'Sonarr'
|
||||
} servers as default?`,
|
||||
{
|
||||
label: 'Media Request',
|
||||
mediaId: media.id,
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
let service;
|
||||
if (isMovie) {
|
||||
service = new RadarrAPI({
|
||||
apiKey: serviceSettings?.apiKey,
|
||||
url: RadarrAPI.buildUrl(serviceSettings, '/api/v3'),
|
||||
});
|
||||
} else {
|
||||
service = new SonarrAPI({
|
||||
apiKey: serviceSettings?.apiKey,
|
||||
url: SonarrAPI.buildUrl(serviceSettings, '/api/v3'),
|
||||
});
|
||||
}
|
||||
|
||||
if (isMovie) {
|
||||
await (service as RadarrAPI).removeMovie(
|
||||
parseInt(
|
||||
is4k
|
||||
? (media.externalServiceSlug4k as string)
|
||||
: (media.externalServiceSlug as string)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
const tmdb = new TheMovieDb();
|
||||
const series = await tmdb.getTvShow({ tvId: media.tmdbId });
|
||||
const tvdbId = series.external_ids.tvdb_id ?? media.tvdbId;
|
||||
if (!tvdbId) {
|
||||
throw new Error('TVDB ID not found');
|
||||
}
|
||||
await (service as SonarrAPI).removeSerie(tvdbId);
|
||||
}
|
||||
|
||||
return res.status(204).send();
|
||||
} catch (e) {
|
||||
logger.error('Something went wrong fetching media in delete request', {
|
||||
label: 'Media',
|
||||
message: e.message,
|
||||
});
|
||||
next({ status: 404, message: 'Media not found' });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
mediaRoutes.get<{ id: string }, MediaWatchDataResponse>(
|
||||
'/:id/watch_data',
|
||||
isAuthenticated(Permission.ADMIN),
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { ServerIcon, ViewListIcon } from '@heroicons/react/outline';
|
||||
import { CheckCircleIcon, DocumentRemoveIcon } from '@heroicons/react/solid';
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
DocumentRemoveIcon,
|
||||
TrashIcon,
|
||||
} from '@heroicons/react/solid';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
@@ -34,8 +38,12 @@ const messages = defineMessages({
|
||||
manageModalClearMedia: 'Clear Data',
|
||||
manageModalClearMediaWarning:
|
||||
'* This will irreversibly remove all data for this {mediaType}, including any requests. If this item exists in your Plex library, the media information will be recreated during the next scan.',
|
||||
manageModalRemoveMediaWarning:
|
||||
'* This will irreversibly remove this {mediaType} from {arr}, including all files.',
|
||||
openarr: 'Open in {arr}',
|
||||
removearr: 'Remove from {arr}',
|
||||
openarr4k: 'Open in 4K {arr}',
|
||||
removearr4k: 'Remove from 4K {arr}',
|
||||
downloadstatus: 'Downloads',
|
||||
markavailable: 'Mark as Available',
|
||||
mark4kavailable: 'Mark as Available in 4K',
|
||||
@@ -90,6 +98,13 @@ const ManageSlideOver: React.FC<
|
||||
revalidate();
|
||||
}
|
||||
};
|
||||
const deleteMediaFile = async () => {
|
||||
if (data.mediaInfo) {
|
||||
await axios.delete(`/api/v1/media/${data.mediaInfo.id}/file`);
|
||||
await axios.delete(`/api/v1/media/${data.mediaInfo.id}`);
|
||||
revalidate();
|
||||
}
|
||||
};
|
||||
|
||||
const markAvailable = async (is4k = false) => {
|
||||
if (data.mediaInfo) {
|
||||
@@ -319,6 +334,39 @@ const ManageSlideOver: React.FC<
|
||||
</Button>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{hasPermission(Permission.ADMIN) &&
|
||||
data?.mediaInfo?.serviceUrl && (
|
||||
<div>
|
||||
<ConfirmButton
|
||||
onClick={() => deleteMediaFile()}
|
||||
confirmText={intl.formatMessage(
|
||||
globalMessages.areyousure
|
||||
)}
|
||||
className="w-full"
|
||||
>
|
||||
<TrashIcon />
|
||||
<span>
|
||||
{intl.formatMessage(messages.removearr, {
|
||||
arr: mediaType === 'movie' ? 'Radarr' : 'Sonarr',
|
||||
})}
|
||||
</span>
|
||||
</ConfirmButton>
|
||||
<div className="mt-1 text-xs text-gray-400">
|
||||
{intl.formatMessage(
|
||||
messages.manageModalRemoveMediaWarning,
|
||||
{
|
||||
mediaType: intl.formatMessage(
|
||||
mediaType === 'movie'
|
||||
? messages.movie
|
||||
: messages.tvshow
|
||||
),
|
||||
arr: mediaType === 'movie' ? 'Radarr' : 'Sonarr',
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -418,21 +466,52 @@ const ManageSlideOver: React.FC<
|
||||
</div>
|
||||
)}
|
||||
{data?.mediaInfo?.serviceUrl4k && (
|
||||
<a
|
||||
href={data?.mediaInfo?.serviceUrl4k}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="block"
|
||||
>
|
||||
<Button buttonType="ghost" className="w-full">
|
||||
<ServerIcon />
|
||||
<span>
|
||||
{intl.formatMessage(messages.openarr4k, {
|
||||
arr: mediaType === 'movie' ? 'Radarr' : 'Sonarr',
|
||||
})}
|
||||
</span>
|
||||
</Button>
|
||||
</a>
|
||||
<>
|
||||
<a
|
||||
href={data?.mediaInfo?.serviceUrl4k}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="block"
|
||||
>
|
||||
<Button buttonType="ghost" className="w-full">
|
||||
<ServerIcon />
|
||||
<span>
|
||||
{intl.formatMessage(messages.openarr4k, {
|
||||
arr: mediaType === 'movie' ? 'Radarr' : 'Sonarr',
|
||||
})}
|
||||
</span>
|
||||
</Button>
|
||||
</a>
|
||||
<div>
|
||||
<ConfirmButton
|
||||
onClick={() => deleteMediaFile()}
|
||||
confirmText={intl.formatMessage(
|
||||
globalMessages.areyousure
|
||||
)}
|
||||
className="w-full"
|
||||
>
|
||||
<TrashIcon />
|
||||
<span>
|
||||
{intl.formatMessage(messages.removearr4k, {
|
||||
arr: mediaType === 'movie' ? 'Radarr' : 'Sonarr',
|
||||
})}
|
||||
</span>
|
||||
</ConfirmButton>
|
||||
<div className="mt-1 text-xs text-gray-400">
|
||||
{intl.formatMessage(
|
||||
messages.manageModalRemoveMediaWarning,
|
||||
{
|
||||
mediaType: intl.formatMessage(
|
||||
mediaType === 'movie'
|
||||
? messages.movie
|
||||
: messages.tvshow
|
||||
),
|
||||
arr: mediaType === 'movie' ? 'Radarr' : 'Sonarr',
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user