pull/3664/merge
thempc 2 weeks ago committed by GitHub
commit fde747f013
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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:
@ -5024,6 +5035,9 @@ paths:
is4k:
type: boolean
example: false
isAnime:
type: boolean
example: false
serverId:
type: number
profileId:
@ -5128,6 +5142,9 @@ paths:
is4k:
type: boolean
example: false
isAnime:
type: boolean
example: false
serverId:
type: number
profileId:
@ -5769,6 +5786,9 @@ paths:
is4k:
type: boolean
example: false
isAnime:
type: boolean
example: false
responses:
'200':
description: Returned media

@ -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,9 +672,20 @@ 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
);
// Fallback for requesting anime if there is no default anime server
// This will sent the anime request to the regular default Radarr instance for single-instance setups
if (!radarrSettings && this.isAnime) {
radarrSettings = settings.radarr.find(
(radarr) => radarr.isDefault && radarr.is4k === this.is4k
);
}
if (
this.serverId !== null &&
this.serverId >= 0 &&
@ -689,9 +707,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,9 +918,20 @@ 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
);
// Fallback for requesting anime if there is no default anime server
// This will sent the anime request to the regular default Sonarr instance for single-instance setups
if (!sonarrSettings && this.isAnime) {
sonarrSettings = settings.sonarr.find(
(sonarr) => sonarr.isDefault && sonarr.is4k === this.is4k
);
}
if (
this.serverId !== null &&
this.serverId >= 0 &&
@ -924,9 +953,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 +1008,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 +1196,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;
}

@ -12,6 +12,7 @@ export type MediaRequestBody = {
tvdbId?: number;
seasons?: number[] | 'all';
is4k?: boolean;
isAnime?: boolean;
serverId?: number;
profileId?: number;
rootFolder?: string;

@ -5,6 +5,7 @@ export interface ServiceCommonServer {
id: number;
name: string;
is4k: boolean;
isAnime: boolean;
isDefault: boolean;
activeProfileId: number;
activeDirectory: string;

@ -57,6 +57,7 @@ export interface DVRSettings {
activeDirectory: string;
tags: number[];
is4k: boolean;
isAnime: boolean;
isDefault: boolean;
externalUrl?: string;
syncEnabled: boolean;

@ -0,0 +1,33 @@
import type { MigrationInterface, QueryRunner } from 'typeorm';
export class AddMediaRequestIsAnimeField1698786580184
implements MigrationInterface
{
name = 'AddMediaRequestIsAnimeField1698786580184';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "temporary_media_request" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "status" integer NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "type" varchar NOT NULL, "mediaId" integer, "requestedById" integer, "modifiedById" integer, "is4k" boolean NOT NULL DEFAULT (0), "isAnime" boolean NOT NULL DEFAULT (0), "serverId" integer, "profileId" integer, "rootFolder" varchar, "languageProfileId" integer, "tags" text, "isAutoRequest" boolean NOT NULL DEFAULT (0), CONSTRAINT "FK_a1aa713f41c99e9d10c48da75a0" FOREIGN KEY ("mediaId") REFERENCES "media" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_6997bee94720f1ecb7f31137095" FOREIGN KEY ("requestedById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_f4fc4efa14c3ba2b29c4525fa15" FOREIGN KEY ("modifiedById") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE NO ACTION)`
);
await queryRunner.query(
`INSERT INTO "temporary_media_request"("id", "status", "createdAt", "updatedAt", "type", "mediaId", "requestedById", "modifiedById", "is4k", "serverId", "profileId", "rootFolder", "languageProfileId", "tags", "isAutoRequest") SELECT "id", "status", "createdAt", "updatedAt", "type", "mediaId", "requestedById", "modifiedById", "is4k", "serverId", "profileId", "rootFolder", "languageProfileId", "tags", "isAutoRequest" FROM "media_request"`
);
await queryRunner.query(`DROP TABLE "media_request"`);
await queryRunner.query(
`ALTER TABLE "temporary_media_request" RENAME TO "media_request"`
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "media_request" RENAME TO "temporary_media_request"`
);
await queryRunner.query(
`CREATE TABLE "media_request" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "status" integer NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "type" varchar NOT NULL, "mediaId" integer, "requestedById" integer, "modifiedById" integer, "is4k" boolean NOT NULL DEFAULT (0), "serverId" integer, "profileId" integer, "rootFolder" varchar, "languageProfileId" integer, "tags" text, "isAutoRequest" boolean NOT NULL DEFAULT (0), CONSTRAINT "FK_a1aa713f41c99e9d10c48da75a0" FOREIGN KEY ("mediaId") REFERENCES "media" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_6997bee94720f1ecb7f31137095" FOREIGN KEY ("requestedById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_f4fc4efa14c3ba2b29c4525fa15" FOREIGN KEY ("modifiedById") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE NO ACTION)`
);
await queryRunner.query(
`INSERT INTO "media_request"("id", "status", "createdAt", "updatedAt", "type", "mediaId", "requestedById", "modifiedById", "is4k", "serverId", "profileId", "rootFolder", "languageProfileId", "tags", "isAutoRequest") SELECT "id", "status", "createdAt", "updatedAt", "type", "mediaId", "requestedById", "modifiedById", "is4k", "serverId", "profileId", "rootFolder", "languageProfileId", "tags", "isAutoRequest" FROM "temporary_media_request"`
);
await queryRunner.query(`DROP TABLE "temporary_media_request"`);
}
}

@ -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,

@ -24,7 +24,11 @@ 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 +96,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;
});

