diff --git a/src/__generated__/generated-radarr-api.ts b/src/__generated__/generated-radarr-api.ts index 4406eab..e29f8ce 100644 --- a/src/__generated__/generated-radarr-api.ts +++ b/src/__generated__/generated-radarr-api.ts @@ -151,6 +151,8 @@ export interface CollectionMovieResource { ratings?: Ratings; genres?: string[] | null; folder?: string | null; + isExisting?: boolean; + isExcluded?: boolean; } export interface CollectionResource { @@ -191,6 +193,7 @@ export enum ColonReplacementFormat { Dash = "dash", SpaceDash = "spaceDash", SpaceDashSpace = "spaceDashSpace", + Smart = "smart", } export interface Command { @@ -233,7 +236,8 @@ export interface CommandResource { started?: string | null; /** @format date-time */ ended?: string | null; - duration?: TimeSpan; + /** @format date-span */ + duration?: string | null; exception?: string | null; trigger?: CommandTrigger; clientUserAgent?: string | null; @@ -298,6 +302,12 @@ export interface CustomFilterResource { filters?: Record[] | null; } +export interface CustomFormatBulkResource { + /** @uniqueItems true */ + ids?: number[] | null; + includeCustomFormatWhenRenaming?: boolean | null; +} + export interface CustomFormatResource { /** @format int32 */ id?: number; @@ -413,6 +423,8 @@ export interface ExtraFileResource { movieFileId?: number | null; relativePath?: string | null; extension?: string | null; + languageTags?: string[] | null; + title?: string | null; type?: ExtraFileType; } @@ -514,6 +526,8 @@ export interface HostConfigResource { password?: string | null; passwordConfirmation?: string | null; logLevel?: string | null; + /** @format int32 */ + logSizeLimit?: number; consoleLogLevel?: string | null; branch?: string | null; apiKey?: string | null; @@ -553,7 +567,30 @@ export interface HttpUri { fragment?: string | null; } -export interface ImportExclusionsResource { +export interface ImportListBulkResource { + ids?: number[] | null; + tags?: number[] | null; + applyTags?: ApplyTags; + enabled?: boolean | null; + enableAuto?: boolean | null; + rootFolderPath?: string | null; + /** @format int32 */ + qualityProfileId?: number | null; + minimumAvailability?: MovieStatusType; +} + +export interface ImportListConfigResource { + /** @format int32 */ + id?: number; + listSyncLevel?: string | null; +} + +export interface ImportListExclusionBulkResource { + /** @uniqueItems true */ + ids?: number[] | null; +} + +export interface ImportListExclusionResource { /** @format int32 */ id?: number; name?: string | null; @@ -565,7 +602,7 @@ export interface ImportExclusionsResource { message?: ProviderMessage; /** @uniqueItems true */ tags?: number[] | null; - presets?: ImportExclusionsResource[] | null; + presets?: ImportListExclusionResource[] | null; /** @format int32 */ tmdbId?: number; movieTitle?: string | null; @@ -573,23 +610,16 @@ export interface ImportExclusionsResource { movieYear?: number; } -export interface ImportListBulkResource { - ids?: number[] | null; - tags?: number[] | null; - applyTags?: ApplyTags; - enabled?: boolean | null; - enableAuto?: boolean | null; - rootFolderPath?: string | null; +export interface ImportListExclusionResourcePagingResource { /** @format int32 */ - qualityProfileId?: number | null; - minimumAvailability?: MovieStatusType; -} - -export interface ImportListConfigResource { + page?: number; /** @format int32 */ - id?: number; - listSyncLevel?: string | null; - importExclusions?: string | null; + pageSize?: number; + sortKey?: string | null; + sortDirection?: SortDirection; + /** @format int32 */ + totalRecords?: number; + records?: ImportListExclusionResource[] | null; } export interface ImportListResource { @@ -616,7 +646,8 @@ export interface ImportListResource { listType?: ImportListType; /** @format int32 */ listOrder?: number; - minRefreshInterval?: TimeSpan; + /** @format date-span */ + minRefreshInterval?: string; } export enum ImportListType { @@ -982,6 +1013,8 @@ export interface MovieResource { physicalRelease?: string | null; /** @format date-time */ digitalRelease?: string | null; + /** @format date-time */ + releaseDate?: string | null; physicalReleaseNote?: string | null; images?: MediaCover[] | null; website?: string | null; @@ -994,6 +1027,8 @@ export interface MovieResource { /** @format int32 */ qualityProfileId?: number; hasFile?: boolean | null; + /** @format int32 */ + movieFileId?: number; monitored?: boolean; minimumAvailability?: MovieStatusType; isAvailable?: boolean; @@ -1019,9 +1054,23 @@ export interface MovieResource { collection?: MovieCollectionResource; /** @format float */ popularity?: number; + /** @format date-time */ + lastSearchTime?: string | null; statistics?: MovieStatisticsResource; } +export interface MovieResourcePagingResource { + /** @format int32 */ + page?: number; + /** @format int32 */ + pageSize?: number; + sortKey?: string | null; + sortDirection?: SortDirection; + /** @format int32 */ + totalRecords?: number; + records?: MovieResource[] | null; +} + export enum MovieRuntimeFormatType { HoursMinutes = "hoursMinutes", Minutes = "minutes", @@ -1076,6 +1125,7 @@ export interface NotificationResource { onMovieFileDelete?: boolean; onMovieFileDeleteForUpgrade?: boolean; onHealthIssue?: boolean; + includeHealthWarnings?: boolean; onHealthRestored?: boolean; onApplicationUpdate?: boolean; onManualInteractionRequired?: boolean; @@ -1091,7 +1141,6 @@ export interface NotificationResource { supportsOnHealthRestored?: boolean; supportsOnApplicationUpdate?: boolean; supportsOnManualInteractionRequired?: boolean; - includeHealthWarnings?: boolean; testCommand?: string | null; } @@ -1222,6 +1271,8 @@ export interface QualityProfileResource { minFormatScore?: number; /** @format int32 */ cutoffFormatScore?: number; + /** @format int32 */ + minUpgradeFormatScore?: number; formatItems?: ProfileFormatItemResource[] | null; language?: Language; } @@ -1259,7 +1310,8 @@ export interface QueueResource { title?: string | null; /** @format double */ sizeleft?: number; - timeleft?: TimeSpan; + /** @format date-span */ + timeleft?: string | null; /** @format date-time */ estimatedCompletionTime?: string | null; /** @format date-time */ @@ -1322,6 +1374,7 @@ export interface Ratings { tmdb?: RatingChild; metacritic?: RatingChild; rottenTomatoes?: RatingChild; + trakt?: RatingChild; } export interface Rejection { @@ -1401,7 +1454,7 @@ export interface ReleaseResource { /** @format int32 */ leechers?: number | null; protocol?: DownloadProtocol; - indexerFlags?: string[] | null; + indexerFlags?: any; /** @format int32 */ movieId?: number | null; /** @format int32 */ @@ -1504,12 +1557,12 @@ export interface SystemResource { mode?: RuntimeMode; branch?: string | null; databaseType?: DatabaseType; - databaseVersion?: Version; + databaseVersion?: string | null; authentication?: AuthenticationType; /** @format int32 */ migrationVersion?: number; urlBase?: string | null; - runtimeVersion?: Version; + runtimeVersion?: string | null; runtimeName?: string | null; /** @format date-time */ startTime?: string; @@ -1566,36 +1619,13 @@ export interface TaskResource { lastStartTime?: string; /** @format date-time */ nextExecution?: string; - lastDuration?: TimeSpan; -} - -export interface TimeSpan { - /** @format int64 */ - ticks?: number; - /** @format int32 */ - days?: number; - /** @format int32 */ - hours?: number; - /** @format int32 */ - milliseconds?: number; - /** @format int32 */ - minutes?: number; - /** @format int32 */ - seconds?: number; - /** @format double */ - totalDays?: number; - /** @format double */ - totalHours?: number; - /** @format double */ - totalMilliseconds?: number; - /** @format double */ - totalMinutes?: number; - /** @format double */ - totalSeconds?: number; + /** @format date-span */ + lastDuration?: string; } export enum TrackedDownloadState { Downloading = "downloading", + ImportBlocked = "importBlocked", ImportPending = "importPending", Importing = "importing", Imported = "imported", @@ -1656,7 +1686,7 @@ export enum UpdateMechanism { export interface UpdateResource { /** @format int32 */ id?: number; - version?: Version; + version?: string | null; branch?: string | null; /** @format date-time */ releaseDate?: string; @@ -1671,21 +1701,6 @@ export interface UpdateResource { hash?: string | null; } -export interface Version { - /** @format int32 */ - major?: number; - /** @format int32 */ - minor?: number; - /** @format int32 */ - build?: number; - /** @format int32 */ - revision?: number; - /** @format int32 */ - majorRevision?: number; - /** @format int32 */ - minorRevision?: number; -} - import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, HeadersDefaults, ResponseType } from "axios"; import axios from "axios"; @@ -1709,7 +1724,9 @@ export interface FullRequestParams extends Omit; export interface ApiConfig extends Omit { - securityWorker?: (securityData: SecurityDataType | null) => Promise | AxiosRequestConfig | void; + securityWorker?: ( + securityData: SecurityDataType | null, + ) => Promise | AxiosRequestConfig | void; secure?: boolean; format?: ResponseType; } @@ -1763,6 +1780,9 @@ export class HttpClient { } protected createFormData(input: Record): FormData { + if (input instanceof FormData) { + return input; + } return Object.keys(input || {}).reduce((formData, key) => { const property = input[key]; const propertyContent: any[] = property instanceof Array ? property : [property]; @@ -1786,7 +1806,10 @@ export class HttpClient { ...params }: FullRequestParams): Promise> => { const secureParams = - ((typeof secure === "boolean" ? secure : this.secure) && this.securityWorker && (await this.securityWorker(this.securityData))) || {}; + ((typeof secure === "boolean" ? secure : this.secure) && + this.securityWorker && + (await this.securityWorker(this.securityData))) || + {}; const requestParams = this.mergeRequestParams(params, secureParams); const responseFormat = format || this.format || undefined; @@ -1802,7 +1825,7 @@ export class HttpClient { ...requestParams, headers: { ...(requestParams.headers || {}), - ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}), + ...(type ? { "Content-Type": type } : {}), }, params: query, responseType: responseFormat, @@ -2089,6 +2112,8 @@ export class Api extends HttpClient @@ -2189,23 +2214,6 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/calendar/${id}`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - /** * No description * @@ -2489,17 +2497,15 @@ export class Api extends HttpClient - this.request({ + v3CustomformatList: (params: RequestParams = {}) => + this.request({ path: `/api/v3/customformat`, - method: "POST", - body: data, + method: "GET", secure: true, - type: ContentType.Json, format: "json", ...params, }), @@ -2508,15 +2514,17 @@ export class Api extends HttpClient - this.request({ + v3CustomformatCreate: (data: CustomFormatResource, params: RequestParams = {}) => + this.request({ path: `/api/v3/customformat`, - method: "GET", + method: "POST", + body: data, secure: true, + type: ContentType.Json, format: "json", ...params, }), @@ -2573,6 +2581,43 @@ export class Api extends HttpClient + this.request({ + path: `/api/v3/customformat/bulk`, + method: "PUT", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params, + }), + + /** + * No description + * + * @tags CustomFormat + * @name V3CustomformatBulkDelete + * @request DELETE:/api/v3/customformat/bulk + * @secure + */ + v3CustomformatBulkDelete: (data: CustomFormatBulkResource, params: RequestParams = {}) => + this.request({ + path: `/api/v3/customformat/bulk`, + method: "DELETE", + body: data, + secure: true, + type: ContentType.Json, + ...params, + }), + /** * No description * @@ -2589,6 +2634,42 @@ export class Api extends HttpClient + this.request({ + path: `/api/v3/wanted/cutoff`, + method: "GET", + query: query, + secure: true, + format: "json", + ...params, + }), + /** * No description * @@ -2677,6 +2758,31 @@ export class Api extends HttpClient + this.request({ + path: `/api/v3/delayprofile/reorder/${id}`, + method: "PUT", + query: query, + secure: true, + format: "json", + ...params, + }), + /** * No description * @@ -2861,10 +2967,18 @@ export class Api extends HttpClient + v3DownloadclientTestCreate: ( + data: DownloadClientResource, + query?: { + /** @default false */ + forceTest?: boolean; + }, + params: RequestParams = {}, + ) => this.request({ path: `/api/v3/downloadclient/test`, method: "POST", + query: query, body: data, secure: true, type: ContentType.Json, @@ -3069,23 +3183,6 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/health/${id}`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - /** * No description * @@ -3252,14 +3349,14 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/exclusions`, + v3ImportlistList: (params: RequestParams = {}) => + this.request({ + path: `/api/v3/importlist`, method: "GET", secure: true, format: "json", @@ -3269,15 +3366,23 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/exclusions`, + v3ImportlistCreate: ( + data: ImportListResource, + query?: { + /** @default false */ + forceSave?: boolean; + }, + params: RequestParams = {}, + ) => + this.request({ + path: `/api/v3/importlist`, method: "POST", + query: query, body: data, secure: true, type: ContentType.Json, @@ -3288,123 +3393,9 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/exclusions/${id}`, - method: "PUT", - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params, - }), - - /** - * No description - * - * @tags ImportExclusions - * @name V3ExclusionsDelete - * @request DELETE:/api/v3/exclusions/{id} - * @secure - */ - v3ExclusionsDelete: (id: number, params: RequestParams = {}) => - this.request({ - path: `/api/v3/exclusions/${id}`, - method: "DELETE", - secure: true, - ...params, - }), - - /** - * No description - * - * @tags ImportExclusions - * @name V3ExclusionsDetail - * @request GET:/api/v3/exclusions/{id} - * @secure - */ - v3ExclusionsDetail: (id: number, params: RequestParams = {}) => - this.request({ - path: `/api/v3/exclusions/${id}`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - - /** - * No description - * - * @tags ImportExclusions - * @name V3ExclusionsBulkCreate - * @request POST:/api/v3/exclusions/bulk - * @secure - */ - v3ExclusionsBulkCreate: (data: ImportExclusionsResource[], params: RequestParams = {}) => - this.request({ - path: `/api/v3/exclusions/bulk`, - method: "POST", - body: data, - secure: true, - type: ContentType.Json, - ...params, - }), - - /** - * No description - * - * @tags ImportList - * @name V3ImportlistList - * @request GET:/api/v3/importlist - * @secure - */ - v3ImportlistList: (params: RequestParams = {}) => - this.request({ - path: `/api/v3/importlist`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - - /** - * No description - * - * @tags ImportList - * @name V3ImportlistCreate - * @request POST:/api/v3/importlist - * @secure - */ - v3ImportlistCreate: ( - data: ImportListResource, - query?: { - /** @default false */ - forceSave?: boolean; - }, - params: RequestParams = {}, - ) => - this.request({ - path: `/api/v3/importlist`, - method: "POST", - query: query, - body: data, - secure: true, - type: ContentType.Json, - format: "json", - ...params, - }), - - /** - * No description - * - * @tags ImportList - * @name V3ImportlistUpdate - * @request PUT:/api/v3/importlist/{id} + * @tags ImportList + * @name V3ImportlistUpdate + * @request PUT:/api/v3/importlist/{id} * @secure */ v3ImportlistUpdate: ( @@ -3522,10 +3513,18 @@ export class Api extends HttpClient + v3ImportlistTestCreate: ( + data: ImportListResource, + query?: { + /** @default false */ + forceTest?: boolean; + }, + params: RequestParams = {}, + ) => this.request({ path: `/api/v3/importlist/test`, method: "POST", + query: query, body: data, secure: true, type: ContentType.Json, @@ -3619,6 +3618,165 @@ export class Api extends HttpClient + this.request({ + path: `/api/v3/exclusions`, + method: "GET", + secure: true, + format: "json", + ...params, + }), + + /** + * No description + * + * @tags ImportListExclusion + * @name V3ExclusionsCreate + * @request POST:/api/v3/exclusions + * @secure + */ + v3ExclusionsCreate: (data: ImportListExclusionResource, params: RequestParams = {}) => + this.request({ + path: `/api/v3/exclusions`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params, + }), + + /** + * No description + * + * @tags ImportListExclusion + * @name V3ExclusionsPagedList + * @request GET:/api/v3/exclusions/paged + * @secure + */ + v3ExclusionsPagedList: ( + query?: { + /** + * @format int32 + * @default 1 + */ + page?: number; + /** + * @format int32 + * @default 10 + */ + pageSize?: number; + sortKey?: string; + sortDirection?: SortDirection; + }, + params: RequestParams = {}, + ) => + this.request({ + path: `/api/v3/exclusions/paged`, + method: "GET", + query: query, + secure: true, + format: "json", + ...params, + }), + + /** + * No description + * + * @tags ImportListExclusion + * @name V3ExclusionsUpdate + * @request PUT:/api/v3/exclusions/{id} + * @secure + */ + v3ExclusionsUpdate: (id: string, data: ImportListExclusionResource, params: RequestParams = {}) => + this.request({ + path: `/api/v3/exclusions/${id}`, + method: "PUT", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params, + }), + + /** + * No description + * + * @tags ImportListExclusion + * @name V3ExclusionsDelete + * @request DELETE:/api/v3/exclusions/{id} + * @secure + */ + v3ExclusionsDelete: (id: number, params: RequestParams = {}) => + this.request({ + path: `/api/v3/exclusions/${id}`, + method: "DELETE", + secure: true, + ...params, + }), + + /** + * No description + * + * @tags ImportListExclusion + * @name V3ExclusionsDetail + * @request GET:/api/v3/exclusions/{id} + * @secure + */ + v3ExclusionsDetail: (id: number, params: RequestParams = {}) => + this.request({ + path: `/api/v3/exclusions/${id}`, + method: "GET", + secure: true, + format: "json", + ...params, + }), + + /** + * No description + * + * @tags ImportListExclusion + * @name V3ExclusionsBulkCreate + * @request POST:/api/v3/exclusions/bulk + * @secure + */ + v3ExclusionsBulkCreate: (data: ImportListExclusionResource[], params: RequestParams = {}) => + this.request({ + path: `/api/v3/exclusions/bulk`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + ...params, + }), + + /** + * No description + * + * @tags ImportListExclusion + * @name V3ExclusionsBulkDelete + * @request DELETE:/api/v3/exclusions/bulk + * @secure + */ + v3ExclusionsBulkDelete: (data: ImportListExclusionBulkResource, params: RequestParams = {}) => + this.request({ + path: `/api/v3/exclusions/bulk`, + method: "DELETE", + body: data, + secure: true, + type: ContentType.Json, + ...params, + }), + /** * No description * @@ -3831,10 +3989,18 @@ export class Api extends HttpClient + v3IndexerTestCreate: ( + data: IndexerResource, + query?: { + /** @default false */ + forceTest?: boolean; + }, + params: RequestParams = {}, + ) => this.request({ path: `/api/v3/indexer/test`, method: "POST", + query: query, body: data, secure: true, type: ContentType.Json, @@ -4326,10 +4492,18 @@ export class Api extends HttpClient + v3MetadataTestCreate: ( + data: MetadataResource, + query?: { + /** @default false */ + forceTest?: boolean; + }, + params: RequestParams = {}, + ) => this.request({ path: `/api/v3/metadata/test`, method: "POST", + query: query, body: data, secure: true, type: ContentType.Json, @@ -4423,6 +4597,42 @@ export class Api extends HttpClient + this.request({ + path: `/api/v3/wanted/missing`, + method: "GET", + query: query, + secure: true, + format: "json", + ...params, + }), + /** * No description * @@ -4437,6 +4647,8 @@ export class Api extends HttpClient @@ -4705,23 +4917,6 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/movie/import/${id}`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - /** * No description * @@ -4789,23 +4984,6 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/movie/lookup/${id}`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - /** * No description * @@ -5018,10 +5196,18 @@ export class Api extends HttpClient + v3NotificationTestCreate: ( + data: NotificationResource, + query?: { + /** @default false */ + forceTest?: boolean; + }, + params: RequestParams = {}, + ) => this.request({ path: `/api/v3/notification/test`, method: "POST", + query: query, body: data, secure: true, type: ContentType.Json, @@ -5291,23 +5477,6 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/queue/${id}`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - /** * No description * @@ -5443,23 +5612,6 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/queue/details/${id}`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - /** * No description * @@ -5477,23 +5629,6 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/queue/status/${id}`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - /** * No description * @@ -5536,23 +5671,6 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/release/${id}`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - /** * No description * @@ -5660,23 +5778,6 @@ export class Api extends HttpClient - this.request({ - path: `/api/v3/release/push/${id}`, - method: "GET", - secure: true, - format: "json", - ...params, - }), - /** * No description * @@ -6314,6 +6415,23 @@ export class Api extends HttpClient + this.request({ + path: `/ping`, + method: "HEAD", + secure: true, + format: "json", + ...params, + }), }; content = { /** diff --git a/src/__generated__/generated-sonarr-api.ts b/src/__generated__/generated-sonarr-api.ts index 4565df3..d62664a 100644 --- a/src/__generated__/generated-sonarr-api.ts +++ b/src/__generated__/generated-sonarr-api.ts @@ -163,7 +163,8 @@ export interface CommandResource { started?: string | null; /** @format date-time */ ended?: string | null; - duration?: TimeSpan; + /** @format date-span */ + duration?: string | null; exception?: string | null; trigger?: CommandTrigger; clientUserAgent?: string | null; @@ -205,6 +206,12 @@ export interface CustomFilterResource { filters?: Record[] | null; } +export interface CustomFormatBulkResource { + /** @uniqueItems true */ + ids?: number[] | null; + includeCustomFormatWhenRenaming?: boolean | null; +} + export interface CustomFormatResource { /** @format int32 */ id?: number; @@ -339,8 +346,7 @@ export interface EpisodeFileResource { customFormatScore?: number; /** @format int32 */ indexerFlags?: number | null; - /** @format int32 */ - releaseType?: number | null; + releaseType?: ReleaseType; mediaInfo?: MediaInfoResource; qualityCutoffNotMet?: boolean; } @@ -373,6 +379,8 @@ export interface EpisodeResource { airDate?: string | null; /** @format date-time */ airDateUtc?: string | null; + /** @format date-time */ + lastSearchTime?: string | null; /** @format int32 */ runtime?: number; finaleType?: string | null; @@ -393,10 +401,8 @@ export interface EpisodeResource { endTime?: string | null; /** @format date-time */ grabDate?: string | null; - seriesTitle?: string | null; series?: SeriesResource; images?: MediaCover[] | null; - grabbed?: boolean; } export interface EpisodeResourcePagingResource { @@ -517,6 +523,8 @@ export interface HostConfigResource { password?: string | null; passwordConfirmation?: string | null; logLevel?: string | null; + /** @format int32 */ + logSizeLimit?: number; consoleLogLevel?: string | null; branch?: string | null; apiKey?: string | null; @@ -574,6 +582,11 @@ export interface ImportListConfigResource { listSyncTag?: number; } +export interface ImportListExclusionBulkResource { + /** @uniqueItems true */ + ids?: number[] | null; +} + export interface ImportListExclusionResource { /** @format int32 */ id?: number; @@ -619,7 +632,8 @@ export interface ImportListResource { listType?: ImportListType; /** @format int32 */ listOrder?: number; - minRefreshInterval?: TimeSpan; + /** @format date-span */ + minRefreshInterval?: string; } export enum ImportListType { @@ -938,6 +952,7 @@ export interface NamingConfigResource { replaceIllegalCharacters?: boolean; /** @format int32 */ colonReplacementFormat?: number; + customColonReplacementFormat?: string | null; /** @format int32 */ multiEpisodeStyle?: number; standardEpisodeFormat?: string | null; @@ -970,18 +985,21 @@ export interface NotificationResource { onGrab?: boolean; onDownload?: boolean; onUpgrade?: boolean; + onImportComplete?: boolean; onRename?: boolean; onSeriesAdd?: boolean; onSeriesDelete?: boolean; onEpisodeFileDelete?: boolean; onEpisodeFileDeleteForUpgrade?: boolean; onHealthIssue?: boolean; + includeHealthWarnings?: boolean; onHealthRestored?: boolean; onApplicationUpdate?: boolean; onManualInteractionRequired?: boolean; supportsOnGrab?: boolean; supportsOnDownload?: boolean; supportsOnUpgrade?: boolean; + supportsOnImportComplete?: boolean; supportsOnRename?: boolean; supportsOnSeriesAdd?: boolean; supportsOnSeriesDelete?: boolean; @@ -991,7 +1009,6 @@ export interface NotificationResource { supportsOnHealthRestored?: boolean; supportsOnApplicationUpdate?: boolean; supportsOnManualInteractionRequired?: boolean; - includeHealthWarnings?: boolean; testCommand?: string | null; } @@ -1025,6 +1042,7 @@ export interface ParsedEpisodeInfo { isMultiSeason?: boolean; isSeasonExtra?: boolean; isSplitEpisode?: boolean; + isMiniSeries?: boolean; special?: boolean; releaseGroup?: string | null; releaseHash?: string | null; @@ -1093,6 +1111,13 @@ export interface Quality { resolution?: number; } +export interface QualityDefinitionLimitsResource { + /** @format int32 */ + min?: number; + /** @format int32 */ + max?: number; +} + export interface QualityDefinitionResource { /** @format int32 */ id?: number; @@ -1134,6 +1159,8 @@ export interface QualityProfileResource { minFormatScore?: number; /** @format int32 */ cutoffFormatScore?: number; + /** @format int32 */ + minUpgradeFormatScore?: number; formatItems?: ProfileFormatItemResource[] | null; } @@ -1173,7 +1200,8 @@ export interface QueueResource { title?: string | null; /** @format double */ sizeleft?: number; - timeleft?: TimeSpan; + /** @format date-span */ + timeleft?: string | null; /** @format date-time */ estimatedCompletionTime?: string | null; /** @format date-time */ @@ -1308,6 +1336,7 @@ export interface ReleaseResource { tvdbId?: number; /** @format int32 */ tvRageId?: number; + imdbId?: string | null; rejections?: string[] | null; /** @format date-time */ publishDate?: string; @@ -1505,6 +1534,8 @@ export interface SeriesResource { tvRageId?: number; /** @format int32 */ tvMazeId?: number; + /** @format int32 */ + tmdbId?: number; /** @format date-time */ firstAired?: string | null; /** @format date-time */ @@ -1597,11 +1628,11 @@ export interface SystemResource { mode?: RuntimeMode; branch?: string | null; authentication?: AuthenticationType; - sqliteVersion?: Version; + sqliteVersion?: string | null; /** @format int32 */ migrationVersion?: number; urlBase?: string | null; - runtimeVersion?: Version; + runtimeVersion?: string | null; runtimeName?: string | null; /** @format date-time */ startTime?: string; @@ -1609,7 +1640,7 @@ export interface SystemResource { packageAuthor?: string | null; packageUpdateMechanism?: UpdateMechanism; packageUpdateMechanismMessage?: string | null; - databaseVersion?: Version; + databaseVersion?: string | null; databaseType?: DatabaseType; } @@ -1646,36 +1677,13 @@ export interface TaskResource { lastStartTime?: string; /** @format date-time */ nextExecution?: string; - lastDuration?: TimeSpan; -} - -export interface TimeSpan { - /** @format int64 */ - ticks?: number; - /** @format int32 */ - days?: number; - /** @format int32 */ - hours?: number; - /** @format int32 */ - milliseconds?: number; - /** @format int32 */ - minutes?: number; - /** @format int32 */ - seconds?: number; - /** @format double */ - totalDays?: number; - /** @format double */ - totalHours?: number; - /** @format double */ - totalMilliseconds?: number; - /** @format double */ - totalMinutes?: number; - /** @format double */ - totalSeconds?: number; + /** @format date-span */ + lastDuration?: string; } export enum TrackedDownloadState { Downloading = "downloading", + ImportBlocked = "importBlocked", ImportPending = "importPending", Importing = "importing", Imported = "imported", @@ -1733,7 +1741,7 @@ export enum UpdateMechanism { export interface UpdateResource { /** @format int32 */ id?: number; - version?: Version; + version?: string | null; branch?: string | null; /** @format date-time */ releaseDate?: string; @@ -1748,21 +1756,6 @@ export interface UpdateResource { hash?: string | null; } -export interface Version { - /** @format int32 */ - major?: number; - /** @format int32 */ - minor?: number; - /** @format int32 */ - build?: number; - /** @format int32 */ - revision?: number; - /** @format int32 */ - majorRevision?: number; - /** @format int32 */ - minorRevision?: number; -} - import type { AxiosInstance, AxiosRequestConfig, AxiosResponse, HeadersDefaults, ResponseType } from "axios"; import axios from "axios"; @@ -1786,7 +1779,9 @@ export interface FullRequestParams extends Omit; export interface ApiConfig extends Omit { - securityWorker?: (securityData: SecurityDataType | null) => Promise | AxiosRequestConfig | void; + securityWorker?: ( + securityData: SecurityDataType | null, + ) => Promise | AxiosRequestConfig | void; secure?: boolean; format?: ResponseType; } @@ -1840,6 +1835,9 @@ export class HttpClient { } protected createFormData(input: Record): FormData { + if (input instanceof FormData) { + return input; + } return Object.keys(input || {}).reduce((formData, key) => { const property = input[key]; const propertyContent: any[] = property instanceof Array ? property : [property]; @@ -1863,7 +1861,10 @@ export class HttpClient { ...params }: FullRequestParams): Promise> => { const secureParams = - ((typeof secure === "boolean" ? secure : this.secure) && this.securityWorker && (await this.securityWorker(this.securityData))) || {}; + ((typeof secure === "boolean" ? secure : this.secure) && + this.securityWorker && + (await this.securityWorker(this.securityData))) || + {}; const requestParams = this.mergeRequestParams(params, secureParams); const responseFormat = format || this.format || undefined; @@ -1879,7 +1880,7 @@ export class HttpClient { ...requestParams, headers: { ...(requestParams.headers || {}), - ...(type && type !== ContentType.FormData ? { "Content-Type": type } : {}), + ...(type ? { "Content-Type": type } : {}), }, params: query, responseType: responseFormat, @@ -2122,6 +2123,8 @@ export class Api extends HttpClient @@ -2382,17 +2385,15 @@ export class Api extends HttpClient - this.request({ + v3CustomformatList: (params: RequestParams = {}) => + this.request({ path: `/api/v3/customformat`, - method: "POST", - body: data, + method: "GET", secure: true, - type: ContentType.Json, format: "json", ...params, }), @@ -2401,15 +2402,17 @@ export class Api extends HttpClient - this.request({ + v3CustomformatCreate: (data: CustomFormatResource, params: RequestParams = {}) => + this.request({ path: `/api/v3/customformat`, - method: "GET", + method: "POST", + body: data, secure: true, + type: ContentType.Json, format: "json", ...params, }), @@ -2466,6 +2469,43 @@ export class Api extends HttpClient + this.request({ + path: `/api/v3/customformat/bulk`, + method: "PUT", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params, + }), + + /** + * No description + * + * @tags CustomFormat + * @name V3CustomformatBulkDelete + * @request DELETE:/api/v3/customformat/bulk + * @secure + */ + v3CustomformatBulkDelete: (data: CustomFormatBulkResource, params: RequestParams = {}) => + this.request({ + path: `/api/v3/customformat/bulk`, + method: "DELETE", + body: data, + secure: true, + type: ContentType.Json, + ...params, + }), + /** * No description * @@ -2838,10 +2878,18 @@ export class Api extends HttpClient + v3DownloadclientTestCreate: ( + data: DownloadClientResource, + query?: { + /** @default false */ + forceTest?: boolean; + }, + params: RequestParams = {}, + ) => this.request({ path: `/api/v3/downloadclient/test`, method: "POST", + query: query, body: data, secure: true, type: ContentType.Json, @@ -2953,6 +3001,10 @@ export class Api extends HttpClient extends HttpClient + v3ImportlistTestCreate: ( + data: ImportListResource, + query?: { + /** @default false */ + forceTest?: boolean; + }, + params: RequestParams = {}, + ) => this.request({ path: `/api/v3/importlist/test`, method: "POST", + query: query, body: data, secure: true, type: ContentType.Json, @@ -3805,6 +3865,24 @@ export class Api extends HttpClient + this.request({ + path: `/api/v3/importlistexclusion/bulk`, + method: "DELETE", + body: data, + secure: true, + type: ContentType.Json, + ...params, + }), + /** * No description * @@ -3972,10 +4050,18 @@ export class Api extends HttpClient + v3IndexerTestCreate: ( + data: IndexerResource, + query?: { + /** @default false */ + forceTest?: boolean; + }, + params: RequestParams = {}, + ) => this.request({ path: `/api/v3/indexer/test`, method: "POST", + query: query, body: data, secure: true, type: ContentType.Json, @@ -4596,10 +4682,18 @@ export class Api extends HttpClient + v3MetadataTestCreate: ( + data: MetadataResource, + query?: { + /** @default false */ + forceTest?: boolean; + }, + params: RequestParams = {}, + ) => this.request({ path: `/api/v3/metadata/test`, method: "POST", + query: query, body: data, secure: true, type: ContentType.Json, @@ -4764,6 +4858,7 @@ export class Api extends HttpClient extends HttpClient + v3NotificationTestCreate: ( + data: NotificationResource, + query?: { + /** @default false */ + forceTest?: boolean; + }, + params: RequestParams = {}, + ) => this.request({ path: `/api/v3/notification/test`, method: "POST", + query: query, body: data, secure: true, type: ContentType.Json, @@ -5055,6 +5158,23 @@ export class Api extends HttpClient + this.request({ + path: `/api/v3/qualitydefinition/limits`, + method: "GET", + secure: true, + format: "json", + ...params, + }), + /** * No description * @@ -6361,6 +6481,23 @@ export class Api extends HttpClient + this.request({ + path: `/ping`, + method: "HEAD", + secure: true, + format: "json", + ...params, + }), }; content = { /** diff --git a/src/custom-formats.ts b/src/custom-formats.ts index 17792cf..aaa7187 100644 --- a/src/custom-formats.ts +++ b/src/custom-formats.ts @@ -67,8 +67,8 @@ export const manageCf = async (cfProcessing: CFProcessing, serverCfs: Map { + const server: QualityDefinitionResource[] = [ + { + quality: { + id: 0, + name: "Unknown", + source: QualitySource.Unknown, + resolution: 0, + }, + title: "Unknown", + weight: 1, + minSize: 1, + maxSize: 199.9, + preferredSize: 194.9, + id: 1, + }, + { + quality: { + id: 1, + name: "SDTV", + source: QualitySource.Television, + resolution: 480, + }, + title: "SDTV", + weight: 2, + minSize: 2, + maxSize: 100, + preferredSize: 95, + id: 2, + }, + ]; + + const client = { + trash_id: "aed34b9f60ee115dfa7918b742336277", + type: "movie", + qualities: [ + { + quality: "SDTV", + min: 2, + preferred: 95, + max: 100, + }, + ], + }; + + test("calculateQualityDefinitionDiff - no diff", async ({}) => { + const result = calculateQualityDefinitionDiff(server, client); + + expect(result.changeMap.size).toBe(0); + expect(result.create).toHaveLength(0); + }); + + test("calculateQualityDefinitionDiff - diff min size", async ({}) => { + const clone: TrashQualityDefintion = JSON.parse(JSON.stringify(client)); + clone.qualities[0].min = 3; + + const result = calculateQualityDefinitionDiff(server, clone); + + expect(result.changeMap.size).toBe(1); + expect(result.create).toHaveLength(0); + }); + + test("calculateQualityDefinitionDiff - diff max size", async ({}) => { + const clone: TrashQualityDefintion = JSON.parse(JSON.stringify(client)); + clone.qualities[0].max = 3; + + const result = calculateQualityDefinitionDiff(server, clone); + + expect(result.changeMap.size).toBe(1); + expect(result.create).toHaveLength(0); + }); + + test("calculateQualityDefinitionDiff - diff preferred size", async ({}) => { + const clone: TrashQualityDefintion = JSON.parse(JSON.stringify(client)); + clone.qualities[0].preferred = 3; + + const result = calculateQualityDefinitionDiff(server, clone); + + expect(result.changeMap.size).toBe(1); + expect(result.create).toHaveLength(0); + }); + + test("calculateQualityDefinitionDiff - create new element", async ({}) => { + const clone: TrashQualityDefintion = JSON.parse(JSON.stringify(client)); + clone.qualities[0].quality = "New"; + + const result = calculateQualityDefinitionDiff(server, clone); + + expect(result.changeMap.size).toBe(0); + expect(result.create).toHaveLength(1); + }); +}); diff --git a/src/types.ts b/src/types.ts index f318e18..cec677f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,13 +2,17 @@ import { CustomFormatResource, CustomFormatSpecificationSchema } from "./__gener export type DynamicImportType = { default: T }; +type RequireAtLeastOne = { + [K in keyof T]-?: Required> & Partial>>; +}[keyof T]; + /** Used in the UI of Sonarr/Radarr to import. Trash JSON are based on that so users can copy&paste stuff */ export type UserFriendlyField = { - name?: string | null; + name?: string | null; // TODO validate if this can really appear? As Input value?: any; } & Pick; -export type TrashCFSpF = { min: number; max: number }; +export type TrashCFSpF = { min: number; max: number; exceptLanguage: boolean; value: any }; /* Language values: @@ -26,12 +30,12 @@ export type CustomFormatImportImplementation = export type TC1 = Omit & { implementation: "ReleaseTitleSpecification" | "LanguageSpecification"; - fields?: UserFriendlyField | null; + fields?: RequireAtLeastOne | null; }; export type TC2 = Omit & { implementation: "SizeSpecification"; - fields?: TrashCFSpF; + fields?: RequireAtLeastOne; }; export type TCM = TC1 | TC2; diff --git a/src/example.test.ts b/src/util.test.ts similarity index 61% rename from src/example.test.ts rename to src/util.test.ts index 4add609..3fd1e45 100644 --- a/src/example.test.ts +++ b/src/util.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "vitest"; -import { CustomFormatResource, PrivacyLevel, QualityDefinitionResource, QualitySource } from "./__generated__/generated-sonarr-api"; -import { calculateQualityDefinitionDiff } from "./quality-definitions"; -import { TrashCF, TrashCFSpF, TrashQualityDefintion } from "./types"; -import { compareObjectsCarr, mapImportCfToRequestCf, toCarrCF } from "./util"; +import { CustomFormatResource as CustomFormatResourceRadarr, PrivacyLevel } from "./__generated__/generated-radarr-api"; +import { CustomFormatResource } from "./__generated__/generated-sonarr-api"; +import { TrashCF, TrashCFSpF } from "./types"; +import { cloneWithJSON, compareObjectsCarr, mapImportCfToRequestCf, toCarrCF } from "./util"; const exampleCFImplementations = { name: "TestSpec", @@ -171,95 +171,54 @@ describe("SizeSpecification", async () => { }); }); -describe("QualityDefinitions", async () => { - const server: QualityDefinitionResource[] = [ - { - quality: { - id: 0, - name: "Unknown", - source: QualitySource.Unknown, - resolution: 0, - }, - title: "Unknown", - weight: 1, - minSize: 1, - maxSize: 199.9, - preferredSize: 194.9, - id: 1, - }, - { - quality: { - id: 1, - name: "SDTV", - source: QualitySource.Television, - resolution: 480, - }, - title: "SDTV", - weight: 2, - minSize: 2, - maxSize: 100, - preferredSize: 95, - id: 2, - }, - ]; +describe("compareObjectsCarr - general", async () => { + const serverResponse = (await import("../tests/samples/20240930_cf_exceptLanguage.json")) as CustomFormatResourceRadarr; - const client = { - trash_id: "aed34b9f60ee115dfa7918b742336277", - type: "movie", - qualities: [ + const custom: TrashCF = { + trash_id: "test123", + name: "Language: Not German or English", + includeCustomFormatWhenRenaming: false, + specifications: [ { - quality: "SDTV", - min: 2, - preferred: 95, - max: 100, + name: "Not German", + implementation: "LanguageSpecification", + negate: true, + required: true, + fields: { + value: 4, + }, }, ], }; - test("calculateQualityDefinitionDiff - no diff", async ({}) => { - const result = calculateQualityDefinitionDiff(server, client); - - expect(result.changeMap.size).toBe(0); - expect(result.create).toHaveLength(0); - }); - - test("calculateQualityDefinitionDiff - diff min size", async ({}) => { - const clone: TrashQualityDefintion = JSON.parse(JSON.stringify(client)); - clone.qualities[0].min = 3; - - const result = calculateQualityDefinitionDiff(server, clone); + test("should not diff for fields length bigger on remote", async () => { + const copied: typeof custom = JSON.parse(JSON.stringify(custom)); - expect(result.changeMap.size).toBe(1); - expect(result.create).toHaveLength(0); + const result = compareObjectsCarr(serverResponse, mapImportCfToRequestCf(toCarrCF(copied))); + expect(result.equal).toBe(true); }); - test("calculateQualityDefinitionDiff - diff max size", async ({}) => { - const clone: TrashQualityDefintion = JSON.parse(JSON.stringify(client)); - clone.qualities[0].max = 3; + test("should not diff for fields length equal length", async () => { + const copied: typeof custom = JSON.parse(JSON.stringify(custom)); + const clonedServer = cloneWithJSON(serverResponse); + clonedServer.specifications![0].fields = [clonedServer.specifications![0].fields![0]]; - const result = calculateQualityDefinitionDiff(server, clone); + expect(clonedServer.specifications![0].fields.length).toBe(1); - expect(result.changeMap.size).toBe(1); - expect(result.create).toHaveLength(0); + const result = compareObjectsCarr(clonedServer, mapImportCfToRequestCf(toCarrCF(copied))); + expect(result.equal).toBe(true); }); - test("calculateQualityDefinitionDiff - diff preferred size", async ({}) => { - const clone: TrashQualityDefintion = JSON.parse(JSON.stringify(client)); - clone.qualities[0].preferred = 3; - - const result = calculateQualityDefinitionDiff(server, clone); - - expect(result.changeMap.size).toBe(1); - expect(result.create).toHaveLength(0); - }); + test("should diff for fields length if local is higher (should not happen normally)", async () => { + const copied: typeof custom = JSON.parse(JSON.stringify(custom)); + copied.specifications![0].fields!.exceptLanguage = false; - test("calculateQualityDefinitionDiff - create new element", async ({}) => { - const clone: TrashQualityDefintion = JSON.parse(JSON.stringify(client)); - clone.qualities[0].quality = "New"; + const clonedServer = cloneWithJSON(serverResponse); + clonedServer.specifications![0].fields = [clonedServer.specifications![0].fields![0]]; - const result = calculateQualityDefinitionDiff(server, clone); + expect(clonedServer.specifications![0].fields.length).toBe(1); - expect(result.changeMap.size).toBe(0); - expect(result.create).toHaveLength(1); + const result = compareObjectsCarr(clonedServer, mapImportCfToRequestCf(toCarrCF(copied))); + expect(result.equal).toBe(false); }); }); diff --git a/src/util.ts b/src/util.ts index ade9ad6..6189bef 100644 --- a/src/util.ts +++ b/src/util.ts @@ -88,25 +88,8 @@ export const mapImportCfToRequestCf = (cf: TrashCF | ConfigarrCF): CustomFormatR throw new Error(`Spec is not correctly defined: ${spec.name}`); } - switch (spec.implementation) { - case "SizeSpecification": - newFields.push({ - name: "min", - value: spec.fields.min, - }); - newFields.push({ - name: "max", - value: spec.fields.max, - }); - break; - case "ReleaseTitleSpecification": - case "LanguageSpecification": - default: - newFields.push({ - value: spec.fields.value, - }); - break; - } + // 2024-09-30: Test if this handles all cases + newFields.push(...Object.entries(spec.fields).map(([key, value]) => ({ name: key, value }))); return { ...spec, @@ -123,35 +106,39 @@ export const mapImportCfToRequestCf = (cf: TrashCF | ConfigarrCF): CustomFormatR return { ...rest, specifications: specs }; }; -export function compareObjectsCarr(object1: any, object2: any): { equal: boolean; changes: string[] } { +export function compareObjectsCarr(serverObject: any, localObject: any): { equal: boolean; changes: string[] } { const changes: string[] = []; - for (const key in object2) { - if (Object.prototype.hasOwnProperty.call(object2, key)) { - if (object1.hasOwnProperty(key)) { - const value1 = object1[key]; - let value2 = object2[key]; + for (const key in localObject) { + if (Object.prototype.hasOwnProperty.call(localObject, key)) { + if (Object.prototype.hasOwnProperty.call(serverObject, key)) { + const serverProperty = serverObject[key]; + let localProperty = localObject[key]; // Todo remove should be already handled if (key === "fields") { - if (!Array.isArray(value2)) { - value2 = [value2]; + if (!Array.isArray(localProperty)) { + localProperty = [localProperty]; } } - if (Array.isArray(value1)) { - if (!Array.isArray(value2)) { - changes.push(`Expected array for key ${key} in object2`); + if (Array.isArray(serverProperty)) { + if (!Array.isArray(localProperty)) { + changes.push(`Expected array for key ${key} in localProperty`); continue; } - if (value1.length !== value2.length) { - changes.push(`Array length mismatch for key ${key}: object1 length ${value1.length}, object2 length ${value2.length}`); + if (serverProperty.length < localProperty.length) { + // Only if server does provide less props as we have -> assume change required. + // For example if radarr introduces new fields for custom formats and we do not have them included this would result in always changed results. + changes.push( + `Array length mismatch for key ${key}: serverProperty length ${serverProperty.length}, localProperty length ${localProperty.length}`, + ); continue; } - for (let i = 0; i < value1.length; i++) { - const { equal: isEqual, changes: subChanges } = compareObjectsCarr(value1[i], value2[i]); + for (let i = 0; i < serverProperty.length; i++) { + const { equal: isEqual, changes: subChanges } = compareObjectsCarr(serverProperty[i], localProperty[i]); // changes.push( // ...subChanges.map((subChange) => `${key}[${i}].${subChange}`) // ); @@ -164,20 +151,20 @@ export function compareObjectsCarr(object1: any, object2: any): { equal: boolean changes.push(`Mismatch found in array element at index ${i} for key '${key}'`); } } - } else if (typeof value2 === "object" && value2 !== null) { - if (typeof value1 !== "object" || value1 === null) { - changes.push(`Expected object for key '${key}' in object1`); + } else if (typeof localProperty === "object" && localProperty !== null) { + if (typeof serverProperty !== "object" || serverProperty === null) { + changes.push(`Expected object for key '${key}' in serverProperty`); continue; } - const { equal: isEqual, changes: subChanges } = compareObjectsCarr(value1, value2); + const { equal: isEqual, changes: subChanges } = compareObjectsCarr(serverProperty, localProperty); changes.push(...subChanges.map((subChange) => `${key}.${subChange}`)); if (!isEqual) { changes.push(`Mismatch found for key '${key}'`); } } else { - if (value1 !== value2) { - changes.push(`Mismatch found for key '${key}': server value '${value1}', value to set '${value2}'`); + if (serverProperty !== localProperty) { + changes.push(`Mismatch found for key '${key}': server value '${serverProperty}', value to set '${localProperty}'`); } } } else { diff --git a/tests/samples/20240930_cf_exceptLanguage.json b/tests/samples/20240930_cf_exceptLanguage.json new file mode 100644 index 0000000..ffb6219 --- /dev/null +++ b/tests/samples/20240930_cf_exceptLanguage.json @@ -0,0 +1,46 @@ +{ + "id": 98, + "name": "Language: Not German or English", + "includeCustomFormatWhenRenaming": false, + "specifications": [ + { + "name": "Not German", + "implementation": "LanguageSpecification", + "implementationName": "Language", + "infoLink": "https://wiki.servarr.com/radarr/settings#custom-formats-2", + "negate": true, + "required": true, + "fields": [ + { + "order": 0, + "name": "value", + "label": "Language", + "value": 4, + "type": "select", + "advanced": false, + "selectOptions": [ + { + "value": -1, + "name": "Any", + "order": 0, + "dividerAfter": false + } + ], + "privacy": "normal", + "isFloat": false + }, + { + "order": 1, + "name": "exceptLanguage", + "label": "Except Language", + "helpText": "Matches if any language other than the selected language is present", + "value": false, + "type": "checkbox", + "advanced": false, + "privacy": "normal", + "isFloat": false + } + ] + } + ] +}