diff --git a/server/src/database/migrations/1733357722729-Remove-default-arr-servers.ts b/server/src/database/migrations/1733357722729-Remove-default-arr-servers.ts new file mode 100644 index 00000000..d4241d85 --- /dev/null +++ b/server/src/database/migrations/1733357722729-Remove-default-arr-servers.ts @@ -0,0 +1,58 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveDefaultArrServers1733357722729 + implements MigrationInterface +{ + name = 'RemoveDefaultArrServers1733357722729'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + CREATE TABLE "temporary_sonarr_settings" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "serverName" varchar NOT NULL, + "url" varchar, + "apiKey" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_sonarr_settings"("id", "serverName", "url", "apiKey") + SELECT "id", + "serverName", + "url", + "apiKey" + FROM "sonarr_settings" + `); + await queryRunner.query(` + DROP TABLE "sonarr_settings" + `); + await queryRunner.query(` + ALTER TABLE "temporary_sonarr_settings" + RENAME TO "sonarr_settings" + `); + await queryRunner.query(` + CREATE TABLE "temporary_radarr_settings" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "serverName" varchar NOT NULL, + "url" varchar, + "apiKey" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_radarr_settings"("id", "serverName", "url", "apiKey") + SELECT "id", + "serverName", + "url", + "apiKey" + FROM "radarr_settings" + `); + await queryRunner.query(` + DROP TABLE "radarr_settings" + `); + await queryRunner.query(` + ALTER TABLE "temporary_radarr_settings" + RENAME TO "radarr_settings" + `); + } + + public async down(queryRunner: QueryRunner): Promise {} +} diff --git a/server/src/modules/api/plex-api/plex-api.service.ts b/server/src/modules/api/plex-api/plex-api.service.ts index dad8e489..724200bc 100644 --- a/server/src/modules/api/plex-api/plex-api.service.ts +++ b/server/src/modules/api/plex-api/plex-api.service.ts @@ -443,13 +443,13 @@ export class PlexApiService { } public async deleteMediaFromDisk(plexId: number | string): Promise { - this.logger.log( - `[Plex] Removed media with ID ${plexId} from Plex library.`, - ); try { await this.plexClient.deleteQuery({ uri: `/library/metadata/${plexId}`, }); + this.logger.log( + `[Plex] Removed media with ID ${plexId} from Plex library.`, + ); } catch (e) { this.logger.warn('Something went wrong while removing media from Plex.', { label: 'Plex API', diff --git a/server/src/modules/api/servarr-api/interfaces/radarr.interface.ts b/server/src/modules/api/servarr-api/interfaces/radarr.interface.ts index 46976eaf..6aa1af64 100644 --- a/server/src/modules/api/servarr-api/interfaces/radarr.interface.ts +++ b/server/src/modules/api/servarr-api/interfaces/radarr.interface.ts @@ -14,6 +14,7 @@ export interface RadarrMovieOptions { export interface RadarrMovie { id: number; title: string; + originalLanguage: RadarrLanguage; isAvailable: boolean; monitored: boolean; tmdbId: number; @@ -34,6 +35,11 @@ export interface RadarrMovie { tags: number[]; } +export interface RadarrLanguage { + id: number; + name: string | null; +} + export interface RadarrInfo { appName: string; version: string; diff --git a/server/src/modules/api/servarr-api/interfaces/sonarr.interface.ts b/server/src/modules/api/servarr-api/interfaces/sonarr.interface.ts index bae6851c..f47b212b 100644 --- a/server/src/modules/api/servarr-api/interfaces/sonarr.interface.ts +++ b/server/src/modules/api/servarr-api/interfaces/sonarr.interface.ts @@ -83,6 +83,7 @@ export interface SonarrEpisode { export interface SonarrSeries { title: string; sortTitle: string; + originalLanguage: SonarrLanguage; seasonCount: number; status: string; overview: string; @@ -131,6 +132,11 @@ export interface SonarrSeries { ended?: boolean; } +export interface SonarrLanguage { + id: number; + name: string | null; +} + export interface AddSeriesOptions { tvdbid: number; title: string; diff --git a/server/src/modules/collections/collection-worker.service.ts b/server/src/modules/collections/collection-worker.service.ts index d571def2..b615d053 100644 --- a/server/src/modules/collections/collection-worker.service.ts +++ b/server/src/modules/collections/collection-worker.service.ts @@ -151,293 +151,300 @@ export class CollectionWorkerService extends TaskBase { ? await this.servarrApi.getSonarrApiClient(collection.sonarrSettingsId) : undefined; - if (plexLibrary.type === 'movie') { - if (radarrApiClient) { - // find tmdbid - const tmdbid = media.tmdbId - ? media.tmdbId - : ( - await this.tmdbIdService.getTmdbIdFromPlexRatingKey( - media.plexId.toString(), - ) - )?.id; - - if (tmdbid) { - const radarrMedia = await radarrApiClient.getMovieByTmdbId(tmdbid); - if (radarrMedia && radarrMedia.id) { - switch (collection.arrAction) { - case ServarrAction.DELETE: - await radarrApiClient.deleteMovie( - radarrMedia.id, - true, - collection.listExclusions, - ); - this.infoLogger('Removed movie from filesystem & Radarr'); - break; - case ServarrAction.UNMONITOR: - await radarrApiClient.unmonitorMovie(radarrMedia.id, false); - this.infoLogger('Unmonitored movie in Radarr'); - break; - case ServarrAction.UNMONITOR_DELETE_ALL: - await radarrApiClient.unmonitorMovie(radarrMedia.id, true); - this.infoLogger('Unmonitored movie in Radarr & removed files'); - break; - case ServarrAction.UNMONITOR_DELETE_EXISTING: - await radarrApiClient.deleteMovie( - radarrMedia.id, - true, - collection.listExclusions, - ); - this.infoLogger('Removed movie from filesystem & Radarr'); - break; - } - } else { - if (collection.arrAction !== ServarrAction.UNMONITOR) { - this.plexApi.deleteMediaFromDisk(media.plexId.toString()); - this.infoLogger( - `Couldn't find movie with tmdb id ${tmdbid} in Radarr, so no Radarr action was taken for movie with Plex ID ${media.plexId}. But the movie was removed from the filesystem`, + if (plexLibrary.type === 'movie' && radarrApiClient) { + // find tmdbid + const tmdbid = media.tmdbId + ? media.tmdbId + : ( + await this.tmdbIdService.getTmdbIdFromPlexRatingKey( + media.plexId.toString(), + ) + )?.id; + + if (tmdbid) { + const radarrMedia = await radarrApiClient.getMovieByTmdbId(tmdbid); + if (radarrMedia && radarrMedia.id) { + switch (collection.arrAction) { + case ServarrAction.DELETE: + await radarrApiClient.deleteMovie( + radarrMedia.id, + true, + collection.listExclusions, ); - } else { - this.infoLogger( - `Radarr unmonitor action was not possible, couldn't find movie with tmdb id ${tmdbid} in Radarr. No action was taken for movie with Plex ID ${media.plexId}`, + this.infoLogger('Removed movie from filesystem & Radarr'); + break; + case ServarrAction.UNMONITOR: + await radarrApiClient.unmonitorMovie(radarrMedia.id, false); + this.infoLogger('Unmonitored movie in Radarr'); + break; + case ServarrAction.UNMONITOR_DELETE_ALL: + await radarrApiClient.unmonitorMovie(radarrMedia.id, true); + this.infoLogger('Unmonitored movie in Radarr & removed files'); + break; + case ServarrAction.UNMONITOR_DELETE_EXISTING: + await radarrApiClient.deleteMovie( + radarrMedia.id, + true, + collection.listExclusions, ); - } + this.infoLogger('Removed movie from filesystem & Radarr'); + break; } } else { - this.infoLogger( - `Couldn't find correct tmdb id. No action taken for movie with Plex ID: ${media.plexId}. Please check this movie manually`, - ); + if (collection.arrAction !== ServarrAction.UNMONITOR) { + this.plexApi.deleteMediaFromDisk(media.plexId.toString()); + this.infoLogger( + `Couldn't find movie with tmdb id ${tmdbid} in Radarr, so no Radarr action was taken for movie with Plex ID ${media.plexId}. But the movie was removed from the filesystem`, + ); + } else { + this.infoLogger( + `Radarr unmonitor action was not possible, couldn't find movie with tmdb id ${tmdbid} in Radarr. No action was taken for movie with Plex ID ${media.plexId}`, + ); + } } + } else { + this.infoLogger( + `Couldn't find correct tmdb id. No action taken for movie with Plex ID: ${media.plexId}. Please check this movie manually`, + ); + } + } else if (plexLibrary.type == 'show' && sonarrApiClient) { + // get the tvdb id + let tvdbId = undefined; + switch (collection.type) { + case EPlexDataType.SEASONS: + plexData = await this.plexApi.getMetadata(media.plexId.toString()); + tvdbId = await this.tvdbidFinder({ + ...media, + ...{ plexID: plexData.parentRatingKey }, + }); + media.tmdbId = media.tmdbId + ? media.tmdbId + : ( + await this.tmdbIdService.getTmdbIdFromPlexRatingKey( + plexData.parentRatingKey, + ) + )?.id; + break; + case EPlexDataType.EPISODES: + plexData = await this.plexApi.getMetadata(media.plexId.toString()); + tvdbId = await this.tvdbidFinder({ + ...media, + ...{ plexID: plexData.grandparentRatingKey }, + }); + media.tmdbId = media.tmdbId + ? media.tmdbId + : ( + await this.tmdbIdService.getTmdbIdFromPlexRatingKey( + plexData.grandparentRatingKey.toString(), + ) + )?.id; + break; + default: + tvdbId = await this.tvdbidFinder(media); + media.tmdbId = media.tmdbId + ? media.tmdbId + : ( + await this.tmdbIdService.getTmdbIdFromPlexRatingKey( + media.plexId.toString(), + ) + )?.id; + break; } - } else { - if (sonarrApiClient) { - // get the tvdb id - let tvdbId = undefined; - switch (collection.type) { - case EPlexDataType.SEASONS: - plexData = await this.plexApi.getMetadata(media.plexId.toString()); - tvdbId = await this.tvdbidFinder({ - ...media, - ...{ plexID: plexData.parentRatingKey }, - }); - media.tmdbId = media.tmdbId - ? media.tmdbId - : ( - await this.tmdbIdService.getTmdbIdFromPlexRatingKey( - plexData.parentRatingKey, - ) - )?.id; - break; - case EPlexDataType.EPISODES: - plexData = await this.plexApi.getMetadata(media.plexId.toString()); - tvdbId = await this.tvdbidFinder({ - ...media, - ...{ plexID: plexData.grandparentRatingKey }, - }); - media.tmdbId = media.tmdbId - ? media.tmdbId - : ( - await this.tmdbIdService.getTmdbIdFromPlexRatingKey( - plexData.grandparentRatingKey.toString(), - ) - )?.id; - break; - default: - tvdbId = await this.tvdbidFinder(media); - media.tmdbId = media.tmdbId - ? media.tmdbId - : ( - await this.tmdbIdService.getTmdbIdFromPlexRatingKey( - media.plexId.toString(), - ) - )?.id; - break; - } - - if (tvdbId) { - let sonarrMedia = await sonarrApiClient.getSeriesByTvdbId(tvdbId); - if (sonarrMedia?.id) { - switch (collection.arrAction) { - case ServarrAction.DELETE: - switch (collection.type) { - case EPlexDataType.SEASONS: - sonarrMedia = await sonarrApiClient.unmonitorSeasons( - sonarrMedia.id, - plexData.index, - true, - ); - this.infoLogger( - `[Sonarr] Removed season ${plexData.index} from show '${sonarrMedia.title}'`, - ); - break; - case EPlexDataType.EPISODES: - await sonarrApiClient.UnmonitorDeleteEpisodes( - sonarrMedia.id, - plexData.parentIndex, - [plexData.index], - true, - ); - this.infoLogger( - `[Sonarr] Removed season ${plexData.parentIndex} episode ${plexData.index} from show '${sonarrMedia.title}'`, - ); - break; - default: - await sonarrApiClient.deleteShow( - sonarrMedia.id, - true, - collection.listExclusions, - ); - this.infoLogger( - `Removed show ${sonarrMedia.title}' from Sonarr`, - ); - break; - } - break; - case ServarrAction.UNMONITOR: - switch (collection.type) { - case EPlexDataType.SEASONS: - sonarrMedia = await sonarrApiClient.unmonitorSeasons( - sonarrMedia.id, - plexData.index, - false, - ); - this.infoLogger( - `[Sonarr] Unmonitored season ${plexData.index} from show '${sonarrMedia.title}'`, - ); - break; - case EPlexDataType.EPISODES: - await sonarrApiClient.UnmonitorDeleteEpisodes( - sonarrMedia.id, - plexData.parentIndex, - [plexData.index], - false, - ); - this.infoLogger( - `[Sonarr] Unmonitored season ${plexData.parentIndex} episode ${plexData.index} from show '${sonarrMedia.title}'`, - ); - break; - default: - sonarrMedia = await sonarrApiClient.unmonitorSeasons( - sonarrMedia.id, - 'all', - false, - ); - if (sonarrMedia) { - // unmonitor show - sonarrMedia.monitored = false; - sonarrApiClient.updateSeries(sonarrMedia); - this.infoLogger( - `[Sonarr] Unmonitored show '${sonarrMedia.title}'`, - ); - } - - break; - } - break; - case ServarrAction.UNMONITOR_DELETE_ALL: - switch (collection.type) { - case EPlexDataType.SEASONS: - sonarrMedia = await sonarrApiClient.unmonitorSeasons( - sonarrMedia.id, - plexData.index, - true, - ); - this.infoLogger( - `[Sonarr] Removed season ${plexData.index} from show '${sonarrMedia.title}'`, - ); - break; - case EPlexDataType.EPISODES: - await sonarrApiClient.UnmonitorDeleteEpisodes( - sonarrMedia.id, - plexData.parentIndex, - [plexData.index], - true, - ); + if (tvdbId) { + let sonarrMedia = await sonarrApiClient.getSeriesByTvdbId(tvdbId); + if (sonarrMedia?.id) { + switch (collection.arrAction) { + case ServarrAction.DELETE: + switch (collection.type) { + case EPlexDataType.SEASONS: + sonarrMedia = await sonarrApiClient.unmonitorSeasons( + sonarrMedia.id, + plexData.index, + true, + ); + this.infoLogger( + `[Sonarr] Removed season ${plexData.index} from show '${sonarrMedia.title}'`, + ); + break; + case EPlexDataType.EPISODES: + await sonarrApiClient.UnmonitorDeleteEpisodes( + sonarrMedia.id, + plexData.parentIndex, + [plexData.index], + true, + ); + this.infoLogger( + `[Sonarr] Removed season ${plexData.parentIndex} episode ${plexData.index} from show '${sonarrMedia.title}'`, + ); + break; + default: + await sonarrApiClient.deleteShow( + sonarrMedia.id, + true, + collection.listExclusions, + ); + this.infoLogger( + `Removed show ${sonarrMedia.title}' from Sonarr`, + ); + break; + } + break; + case ServarrAction.UNMONITOR: + switch (collection.type) { + case EPlexDataType.SEASONS: + sonarrMedia = await sonarrApiClient.unmonitorSeasons( + sonarrMedia.id, + plexData.index, + false, + ); + this.infoLogger( + `[Sonarr] Unmonitored season ${plexData.index} from show '${sonarrMedia.title}'`, + ); + break; + case EPlexDataType.EPISODES: + await sonarrApiClient.UnmonitorDeleteEpisodes( + sonarrMedia.id, + plexData.parentIndex, + [plexData.index], + false, + ); + this.infoLogger( + `[Sonarr] Unmonitored season ${plexData.parentIndex} episode ${plexData.index} from show '${sonarrMedia.title}'`, + ); + break; + default: + sonarrMedia = await sonarrApiClient.unmonitorSeasons( + sonarrMedia.id, + 'all', + false, + ); + + if (sonarrMedia) { + // unmonitor show + sonarrMedia.monitored = false; + sonarrApiClient.updateSeries(sonarrMedia); this.infoLogger( - `[Sonarr] Removed season ${plexData.parentIndex} episode ${plexData.index} from show '${sonarrMedia.title}'`, - ); - break; - default: - sonarrMedia = await sonarrApiClient.unmonitorSeasons( - sonarrMedia.id, - 'all', - true, - ); - - if (sonarrMedia) { - // unmonitor show - sonarrMedia.monitored = false; - sonarrApiClient.updateSeries(sonarrMedia); - this.infoLogger( - `[Sonarr] Unmonitored show '${sonarrMedia.title}' and removed all episodes`, - ); - } - - break; - } - break; - case ServarrAction.UNMONITOR_DELETE_EXISTING: - switch (collection.type) { - case EPlexDataType.SEASONS: - sonarrMedia = await sonarrApiClient.unmonitorSeasons( - sonarrMedia.id, - plexData.index, - true, - true, + `[Sonarr] Unmonitored show '${sonarrMedia.title}'`, ); + } + + break; + } + break; + case ServarrAction.UNMONITOR_DELETE_ALL: + switch (collection.type) { + case EPlexDataType.SEASONS: + sonarrMedia = await sonarrApiClient.unmonitorSeasons( + sonarrMedia.id, + plexData.index, + true, + ); + this.infoLogger( + `[Sonarr] Removed season ${plexData.index} from show '${sonarrMedia.title}'`, + ); + break; + case EPlexDataType.EPISODES: + await sonarrApiClient.UnmonitorDeleteEpisodes( + sonarrMedia.id, + plexData.parentIndex, + [plexData.index], + true, + ); + this.infoLogger( + `[Sonarr] Removed season ${plexData.parentIndex} episode ${plexData.index} from show '${sonarrMedia.title}'`, + ); + break; + default: + sonarrMedia = await sonarrApiClient.unmonitorSeasons( + sonarrMedia.id, + 'all', + true, + ); + + if (sonarrMedia) { + // unmonitor show + sonarrMedia.monitored = false; + sonarrApiClient.updateSeries(sonarrMedia); this.infoLogger( - `[Sonarr] Removed exisiting episodes from season ${plexData.index} from show '${sonarrMedia.title}'`, - ); - break; - case EPlexDataType.EPISODES: - await sonarrApiClient.UnmonitorDeleteEpisodes( - sonarrMedia.id, - plexData.parentIndex, - [plexData.index], - true, + `[Sonarr] Unmonitored show '${sonarrMedia.title}' and removed all episodes`, ); + } + + break; + } + break; + case ServarrAction.UNMONITOR_DELETE_EXISTING: + switch (collection.type) { + case EPlexDataType.SEASONS: + sonarrMedia = await sonarrApiClient.unmonitorSeasons( + sonarrMedia.id, + plexData.index, + true, + true, + ); + this.infoLogger( + `[Sonarr] Removed exisiting episodes from season ${plexData.index} from show '${sonarrMedia.title}'`, + ); + break; + case EPlexDataType.EPISODES: + await sonarrApiClient.UnmonitorDeleteEpisodes( + sonarrMedia.id, + plexData.parentIndex, + [plexData.index], + true, + ); + this.infoLogger( + `[Sonarr] Removed season ${plexData.parentIndex} episode ${plexData.index} from show '${sonarrMedia.title}'`, + ); + break; + default: + sonarrMedia = await sonarrApiClient.unmonitorSeasons( + sonarrMedia.id, + 'existing', + true, + ); + + if (sonarrMedia) { + // unmonitor show + sonarrMedia.monitored = false; + sonarrApiClient.updateSeries(sonarrMedia); this.infoLogger( - `[Sonarr] Removed season ${plexData.parentIndex} episode ${plexData.index} from show '${sonarrMedia.title}'`, - ); - break; - default: - sonarrMedia = await sonarrApiClient.unmonitorSeasons( - sonarrMedia.id, - 'existing', - true, + `[Sonarr] Unmonitored show '${sonarrMedia.title}' and Removed exisiting episodes`, ); + } - if (sonarrMedia) { - // unmonitor show - sonarrMedia.monitored = false; - sonarrApiClient.updateSeries(sonarrMedia); - this.infoLogger( - `[Sonarr] Unmonitored show '${sonarrMedia.title}' and Removed exisiting episodes`, - ); - } - - break; - } - break; - } - } else { - if (collection.arrAction !== ServarrAction.UNMONITOR) { - this.plexApi.deleteMediaFromDisk(plexData.ratingKey); - this.infoLogger( - `Couldn't find correct tvdb id. No Sonarr action was taken for show: https://www.themoviedb.org/tv/${media.tmdbId}. But media item was removed through Plex`, - ); - } else { - this.infoLogger( - `Couldn't find correct tvdb id. No unmonitor action was taken for show: https://www.themoviedb.org/tv/${media.tmdbId}`, - ); - } + break; + } + break; } } else { - this.infoLogger( - `Couldn't find correct tvdb id. No action was taken for show: https://www.themoviedb.org/tv/${media.tmdbId}. Please check this show manually`, - ); + if (collection.arrAction !== ServarrAction.UNMONITOR) { + this.plexApi.deleteMediaFromDisk(plexData.ratingKey); + this.infoLogger( + `Couldn't find correct tvdb id. No Sonarr action was taken for show: https://www.themoviedb.org/tv/${media.tmdbId}. But media item was removed through Plex`, + ); + } else { + this.infoLogger( + `Couldn't find correct tvdb id. No unmonitor action was taken for show: https://www.themoviedb.org/tv/${media.tmdbId}`, + ); + } } + } else { + this.infoLogger( + `Couldn't find correct tvdb id. No action was taken for show: https://www.themoviedb.org/tv/${media.tmdbId}. Please check this show manually`, + ); + } + } else if (!radarrApiClient && !sonarrApiClient) { + if (collection.arrAction !== ServarrAction.UNMONITOR) { + this.infoLogger( + `Couldn't utilize *arr to find and remove the media with id ${media.plexId}, so media will be removed from the filesystem through Plex. No unmonitor action was taken.`, + ); + await this.plexApi.deleteMediaFromDisk(media.plexId.toString()); + } else { + this.infoLogger( + `*arr unmonitor action isn't possible, since *arr is not available. Didn't unmonitor media with id ${media.plexId}.}`, + ); } } @@ -475,19 +482,6 @@ export class CollectionWorkerService extends TaskBase { break; } } - - // If *arr not configured, remove media through Plex - if (!(plexLibrary.type === 'movie' ? radarrApiClient : sonarrApiClient)) - if (collection.arrAction !== ServarrAction.UNMONITOR) { - await this.plexApi.deleteMediaFromDisk(media.plexId.toString()); - this.infoLogger( - `Couldn't utilize *arr to find and remove the media with id ${media.plexId}, so media was removed from the filesystem through Plex. No unmonitor action was taken.`, - ); - } else { - this.infoLogger( - `*arr unmonitor action isn't possible, since *arr is not available. Didn't unmonitor media with id ${media.plexId}.}`, - ); - } } } diff --git a/server/src/modules/rules/constants/rules.constants.ts b/server/src/modules/rules/constants/rules.constants.ts index d37ac18d..37e05209 100644 --- a/server/src/modules/rules/constants/rules.constants.ts +++ b/server/src/modules/rules/constants/rules.constants.ts @@ -430,6 +430,13 @@ export class RuleConstants { mediaType: MediaType.MOVIE, type: RuleType.TEXT, } as Property, + { + id: 13, + name: 'originalLanguage', + humanName: 'Original language', + mediaType: MediaType.MOVIE, + type: RuleType.TEXT, + } as Property, ], }, { @@ -555,6 +562,13 @@ export class RuleConstants { mediaType: MediaType.SHOW, type: RuleType.TEXT, } as Property, + { + id: 15, + name: 'originalLanguage', + humanName: 'Original language', + mediaType: MediaType.SHOW, + type: RuleType.TEXT, + } as Property, ], }, { diff --git a/server/src/modules/rules/getter/radarr-getter.service.ts b/server/src/modules/rules/getter/radarr-getter.service.ts index f82dc878..dc8925f5 100644 --- a/server/src/modules/rules/getter/radarr-getter.service.ts +++ b/server/src/modules/rules/getter/radarr-getter.service.ts @@ -56,27 +56,27 @@ export class RadarrGetterService { return movieResponse.added ? new Date(movieResponse.added) : null; } case 'fileDate': { - return movieResponse?.movieFile?.dateAdded + return movieResponse.movieFile?.dateAdded ? new Date(movieResponse.movieFile.dateAdded) : null; } case 'filePath': { - return movieResponse?.movieFile?.path + return movieResponse.movieFile?.path ? movieResponse.movieFile.path : null; } case 'fileQuality': { - return movieResponse?.movieFile?.quality?.quality?.resolution + return movieResponse.movieFile?.quality?.quality?.resolution ? movieResponse.movieFile.quality.quality.resolution : null; } case 'fileAudioChannels': { - return movieResponse?.movieFile + return movieResponse.movieFile ? movieResponse.movieFile.mediaInfo?.audioChannels : null; } case 'runTime': { - if (movieResponse?.movieFile?.mediaInfo?.runTime) { + if (movieResponse.movieFile?.mediaInfo?.runTime) { const hms = movieResponse.movieFile.mediaInfo.runTime; const splitted = hms.split(':'); return +splitted[0] * 60 + +splitted[1]; @@ -84,35 +84,34 @@ export class RadarrGetterService { return null; } case 'monitored': { - return movieResponse?.monitored + return movieResponse.monitored ? movieResponse.monitored ? 1 : 0 : null; } case 'tags': { - const movieTags = movieResponse?.tags; + const movieTags = movieResponse.tags; return (await radarrApiClient.getTags()) ?.filter((el) => movieTags.includes(el.id)) .map((el) => el.label); } case 'profile': { - const movieProfile = movieResponse?.qualityProfileId; + const movieProfile = movieResponse.qualityProfileId; return (await radarrApiClient.getProfiles())?.find( (el) => el.id === movieProfile, ).name; } case 'fileSize': { - return movieResponse?.sizeOnDisk + return movieResponse.sizeOnDisk ? Math.round(movieResponse.sizeOnDisk / 1048576) : movieResponse.movieFile?.size ? Math.round(movieResponse.movieFile.size / 1048576) : null; } case 'releaseDate': { - return movieResponse?.physicalRelease && - movieResponse?.digitalRelease + return movieResponse.physicalRelease && movieResponse.digitalRelease ? (await new Date(movieResponse.physicalRelease)) > new Date(movieResponse.digitalRelease) ? new Date(movieResponse.digitalRelease) @@ -124,10 +123,15 @@ export class RadarrGetterService { : null; } case 'inCinemas': { - return movieResponse?.inCinemas + return movieResponse.inCinemas ? new Date(movieResponse.inCinemas) : null; } + case 'originalLanguage': { + return movieResponse.originalLanguage?.name + ? movieResponse.originalLanguage.name + : null; + } } } else { this.logger.debug( diff --git a/server/src/modules/rules/getter/sonarr-getter.service.ts b/server/src/modules/rules/getter/sonarr-getter.service.ts index 6bf9af86..5c0cbf36 100644 --- a/server/src/modules/rules/getter/sonarr-getter.service.ts +++ b/server/src/modules/rules/getter/sonarr-getter.service.ts @@ -263,6 +263,11 @@ export class SonarrGetterService { : false; } } + case 'originalLanguage': { + return showResponse.originalLanguage?.name + ? showResponse.originalLanguage.name + : null; + } } } else return null; } else { diff --git a/server/src/modules/rules/rules.service.ts b/server/src/modules/rules/rules.service.ts index 25ac6a63..34354986 100644 --- a/server/src/modules/rules/rules.service.ts +++ b/server/src/modules/rules/rules.service.ts @@ -238,6 +238,8 @@ export class RulesService { forceOverseerr: params.forceOverseerr ? params.forceOverseerr : false, tautulliWatchedPercentOverride: params.tautulliWatchedPercentOverride ?? null, + radarrSettingsId: params.radarrSettingsId ?? null, + sonarrSettingsId: params.sonarrSettingsId ?? null, visibleOnRecommended: params.collection?.visibleOnRecommended, visibleOnHome: params.collection?.visibleOnHome, deleteAfterDays: +params.collection?.deleteAfterDays, @@ -1014,7 +1016,9 @@ export class RulesService { ); //test first value - const first = firstValApplication.props[parsedRule.firstVal[1]]; + const first = firstValApplication.props.find( + (x) => x.id == parsedRule.firstVal[1], + ); result = first.cacheReset ? true : result; @@ -1023,7 +1027,9 @@ export class RulesService { : undefined; // test second value - const second = secondValApplication?.props[parsedRule.lastVal[1]]; + const second = secondValApplication?.props.find( + (x) => x.id == parsedRule.lastVal[1], + ); result = second?.cacheReset ? true : result; } diff --git a/server/src/modules/settings/dto's/radarr-setting.dto.ts b/server/src/modules/settings/dto's/radarr-setting.dto.ts index d6586c28..1c0ebd56 100644 --- a/server/src/modules/settings/dto's/radarr-setting.dto.ts +++ b/server/src/modules/settings/dto's/radarr-setting.dto.ts @@ -8,8 +8,6 @@ export type RadarrSettingDto = { url: string; apiKey: string; - - isDefault: boolean; }; export type RadarrSettingRawDto = Omit; diff --git a/server/src/modules/settings/dto's/sonarr-setting.dto.ts b/server/src/modules/settings/dto's/sonarr-setting.dto.ts index 74ea6510..8ede6d05 100644 --- a/server/src/modules/settings/dto's/sonarr-setting.dto.ts +++ b/server/src/modules/settings/dto's/sonarr-setting.dto.ts @@ -8,8 +8,6 @@ export type SonarrSettingDto = { url: string; apiKey: string; - - isDefault: boolean; }; export type SonarrSettingRawDto = Omit; diff --git a/server/src/modules/settings/entities/radarr_settings.entities.ts b/server/src/modules/settings/entities/radarr_settings.entities.ts index 6fd7382f..ea7e036a 100644 --- a/server/src/modules/settings/entities/radarr_settings.entities.ts +++ b/server/src/modules/settings/entities/radarr_settings.entities.ts @@ -15,9 +15,6 @@ export class RadarrSettings { @Column({ nullable: true }) apiKey: string; - @Column({ default: false }) - isDefault: boolean; - @OneToMany(() => Collection, (collection) => collection.radarrSettings) collections: Collection[]; } diff --git a/server/src/modules/settings/entities/sonarr_settings.entities.ts b/server/src/modules/settings/entities/sonarr_settings.entities.ts index 7c877808..640aa5ea 100644 --- a/server/src/modules/settings/entities/sonarr_settings.entities.ts +++ b/server/src/modules/settings/entities/sonarr_settings.entities.ts @@ -15,9 +15,6 @@ export class SonarrSettings { @Column({ nullable: true }) apiKey: string; - @Column({ default: false }) - isDefault: boolean; - @OneToMany(() => Collection, (collection) => collection.sonarrSettings) collections: Collection[]; } diff --git a/server/src/modules/settings/settings.service.ts b/server/src/modules/settings/settings.service.ts index 35115bc5..ca99bf52 100644 --- a/server/src/modules/settings/settings.service.ts +++ b/server/src/modules/settings/settings.service.ts @@ -1,7 +1,7 @@ import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { randomUUID } from 'crypto'; -import { Not, Repository } from 'typeorm'; +import { Repository } from 'typeorm'; import { isValidCron } from 'cron-validator'; import { BasicResponseDto } from '../api/external-api/dto/basic-response.dto'; import { OverseerrApiService } from '../api/overseerr-api/overseerr-api.service'; @@ -154,25 +154,15 @@ export class SettingsService implements SettingDto { try { settings.url = settings.url.toLowerCase(); - return this.radarrSettingsRepo.manager.transaction(async (manager) => { - if (settings.isDefault) { - await manager - .withRepository(this.radarrSettingsRepo) - .update({ isDefault: true }, { isDefault: false }); - } - - const savedSetting = await manager - .withRepository(this.radarrSettingsRepo) - .save(settings); + const savedSetting = await this.radarrSettingsRepo.save(settings); - this.logger.log('Radarr setting added'); - return { - data: savedSetting, - status: 'OK', - code: 1, - message: 'Success', - }; - }); + this.logger.log('Radarr setting added'); + return { + data: savedSetting, + status: 'OK', + code: 1, + message: 'Success', + }; } catch (e) { this.logger.error('Error while adding Radarr setting: ', e); return { status: 'NOK', code: 0, message: 'Failure' }; @@ -185,33 +175,16 @@ export class SettingsService implements SettingDto { try { settings.url = settings.url.toLowerCase(); - const data = await this.radarrSettingsRepo.manager.transaction( - async (manager) => { - const settingsDb = await manager - .withRepository(this.radarrSettingsRepo) - .findOne({ - where: { id: settings.id }, - }); - - const data = { - ...settingsDb, - ...settings, - }; - - await manager.withRepository(this.radarrSettingsRepo).save(data); - - if (settings.isDefault) { - await manager - .withRepository(this.radarrSettingsRepo) - .update( - { isDefault: true, id: Not(settings.id) }, - { isDefault: false }, - ); - } + const settingsDb = await this.radarrSettingsRepo.findOne({ + where: { id: settings.id }, + }); - return data; - }, - ); + const data = { + ...settingsDb, + ...settings, + }; + + await this.radarrSettingsRepo.save(data); this.servarr.deleteCachedRadarrApiClient(settings.id); this.logger.log('Radarr settings updated'); @@ -284,25 +257,15 @@ export class SettingsService implements SettingDto { try { settings.url = settings.url.toLowerCase(); - return this.sonarrSettingsRepo.manager.transaction(async (manager) => { - if (settings.isDefault) { - await manager - .withRepository(this.sonarrSettingsRepo) - .update({ isDefault: true }, { isDefault: false }); - } - - const savedSetting = await manager - .withRepository(this.sonarrSettingsRepo) - .save(settings); + const savedSetting = await this.sonarrSettingsRepo.save(settings); - this.logger.log('Sonarr setting added'); - return { - data: savedSetting, - status: 'OK', - code: 1, - message: 'Success', - }; - }); + this.logger.log('Sonarr setting added'); + return { + data: savedSetting, + status: 'OK', + code: 1, + message: 'Success', + }; } catch (e) { this.logger.error('Error while adding Sonarr setting: ', e); return { status: 'NOK', code: 0, message: 'Failure' }; @@ -315,33 +278,16 @@ export class SettingsService implements SettingDto { try { settings.url = settings.url.toLowerCase(); - const data = await this.sonarrSettingsRepo.manager.transaction( - async (manager) => { - const settingsDb = await manager - .withRepository(this.sonarrSettingsRepo) - .findOne({ - where: { id: settings.id }, - }); - - const data = { - ...settingsDb, - ...settings, - }; - - await manager.withRepository(this.sonarrSettingsRepo).save(data); - - if (settings.isDefault) { - await manager - .withRepository(this.sonarrSettingsRepo) - .update( - { isDefault: true, id: Not(settings.id) }, - { isDefault: false }, - ); - } + const settingsDb = await this.sonarrSettingsRepo.findOne({ + where: { id: settings.id }, + }); - return data; - }, - ); + const data = { + ...settingsDb, + ...settings, + }; + + await this.sonarrSettingsRepo.save(data); this.servarr.deleteCachedSonarrApiClient(settings.id); diff --git a/ui/src/components/Rules/RuleGroup/AddModal/ArrAction/index.tsx b/ui/src/components/Rules/RuleGroup/AddModal/ArrAction/index.tsx index b8a518df..b52a3751 100644 --- a/ui/src/components/Rules/RuleGroup/AddModal/ArrAction/index.tsx +++ b/ui/src/components/Rules/RuleGroup/AddModal/ArrAction/index.tsx @@ -8,9 +8,9 @@ type ArrType = 'Radarr' | 'Sonarr' interface ArrActionProps { type: ArrType arrAction?: number - settingId?: number - options?: Option[] - onUpdate: (value: number, settingId?: number) => void + settingId?: number | null // null for when the user has selected 'None', undefined for when this is a new rule + options: Option[] + onUpdate: (arrAction: number, settingId?: number | null) => void } interface Option { @@ -19,15 +19,17 @@ interface Option { } const ArrAction = (props: ArrActionProps) => { - const [prevType, setPrevType] = useState(props.type) + const selectedSetting = + props.settingId === undefined ? '-1' : (props.settingId?.toString() ?? '') const [settings, setSettings] = useState<(IRadarrSetting | ISonarrSetting)[]>( [], ) const [loading, setLoading] = useState(true) - const action = props.arrAction ? props.arrAction : 0 + const action = props.arrAction ?? 0 - const handleSelectedSettingIdChange = (id?: number) => { - props.onUpdate(action, id) + const handleSelectedSettingIdChange = (id?: number | null) => { + const actionUpdate = id == null ? 0 : action + props.onUpdate(actionUpdate, id) } const handleActionChange = (value: number) => { @@ -40,19 +42,15 @@ const ArrAction = (props: ArrActionProps) => { const settingsResponse = await GetApiHandler( `/settings/${type.toLowerCase()}`, ) - setPrevType(type) setSettings(settingsResponse) setLoading(false) - if (!props.settingId || type != prevType) { - if (settingsResponse.length > 0) { - const defaultServer = settingsResponse.find((s) => s.isDefault) - handleSelectedSettingIdChange( - defaultServer ? defaultServer.id : settingsResponse[0]?.id, - ) - } else { - handleSelectedSettingIdChange(undefined) - } + // The selected server does not exist anymore (old client data potentially) so deselect + if ( + props.settingId && + settingsResponse.find((x) => x.id === props.settingId) == null + ) { + handleSelectedSettingIdChange(undefined) } } @@ -60,39 +58,39 @@ const ArrAction = (props: ArrActionProps) => { loadArrSettings(props.type) }, [props.type]) - const options: Option[] = props.options - ? props.options - : [ + const noneServerSelected = selectedSetting === '' + + const options: Option[] = noneServerSelected + ? [ { id: 0, name: 'Delete', }, - { - id: 1, - name: 'Unmonitor and delete files', - }, - { - id: 3, - name: 'Unmonitor and keep files', - }, ] + : props.options return (
setIsDefault(e.target.checked)} - /> -
-
-
-