From 39069b1c7d2e97bf30df8a7c5e92ba654f26ae3b Mon Sep 17 00:00:00 2001 From: thempc Date: Mon, 30 Oct 2023 14:17:24 +0100 Subject: [PATCH 1/7] feat: adding anime specific Sonarr/Radarr instance support re #2876 --- overseerr-api.yml | 20 +++++++ server/entity/MediaRequest.ts | 53 ++++++++++++------- server/interfaces/api/requestInterfaces.ts | 1 + server/interfaces/api/serviceInterfaces.ts | 1 + server/lib/settings.ts | 1 + server/routes/service.ts | 4 ++ server/routes/settings/radarr.ts | 13 ++++- server/routes/settings/sonarr.ts | 12 ++++- src/components/RequestBlock/index.tsx | 1 + src/components/RequestCard/index.tsx | 1 + .../RequestList/RequestItem/index.tsx | 1 + .../RequestModal/AdvancedRequester/index.tsx | 16 ++++-- .../RequestModal/MovieRequestModal.tsx | 7 +++ .../RequestModal/TvRequestModal.tsx | 9 ++-- src/components/RequestModal/index.tsx | 1 + src/components/Settings/RadarrModal/index.tsx | 28 ++++++++-- src/components/Settings/SettingsServices.tsx | 14 ++++- src/components/Settings/SonarrModal/index.tsx | 28 ++++++++-- src/hooks/useRequestOverride.ts | 5 +- src/i18n/locale/en.json | 10 ++++ 20 files changed, 189 insertions(+), 37 deletions(-) diff --git a/overseerr-api.yml b/overseerr-api.yml index 6e8f5896..bfbb12d4 100644 --- a/overseerr-api.yml +++ b/overseerr-api.yml @@ -360,6 +360,9 @@ components: is4k: type: boolean example: false + isAnime: + type: boolean + example: false minimumAvailability: type: string example: 'In Cinema' @@ -385,6 +388,7 @@ components: - activeProfileName - activeDirectory - is4k + - isAnime - minimumAvailability - isDefault SonarrSettings: @@ -439,6 +443,9 @@ components: is4k: type: boolean example: false + isAnime: + type: boolean + example: false enableSeasonFolders: type: boolean example: false @@ -464,6 +471,7 @@ components: - activeProfileName - activeDirectory - is4k + - isAnime - enableSeasonFolders - isDefault ServarrTag: @@ -1028,6 +1036,9 @@ components: is4k: type: boolean example: false + isAnime: + type: boolean + example: false serverId: type: number profileId: @@ -5018,6 +5029,9 @@ paths: is4k: type: boolean example: false + isAnime: + type: boolean + example: false serverId: type: number profileId: @@ -5122,6 +5136,9 @@ paths: is4k: type: boolean example: false + isAnime: + type: boolean + example: false serverId: type: number profileId: @@ -5763,6 +5780,9 @@ paths: is4k: type: boolean example: false + isAnime: + type: boolean + example: false responses: '200': description: Returned media diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index ba67ab7b..ad13fec6 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -6,7 +6,6 @@ import type { } from '@server/api/servarr/sonarr'; import SonarrAPI from '@server/api/servarr/sonarr'; import TheMovieDb from '@server/api/themoviedb'; -import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants'; import { MediaRequestStatus, MediaStatus, @@ -157,6 +156,7 @@ export class MediaRequest { .leftJoin('request.media', 'media') .leftJoinAndSelect('request.requestedBy', 'user') .where('request.is4k = :is4k', { is4k: requestBody.is4k }) + .andWhere('request.isAnime = :isAnime', { isAnime: requestBody.isAnime }) .andWhere('media.tmdbId = :tmdbId', { tmdbId: tmdbMedia.id }) .andWhere('media.mediaType = :mediaType', { mediaType: requestBody.mediaType, @@ -173,6 +173,7 @@ export class MediaRequest { tmdbId: tmdbMedia.id, mediaType: requestBody.mediaType, is4k: requestBody.is4k, + isAnime: requestBody.isAnime, label: 'Media Request', }); @@ -231,6 +232,7 @@ export class MediaRequest { ? user : undefined, is4k: requestBody.is4k, + isAnime: requestBody.isAnime, serverId: requestBody.serverId, profileId: requestBody.profileId, rootFolder: requestBody.rootFolder, @@ -260,6 +262,7 @@ export class MediaRequest { .filter( (request) => request.is4k === requestBody.is4k && + request.isAnime === requestBody.isAnime && request.status !== MediaRequestStatus.DECLINED ) .reduce((seasons, request) => { @@ -334,6 +337,7 @@ export class MediaRequest { ? user : undefined, is4k: requestBody.is4k, + isAnime: requestBody.isAnime, serverId: requestBody.serverId, profileId: requestBody.profileId, rootFolder: requestBody.rootFolder, @@ -414,6 +418,9 @@ export class MediaRequest { @Column({ default: false }) public is4k: boolean; + @Column({ default: false }) + public isAnime: boolean; + @Column({ nullable: true }) public serverId: number; @@ -665,7 +672,10 @@ export class MediaRequest { } let radarrSettings = settings.radarr.find( - (radarr) => radarr.isDefault && radarr.is4k === this.is4k + (radarr) => + radarr.isDefault && + radarr.is4k === this.is4k && + radarr.isAnime === this.isAnime ); if ( @@ -689,9 +699,9 @@ export class MediaRequest { if (!radarrSettings) { logger.warn( `There is no default ${ - this.is4k ? '4K ' : '' + this.isAnime ? 'Anime ' : this.is4k ? '4K ' : '' }Radarr server configured. Did you set any of your ${ - this.is4k ? '4K ' : '' + this.isAnime ? 'Anime ' : this.is4k ? '4K ' : '' }Radarr servers as default?`, { label: 'Media Request', @@ -900,7 +910,10 @@ export class MediaRequest { } let sonarrSettings = settings.sonarr.find( - (sonarr) => sonarr.isDefault && sonarr.is4k === this.is4k + (sonarr) => + sonarr.isDefault && + sonarr.is4k === this.is4k && + sonarr.isAnime == this.isAnime ); if ( @@ -924,9 +937,9 @@ export class MediaRequest { if (!sonarrSettings) { logger.warn( `There is no default ${ - this.is4k ? '4K ' : '' + this.isAnime ? 'Anime ' : this.is4k ? '4K ' : '' }Sonarr server configured. Did you set any of your ${ - this.is4k ? '4K ' : '' + this.isAnime ? 'Anime ' : this.is4k ? '4K ' : '' }Sonarr servers as default?`, { label: 'Media Request', @@ -979,11 +992,7 @@ export class MediaRequest { let seriesType: SonarrSeries['seriesType'] = 'standard'; // Change series type to anime if the anime keyword is present on tmdb - if ( - series.keywords.results.some( - (keyword) => keyword.id === ANIME_KEYWORD_ID - ) - ) { + if (this.isAnime) { seriesType = sonarrSettings.animeSeriesType ?? 'anime'; } @@ -1171,30 +1180,38 @@ export class MediaRequest { switch (type) { case Notification.MEDIA_APPROVED: - event = `${this.is4k ? '4K ' : ''}${mediaType} Request Approved`; + event = `${ + this.isAnime ? 'Anime ' : this.is4k ? '4K ' : '' + }${mediaType} Request Approved`; notifyAdmin = false; break; case Notification.MEDIA_DECLINED: - event = `${this.is4k ? '4K ' : ''}${mediaType} Request Declined`; + event = `${ + this.isAnime ? 'Anime ' : this.is4k ? '4K ' : '' + }${mediaType} Request Declined`; notifyAdmin = false; break; case Notification.MEDIA_PENDING: - event = `New ${this.is4k ? '4K ' : ''}${mediaType} Request`; + event = `New ${ + this.isAnime ? 'Anime ' : this.is4k ? '4K ' : '' + }${mediaType} Request`; break; case Notification.MEDIA_AUTO_REQUESTED: event = `${ - this.is4k ? '4K ' : '' + this.isAnime ? 'Anime ' : this.is4k ? '4K ' : '' }${mediaType} Request Automatically Submitted`; notifyAdmin = false; notifySystem = false; break; case Notification.MEDIA_AUTO_APPROVED: event = `${ - this.is4k ? '4K ' : '' + this.isAnime ? 'Anime ' : this.is4k ? '4K ' : '' }${mediaType} Request Automatically Approved`; break; case Notification.MEDIA_FAILED: - event = `${this.is4k ? '4K ' : ''}${mediaType} Request Failed`; + event = `${ + this.isAnime ? 'Anime ' : this.is4k ? '4K ' : '' + }${mediaType} Request Failed`; break; } diff --git a/server/interfaces/api/requestInterfaces.ts b/server/interfaces/api/requestInterfaces.ts index 89863cb0..d860c966 100644 --- a/server/interfaces/api/requestInterfaces.ts +++ b/server/interfaces/api/requestInterfaces.ts @@ -12,6 +12,7 @@ export type MediaRequestBody = { tvdbId?: number; seasons?: number[] | 'all'; is4k?: boolean; + isAnime?: boolean; serverId?: number; profileId?: number; rootFolder?: string; diff --git a/server/interfaces/api/serviceInterfaces.ts b/server/interfaces/api/serviceInterfaces.ts index 3b430b0b..6c7de584 100644 --- a/server/interfaces/api/serviceInterfaces.ts +++ b/server/interfaces/api/serviceInterfaces.ts @@ -5,6 +5,7 @@ export interface ServiceCommonServer { id: number; name: string; is4k: boolean; + isAnime: boolean; isDefault: boolean; activeProfileId: number; activeDirectory: string; diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 10213a04..51f615a7 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -57,6 +57,7 @@ export interface DVRSettings { activeDirectory: string; tags: number[]; is4k: boolean; + isAnime: boolean; isDefault: boolean; externalUrl?: string; syncEnabled: boolean; diff --git a/server/routes/service.ts b/server/routes/service.ts index 083e1eb5..cd23bcfa 100644 --- a/server/routes/service.ts +++ b/server/routes/service.ts @@ -19,6 +19,7 @@ serviceRoutes.get('/radarr', async (req, res) => { id: radarr.id, name: radarr.name, is4k: radarr.is4k, + isAnime: radarr.isAnime, isDefault: radarr.isDefault, activeDirectory: radarr.activeDirectory, activeProfileId: radarr.activeProfileId, @@ -59,6 +60,7 @@ serviceRoutes.get<{ radarrId: string }>( id: radarrSettings.id, name: radarrSettings.name, is4k: radarrSettings.is4k, + isAnime: radarrSettings.isAnime, isDefault: radarrSettings.isDefault, activeDirectory: radarrSettings.activeDirectory, activeProfileId: radarrSettings.activeProfileId, @@ -87,6 +89,7 @@ serviceRoutes.get('/sonarr', async (req, res) => { id: sonarr.id, name: sonarr.name, is4k: sonarr.is4k, + isAnime: sonarr.isAnime, isDefault: sonarr.isDefault, activeDirectory: sonarr.activeDirectory, activeProfileId: sonarr.activeProfileId, @@ -133,6 +136,7 @@ serviceRoutes.get<{ sonarrId: string }>( id: sonarrSettings.id, name: sonarrSettings.name, is4k: sonarrSettings.is4k, + isAnime: sonarrSettings.isAnime, isDefault: sonarrSettings.isDefault, activeDirectory: sonarrSettings.activeDirectory, activeProfileId: sonarrSettings.activeProfileId, diff --git a/server/routes/settings/radarr.ts b/server/routes/settings/radarr.ts index c2b0a6f5..cf7e3286 100644 --- a/server/routes/settings/radarr.ts +++ b/server/routes/settings/radarr.ts @@ -24,7 +24,12 @@ radarrRoutes.post('/', (req, res) => { // and are the default if (req.body.isDefault) { settings.radarr - .filter((radarrInstance) => radarrInstance.is4k === req.body.is4k) + .filter( + (radarrInstance) => + radarrInstance.is4k === req.body.is4k && + radarrInstance.isAnime && + req.body.isAnime + ) .forEach((radarrInstance) => { radarrInstance.isDefault = false; }); @@ -92,7 +97,11 @@ radarrRoutes.put<{ id: string }, RadarrSettings, RadarrSettings>( // and are the default if (req.body.isDefault) { settings.radarr - .filter((radarrInstance) => radarrInstance.is4k === req.body.is4k) + .filter( + (radarrInstance) => + radarrInstance.is4k === req.body.is4k && + radarrInstance.isAnime === req.body.isAnime + ) .forEach((radarrInstance) => { radarrInstance.isDefault = false; }); diff --git a/server/routes/settings/sonarr.ts b/server/routes/settings/sonarr.ts index 358d0700..718269bf 100644 --- a/server/routes/settings/sonarr.ts +++ b/server/routes/settings/sonarr.ts @@ -24,7 +24,11 @@ sonarrRoutes.post('/', (req, res) => { // and are the default if (req.body.isDefault) { settings.sonarr - .filter((sonarrInstance) => sonarrInstance.is4k === req.body.is4k) + .filter( + (sonarrInstance) => + sonarrInstance.is4k === req.body.is4k && + sonarrInstance.isAnime === req.body.isAnime + ) .forEach((sonarrInstance) => { sonarrInstance.isDefault = false; }); @@ -90,7 +94,11 @@ sonarrRoutes.put<{ id: string }>('/:id', (req, res) => { // and are the default if (req.body.isDefault) { settings.sonarr - .filter((sonarrInstance) => sonarrInstance.is4k === req.body.is4k) + .filter( + (sonarrInstance) => + sonarrInstance.is4k === req.body.is4k && + sonarrInstance.isAnime === req.body.isAnime + ) .forEach((sonarrInstance) => { sonarrInstance.isDefault = false; }); diff --git a/src/components/RequestBlock/index.tsx b/src/components/RequestBlock/index.tsx index ca8cb5fa..f37f4538 100644 --- a/src/components/RequestBlock/index.tsx +++ b/src/components/RequestBlock/index.tsx @@ -78,6 +78,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => { tmdbId={request.media.tmdbId} type={request.type} is4k={request.is4k} + isAnime={request.isAnime} editRequest={request} onCancel={() => setShowEditModal(false)} onComplete={() => { diff --git a/src/components/RequestCard/index.tsx b/src/components/RequestCard/index.tsx index 44abd555..10c5eb32 100644 --- a/src/components/RequestCard/index.tsx +++ b/src/components/RequestCard/index.tsx @@ -308,6 +308,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => { tmdbId={request.media.tmdbId} type={request.type} is4k={request.is4k} + isAnime={request.isAnime} editRequest={request} onCancel={() => setShowEditModal(false)} onComplete={() => { diff --git a/src/components/RequestList/RequestItem/index.tsx b/src/components/RequestList/RequestItem/index.tsx index a42483ab..91c82cd4 100644 --- a/src/components/RequestList/RequestItem/index.tsx +++ b/src/components/RequestList/RequestItem/index.tsx @@ -368,6 +368,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => { tmdbId={request.media.tmdbId} type={request.type} is4k={request.is4k} + isAnime={request.isAnime} editRequest={request} onCancel={() => setShowEditModal(false)} onComplete={() => { diff --git a/src/components/RequestModal/AdvancedRequester/index.tsx b/src/components/RequestModal/AdvancedRequester/index.tsx index 4f5bb9ac..f17c470f 100644 --- a/src/components/RequestModal/AdvancedRequester/index.tsx +++ b/src/components/RequestModal/AdvancedRequester/index.tsx @@ -152,7 +152,8 @@ const AdvancedRequester = ({ useEffect(() => { let defaultServer = data?.find( - (server) => server.isDefault && is4k === server.is4k + (server) => + server.isDefault && is4k === server.is4k && isAnime === server.isAnime ); if (!defaultServer && (data ?? []).length > 0) { @@ -293,7 +294,9 @@ const AdvancedRequester = ({ if ( (!data || selectedServer === null || - (data.filter((server) => server.is4k === is4k).length < 2 && + (data.filter( + (server) => server.is4k === is4k && server.isAnime === isAnime + ).length < 2 && (!serverData || (serverData.profiles.length < 2 && serverData.rootFolders.length < 2 && @@ -312,7 +315,9 @@ const AdvancedRequester = ({
{!!data && selectedServer !== null && (
- {data.filter((server) => server.is4k === is4k).length > 1 && ( + {data.filter( + (server) => server.is4k === is4k && server.isAnime === isAnime + ).length > 1 && (
+
+ +
+ +
+
+
+ +
+ +
+