@ -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;
});

@ -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={() => {

@ -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={() => {

@ -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={() => {

@ -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 = ({
<div className="rounded-md">
{!!data && selectedServer !== null && (
<div className="flex flex-col md:flex-row">
{data.filter((server) => server.is4k === is4k).length > 1 && (
{data.filter(
(server) => server.is4k === is4k && server.isAnime === isAnime
).length > 1 && (
<div className="mb-3 w-full flex-shrink-0 flex-grow last:pr-0 md:w-1/4 md:pr-4">
<label htmlFor="server">
{intl.formatMessage(messages.destinationserver)}
@ -326,7 +331,10 @@ const AdvancedRequester = ({
className="border-gray-700 bg-gray-800"
>
{data
.filter((server) => server.is4k === is4k)
.filter(
(server) =>
server.is4k === is4k && server.isAnime === isAnime
)
.map((server) => (
<option
key={`server-list-${server.id}`}

@ -5,6 +5,7 @@ import AdvancedRequester from '@app/components/RequestModal/AdvancedRequester';
import QuotaDisplay from '@app/components/RequestModal/QuotaDisplay';
import { useUser } from '@app/hooks/useUser';
import globalMessages from '@app/i18n/globalMessages';
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
import { MediaStatus } from '@server/constants/media';
import type { MediaRequest } from '@server/entity/MediaRequest';
import type { QuotaResponse } from '@server/interfaces/api/userInterfaces';
@ -68,6 +69,10 @@ const MovieRequestModal = ({
: null
);
const isAnime = data?.keywords.some(
(keyword) => keyword.id === ANIME_KEYWORD_ID
);
useEffect(() => {
if (onUpdating) {
onUpdating(isUpdating);
@ -92,6 +97,7 @@ const MovieRequestModal = ({
mediaId: data?.id,
mediaType: 'movie',
is4k,
isAnime,
...overrideParams,
});
mutate('/api/v1/request?filter=all&take=10&sort=modified&skip=0');
@ -129,7 +135,16 @@ const MovieRequestModal = ({
} finally {
setIsUpdating(false);
}
}, [data, onComplete, addToast, requestOverrides, hasPermission, intl, is4k]);
}, [
data,
onComplete,
addToast,
requestOverrides,
hasPermission,
intl,
is4k,
isAnime,
]);
const cancelRequest = async () => {
setIsUpdating(true);
@ -345,6 +360,7 @@ const MovieRequestModal = ({
<AdvancedRequester
type="movie"
is4k={is4k}
isAnime={isAnime}
onChange={(overrides) => {
setRequestOverrides(overrides);
}}

@ -94,6 +94,10 @@ const TvRequestModal = ({
: null
);
const isAnime = data?.keywords.some(
(keyword) => keyword.id === ANIME_KEYWORD_ID
);
const currentlyRemaining =
(quota?.tv.remaining ?? 0) -
selectedSeasons.length +
@ -195,6 +199,7 @@ const TvRequestModal = ({
tvdbId: tvdbId ?? data?.externalIds.tvdbId,
mediaType: 'tv',
is4k,
isAnime,
seasons: settings.currentSettings.partialRequestsEnabled
? selectedSeasons
: getAllSeasons().filter(
@ -698,9 +703,7 @@ const TvRequestModal = ({
<AdvancedRequester
type="tv"
is4k={is4k}
isAnime={data?.keywords.some(
(keyword) => keyword.id === ANIME_KEYWORD_ID
)}
isAnime={isAnime}
onChange={(overrides) => setRequestOverrides(overrides)}
requestUser={editRequest?.requestedBy}
defaultOverrides={

@ -10,6 +10,7 @@ interface RequestModalProps {
type: 'movie' | 'tv' | 'collection';
tmdbId: number;
is4k?: boolean;
isAnime?: boolean;
editRequest?: MediaRequest;
onComplete?: (newStatus: MediaStatus) => void;
onCancel?: () => void;

@ -19,8 +19,12 @@ type OptionType = {
const messages = defineMessages({
createradarr: 'Add New Radarr Server',
create4kradarr: 'Add New 4K Radarr Server',
createAnimeradarr: 'Add New Anime Radarr Server',
create4kAnimeradarr: 'Add New 4K Anime Radarr Server',
editradarr: 'Edit Radarr Server',
edit4kradarr: 'Edit 4K Radarr Server',
editAnimeradarr: 'Edit Anime Radarr Server',
edit4kAnimeradarr: 'Edit 4K Anime Radarr Server',
validationNameRequired: 'You must provide a server name',
validationHostnameRequired: 'You must provide a valid hostname or IP address',
validationPortRequired: 'You must provide a valid port number',
@ -34,6 +38,8 @@ const messages = defineMessages({
add: 'Add Server',
defaultserver: 'Default Server',
default4kserver: 'Default 4K Server',
defaultAnimeserver: 'Default Anime Server',
default4kAnimeserver: 'Default 4K Anime Server',
servername: 'Server Name',
hostname: 'Hostname or IP Address',
port: 'Port',
@ -46,6 +52,7 @@ const messages = defineMessages({
rootfolder: 'Root Folder',
minimumAvailability: 'Minimum Availability',
server4k: '4K Server',
serverAnime: 'Anime Server',
selectQualityProfile: 'Select quality profile',
selectRootFolder: 'Select root folder',
selectMinimumAvailability: 'Select minimum availability',
@ -238,6 +245,7 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
tags: radarr?.tags ?? [],
isDefault: radarr?.isDefault ?? false,
is4k: radarr?.is4k ?? false,
isAnime: radarr?.isAnime ?? false,
externalUrl: radarr?.externalUrl,
syncEnabled: radarr?.syncEnabled ?? false,
enableSearch: !radarr?.preventSearch,
@ -261,6 +269,7 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
activeProfileName: profileName,
activeDirectory: values.rootFolder,
is4k: values.is4k,
isAnime: values.isAnime,
minimumAvailability: values.minimumAvailability,
tags: values.tags,
isDefault: values.isDefault,
@ -336,12 +345,22 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
title={
!radarr
? intl.formatMessage(
values.is4k
values.isAnime && values.is4k
? messages.create4kAnimeradarr
: values.isAnime
? messages.createAnimeradarr
: values.is4k
? messages.create4kradarr
: messages.createradarr
)
: intl.formatMessage(
values.is4k ? messages.edit4kradarr : messages.editradarr
values.isAnime && values.is4k
? messages.edit4kAnimeradarr
: values.isAnime
? messages.editAnimeradarr
: values.is4k
? messages.edit4kradarr
: messages.editradarr
)
}
>
@ -349,7 +368,11 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
<div className="form-row">
<label htmlFor="isDefault" className="checkbox-label">
{intl.formatMessage(
values.is4k
values.isAnime && values.is4k
? messages.default4kAnimeserver
: values.isAnime
? messages.defaultAnimeserver
: values.is4k
? messages.default4kserver
: messages.defaultserver
)}
@ -366,6 +389,14 @@ const RadarrModal = ({ onClose, radarr, onSave }: RadarrModalProps) => {
<Field type="checkbox" id="is4k" name="is4k" />
</div>
</div>
<div className="form-row">
<label htmlFor="isAnime" className="checkbox-label">
{intl.formatMessage(messages.serverAnime)}
</label>
<div className="form-input-area">
<Field type="checkbox" id="isAnime" name="isAnime" />
</div>
</div>
<div className="form-row">
<label htmlFor="name" className="text-label">
{intl.formatMessage(messages.servername)}

@ -27,7 +27,11 @@ const messages = defineMessages({
ssl: 'SSL',
default: 'Default',
default4k: 'Default 4K',
defaultAnime: 'Default Anime',
default4kAnime: 'Default 4K Anime',
is4k: '4K',
isAnime: 'Anime',
is4kAnime: '4K Anime',
address: 'Address',
activeProfile: 'Active Profile',
addradarr: 'Add Radarr Server',
@ -36,6 +40,8 @@ const messages = defineMessages({
'At least one {serverType} server must be marked as default in order for {mediaType} requests to be processed.',
noDefaultNon4kServer:
'If you only have a single {serverType} server for both non-4K and 4K content (or if you only download 4K content), your {serverType} server should <strong>NOT</strong> be designated as a 4K server.',
noDefaultNonAnimeServer:
'If you only have a single {serverType} server for both non-Anime and Anime content (or if you only download Anime content), your {serverType} server should <strong>NOT</strong> be designated as a Anime server.',
noDefault4kServer:
'A 4K {serverType} server must be marked as default in order to enable users to submit 4K {mediaType} requests.',
mediaTypeMovie: 'movie',
@ -47,6 +53,7 @@ interface ServerInstanceProps {
name: string;
isDefault?: boolean;
is4k?: boolean;
isAnime?: boolean;
hostname: string;
port: number;
isSSL?: boolean;
@ -63,6 +70,7 @@ const ServerInstance = ({
port,
profileName,
is4k = false,
isAnime = false,
isDefault = false,
isSSL = false,
isSonarr = false,
@ -89,17 +97,33 @@ const ServerInstance = ({
{name}
</a>
</h3>
{isDefault && !is4k && (
{isDefault && !is4k && !isAnime && (
<Badge>{intl.formatMessage(messages.default)}</Badge>
)}
{isDefault && is4k && (
{isDefault && is4k && isAnime && (
<Badge>{intl.formatMessage(messages.default4kAnime)}</Badge>
)}
{isDefault && is4k && !isAnime && (
<Badge>{intl.formatMessage(messages.default4k)}</Badge>
)}
{!isDefault && is4k && (
{isDefault && !is4k && isAnime && (
<Badge>{intl.formatMessage(messages.defaultAnime)}</Badge>
)}
{!isDefault && is4k && isAnime && (
<Badge badgeType="warning">
{intl.formatMessage(messages.is4kAnime)}
</Badge>
)}
{!isDefault && is4k && !isAnime && (
<Badge badgeType="warning">
{intl.formatMessage(messages.is4k)}
</Badge>
)}
{!isDefault && !is4k && isAnime && (
<Badge badgeType="warning">
{intl.formatMessage(messages.isAnime)}
</Badge>
)}
{isSSL && (
<Badge badgeType="success">
{intl.formatMessage(messages.ssl)}
@ -298,6 +322,19 @@ const SettingsServices = () => {
),
})}
/>
) : !radarrData.some(
(radarr) => radarr.isDefault && !radarr.isAnime
) ? (
<Alert
title={intl.formatMessage(messages.noDefaultNonAnimeServer, {
serverType: 'Radarr',
strong: (msg: React.ReactNode) => (
<strong className="font-semibold text-white">
{msg}
</strong>
),
})}
/>
) : (
radarrData.some((radarr) => radarr.is4k) &&
!radarrData.some(
@ -306,7 +343,7 @@ const SettingsServices = () => {
<Alert
title={intl.formatMessage(messages.noDefault4kServer, {
serverType: 'Radarr',
mediaType: intl.formatMessage(messages.mediaTypeMovie),
mediaType: intl.formatMessage(messages.mediaTypeSeries),
})}
/>
)
@ -322,6 +359,7 @@ const SettingsServices = () => {
isSSL={radarr.useSsl}
isDefault={radarr.isDefault}
is4k={radarr.is4k}
isAnime={radarr.isAnime}
externalUrl={radarr.externalUrl}
onEdit={() => setEditRadarrModal({ open: true, radarr })}
onDelete={() =>
@ -386,6 +424,19 @@ const SettingsServices = () => {
),
})}
/>
) : !sonarrData.some(
(sonarr) => sonarr.isDefault && !sonarr.isAnime
) ? (
<Alert
title={intl.formatMessage(messages.noDefaultNonAnimeServer, {
serverType: 'Sonarr',
strong: (msg: React.ReactNode) => (
<strong className="font-semibold text-white">
{msg}
</strong>
),
})}
/>
) : (
sonarrData.some((sonarr) => sonarr.is4k) &&
!sonarrData.some(
@ -411,6 +462,7 @@ const SettingsServices = () => {
isSonarr
isDefault={sonarr.isDefault}
is4k={sonarr.is4k}
isAnime={sonarr.isAnime}
externalUrl={sonarr.externalUrl}
onEdit={() => setEditSonarrModal({ open: true, sonarr })}
onDelete={() =>

@ -20,8 +20,12 @@ type OptionType = {
const messages = defineMessages({
createsonarr: 'Add New Sonarr Server',
create4ksonarr: 'Add New 4K Sonarr Server',
createAnimesonarr: 'Add New Anime Sonarr Server',
create4kAnimesonarr: 'Add New 4K Anime Sonarr Server',
editsonarr: 'Edit Sonarr Server',
edit4ksonarr: 'Edit 4K Sonarr Server',
editAnimesonarr: 'Edit Anime Sonarr Server',
edit4kAnimesonarr: 'Edit 4K Anime Sonarr Server',
validationNameRequired: 'You must provide a server name',
validationHostnameRequired: 'You must provide a valid hostname or IP address',
validationPortRequired: 'You must provide a valid port number',
@ -34,6 +38,8 @@ const messages = defineMessages({
add: 'Add Server',
defaultserver: 'Default Server',
default4kserver: 'Default 4K Server',
defaultAnimeserver: 'Default Anime Server',
default4kAnimeserver: 'Default 4K Anime Server',
servername: 'Server Name',
hostname: 'Hostname or IP Address',
port: 'Port',
@ -50,6 +56,7 @@ const messages = defineMessages({
animerootfolder: 'Anime Root Folder',
seasonfolders: 'Season Folders',
server4k: '4K Server',
serverAnime: 'Anime Server',
selectQualityProfile: 'Select quality profile',
selectRootFolder: 'Select root folder',
selectLanguageProfile: 'Select language profile',
@ -119,6 +126,7 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
name: Yup.string().required(
intl.formatMessage(messages.validationNameRequired)
),
isAnime: Yup.boolean(),
hostname: Yup.string()
.required(intl.formatMessage(messages.validationHostnameRequired))
.matches(
@ -140,6 +148,24 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
activeLanguageProfileId: Yup.number().required(
intl.formatMessage(messages.validationLanguageProfileRequired)
),
activeAnimeRootFolder: Yup.string().when('isAnime', {
is: true,
then: Yup.string().required(
intl.formatMessage(messages.validationRootFolderRequired)
),
}),
activeAnimeProfileId: Yup.string().when('isAnime', {
is: true,
then: Yup.string().required(
intl.formatMessage(messages.validationProfileRequired)
),
}),
activeAnimeLanguageProfileId: Yup.string().when('isAnime', {
is: true,
then: Yup.string().required(
intl.formatMessage(messages.validationLanguageProfileRequired)
),
}),
externalUrl: Yup.string()
.url(intl.formatMessage(messages.validationApplicationUrl))
.test(
@ -255,6 +281,7 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
animeTags: sonarr?.animeTags ?? [],
isDefault: sonarr?.isDefault ?? false,
is4k: sonarr?.is4k ?? false,
isAnime: sonarr?.isAnime ?? false,
enableSeasonFolders: sonarr?.enableSeasonFolders ?? false,
externalUrl: sonarr?.externalUrl,
syncEnabled: sonarr?.syncEnabled ?? false,
@ -297,6 +324,7 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
tags: values.tags,
animeTags: values.animeTags,
is4k: values.is4k,
isAnime: values.isAnime,
isDefault: values.isDefault,
enableSeasonFolders: values.enableSeasonFolders,
externalUrl: values.externalUrl,
@ -371,12 +399,22 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
title={
!sonarr
? intl.formatMessage(
values.is4k
values.isAnime && values.is4k
? messages.create4kAnimesonarr
: values.isAnime
? messages.createAnimesonarr
: values.is4k
? messages.create4ksonarr
: messages.createsonarr
)
: intl.formatMessage(
values.is4k ? messages.edit4ksonarr : messages.editsonarr
values.isAnime && values.is4k
? messages.edit4kAnimesonarr
: values.isAnime
? messages.editAnimesonarr
: values.is4k
? messages.edit4ksonarr
: messages.editsonarr
)
}
>
@ -384,7 +422,11 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
<div className="form-row">
<label htmlFor="isDefault" className="checkbox-label">
{intl.formatMessage(
values.is4k
values.isAnime && values.is4k
? messages.default4kAnimeserver
: values.isAnime
? messages.defaultAnimeserver
: values.is4k
? messages.default4kserver
: messages.defaultserver
)}
@ -401,6 +443,14 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
<Field type="checkbox" id="is4k" name="is4k" />
</div>
</div>
<div className="form-row">
<label htmlFor="isAnime" className="checkbox-label">
{intl.formatMessage(messages.serverAnime)}
</label>
<div className="form-input-area">
<Field type="checkbox" id="isAnime" name="isAnime" />
</div>
</div>
<div className="form-row">
<label htmlFor="name" className="text-label">
{intl.formatMessage(messages.servername)}
@ -774,6 +824,9 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
<div className="form-row">
<label htmlFor="activeAnimeProfileId" className="text-label">
{intl.formatMessage(messages.animequalityprofile)}
<span className="label-required">
{values.isAnime ? '*' : ''}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
@ -814,6 +867,9 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
<div className="form-row">
<label htmlFor="activeAnimeRootFolder" className="text-label">
{intl.formatMessage(messages.animerootfolder)}
<span className="label-required">
{values.isAnime ? '*' : ''}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">
@ -843,7 +899,9 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
</div>
{errors.activeAnimeRootFolder &&
touched.activeAnimeRootFolder && (
<div className="error">{errors.rootFolder}</div>
<div className="error">
{errors.activeAnimeRootFolder}
</div>
)}
</div>
</div>
@ -853,6 +911,9 @@ const SonarrModal = ({ onClose, sonarr, onSave }: SonarrModalProps) => {
className="text-label"
>
{intl.formatMessage(messages.animelanguageprofile)}
<span className="label-required">
{values.isAnime ? '*' : ''}
</span>
</label>
<div className="form-input-area">
<div className="form-input-field">

@ -28,7 +28,10 @@ const useRequestOverride = (request: MediaRequest): OverrideStatus => {
}
const defaultServer = allServers.find(
(server) => server.is4k === request.is4k && server.isDefault
(server) =>
server.is4k === request.is4k &&
server.isAnime === request.isAnime &&
server.isDefault
);
const activeServer = allServers.find(

@ -660,11 +660,17 @@
"components.Settings.RadarrModal.announced": "Announced",
"components.Settings.RadarrModal.apiKey": "API Key",
"components.Settings.RadarrModal.baseUrl": "URL Base",
"components.Settings.RadarrModal.create4kAnimeradarr": "Add New 4K Anime Radarr Server",
"components.Settings.RadarrModal.create4kradarr": "Add New 4K Radarr Server",
"components.Settings.RadarrModal.createAnimeradarr": "Add New Anime Radarr Server",
"components.Settings.RadarrModal.createradarr": "Add New Radarr Server",
"components.Settings.RadarrModal.default4kAnimeserver": "Default 4K Anime Server",
"components.Settings.RadarrModal.default4kserver": "Default 4K Server",
"components.Settings.RadarrModal.defaultAnimeserver": "Default Anime Server",
"components.Settings.RadarrModal.defaultserver": "Default Server",
"components.Settings.RadarrModal.edit4kAnimeradarr": "Edit 4K Anime Radarr Server",
"components.Settings.RadarrModal.edit4kradarr": "Edit 4K Radarr Server",
"components.Settings.RadarrModal.editAnimeradarr": "Edit Anime Radarr Server",
"components.Settings.RadarrModal.editradarr": "Edit Radarr Server",
"components.Settings.RadarrModal.enableSearch": "Enable Automatic Search",
"components.Settings.RadarrModal.externalUrl": "External URL",
@ -684,6 +690,7 @@
"components.Settings.RadarrModal.selectRootFolder": "Select root folder",
"components.Settings.RadarrModal.selecttags": "Select tags",
"components.Settings.RadarrModal.server4k": "4K Server",
"components.Settings.RadarrModal.serverAnime": "Anime Server",
"components.Settings.RadarrModal.servername": "Server Name",
"components.Settings.RadarrModal.ssl": "Use SSL",
"components.Settings.RadarrModal.syncEnabled": "Enable Scan",
@ -840,11 +847,17 @@
"components.Settings.SonarrModal.animerootfolder": "Anime Root Folder",
"components.Settings.SonarrModal.apiKey": "API Key",
"components.Settings.SonarrModal.baseUrl": "URL Base",
"components.Settings.SonarrModal.create4kAnimesonarr": "Add New 4K Anime Sonarr Server",
"components.Settings.SonarrModal.create4ksonarr": "Add New 4K Sonarr Server",
"components.Settings.SonarrModal.createAnimesonarr": "Add New Anime Sonarr Server",
"components.Settings.SonarrModal.createsonarr": "Add New Sonarr Server",
"components.Settings.SonarrModal.default4kAnimeserver": "Default 4K Anime Server",
"components.Settings.SonarrModal.default4kserver": "Default 4K Server",
"components.Settings.SonarrModal.defaultAnimeserver": "Default Anime Server",
"components.Settings.SonarrModal.defaultserver": "Default Server",
"components.Settings.SonarrModal.edit4kAnimesonarr": "Edit 4K Anime Sonarr Server",
"components.Settings.SonarrModal.edit4ksonarr": "Edit 4K Sonarr Server",
"components.Settings.SonarrModal.editAnimesonarr": "Edit Anime Sonarr Server",
"components.Settings.SonarrModal.editsonarr": "Edit Sonarr Server",
"components.Settings.SonarrModal.enableSearch": "Enable Automatic Search",
"components.Settings.SonarrModal.externalUrl": "External URL",
@ -865,6 +878,7 @@
"components.Settings.SonarrModal.selecttags": "Select tags",
"components.Settings.SonarrModal.seriesType": "Series Type",
"components.Settings.SonarrModal.server4k": "4K Server",
"components.Settings.SonarrModal.serverAnime": "Anime Server",
"components.Settings.SonarrModal.servername": "Server Name",
"components.Settings.SonarrModal.ssl": "Use SSL",
"components.Settings.SonarrModal.syncEnabled": "Enable Scan",
@ -898,6 +912,8 @@
"components.Settings.currentlibrary": "Current Library: {name}",
"components.Settings.default": "Default",
"components.Settings.default4k": "Default 4K",
"components.Settings.default4kAnime": "Default 4K Anime",
"components.Settings.defaultAnime": "Default Anime",
"components.Settings.deleteServer": "Delete {serverType} Server",
"components.Settings.deleteserverconfirm": "Are you sure you want to delete this server?",
"components.Settings.email": "Email",
@ -906,6 +922,8 @@
"components.Settings.externalUrl": "External URL",
"components.Settings.hostname": "Hostname or IP Address",
"components.Settings.is4k": "4K",
"components.Settings.is4kAnime": "4K Anime",
"components.Settings.isAnime": "Anime",
"components.Settings.librariesRemaining": "Libraries Remaining: {count}",
"components.Settings.manualscan": "Manual Library Scan",
"components.Settings.manualscanDescription": "Normally, this will only be run once every 24 hours. Overseerr will check your Plex server's recently added more aggressively. If this is your first time configuring Plex, a one-time full manual library scan is recommended!",
@ -921,6 +939,7 @@
"components.Settings.menuUsers": "Users",
"components.Settings.noDefault4kServer": "A 4K {serverType} server must be marked as default in order to enable users to submit 4K {mediaType} requests.",
"components.Settings.noDefaultNon4kServer": "If you only have a single {serverType} server for both non-4K and 4K content (or if you only download 4K content), your {serverType} server should <strong>NOT</strong> be designated as a 4K server.",
"components.Settings.noDefaultNonAnimeServer": "If you only have a single {serverType} server for both non-Anime and Anime content (or if you only download Anime content), your {serverType} server should <strong>NOT</strong> be designated as a Anime server.",
"components.Settings.noDefaultServer": "At least one {serverType} server must be marked as default in order for {mediaType} requests to be processed.",
"components.Settings.notificationAgentSettingsDescription": "Configure and enable notification agents.",
"components.Settings.notifications": "Notifications",

Loading…
Cancel
Save