pull/3664/merge
thempc 1 month 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: is4k:
type: boolean type: boolean
example: false example: false
isAnime:
type: boolean
example: false
minimumAvailability: minimumAvailability:
type: string type: string
example: 'In Cinema' example: 'In Cinema'
@ -385,6 +388,7 @@ components:
- activeProfileName - activeProfileName
- activeDirectory - activeDirectory
- is4k - is4k
- isAnime
- minimumAvailability - minimumAvailability
- isDefault - isDefault
SonarrSettings: SonarrSettings:
@ -439,6 +443,9 @@ components:
is4k: is4k:
type: boolean type: boolean
example: false example: false
isAnime:
type: boolean
example: false
enableSeasonFolders: enableSeasonFolders:
type: boolean type: boolean
example: false example: false
@ -464,6 +471,7 @@ components:
- activeProfileName - activeProfileName
- activeDirectory - activeDirectory
- is4k - is4k
- isAnime
- enableSeasonFolders - enableSeasonFolders
- isDefault - isDefault
ServarrTag: ServarrTag:
@ -1028,6 +1036,9 @@ components:
is4k: is4k:
type: boolean type: boolean
example: false example: false
isAnime:
type: boolean
example: false
serverId: serverId:
type: number type: number
profileId: profileId:
@ -5024,6 +5035,9 @@ paths:
is4k: is4k:
type: boolean type: boolean
example: false example: false
isAnime:
type: boolean
example: false
serverId: serverId:
type: number type: number
profileId: profileId:
@ -5128,6 +5142,9 @@ paths:
is4k: is4k:
type: boolean type: boolean
example: false example: false
isAnime:
type: boolean
example: false
serverId: serverId:
type: number type: number
profileId: profileId:
@ -5769,6 +5786,9 @@ paths:
is4k: is4k:
type: boolean type: boolean
example: false example: false
isAnime:
type: boolean
example: false
responses: responses:
'200': '200':
description: Returned media description: Returned media

@ -6,7 +6,6 @@ import type {
} from '@server/api/servarr/sonarr'; } from '@server/api/servarr/sonarr';
import SonarrAPI from '@server/api/servarr/sonarr'; import SonarrAPI from '@server/api/servarr/sonarr';
import TheMovieDb from '@server/api/themoviedb'; import TheMovieDb from '@server/api/themoviedb';
import { ANIME_KEYWORD_ID } from '@server/api/themoviedb/constants';
import { import {
MediaRequestStatus, MediaRequestStatus,
MediaStatus, MediaStatus,
@ -157,6 +156,7 @@ export class MediaRequest {
.leftJoin('request.media', 'media') .leftJoin('request.media', 'media')
.leftJoinAndSelect('request.requestedBy', 'user') .leftJoinAndSelect('request.requestedBy', 'user')
.where('request.is4k = :is4k', { is4k: requestBody.is4k }) .where('request.is4k = :is4k', { is4k: requestBody.is4k })
.andWhere('request.isAnime = :isAnime', { isAnime: requestBody.isAnime })
.andWhere('media.tmdbId = :tmdbId', { tmdbId: tmdbMedia.id }) .andWhere('media.tmdbId = :tmdbId', { tmdbId: tmdbMedia.id })
.andWhere('media.mediaType = :mediaType', { .andWhere('media.mediaType = :mediaType', {
mediaType: requestBody.mediaType, mediaType: requestBody.mediaType,
@ -173,6 +173,7 @@ export class MediaRequest {
tmdbId: tmdbMedia.id, tmdbId: tmdbMedia.id,
mediaType: requestBody.mediaType, mediaType: requestBody.mediaType,
is4k: requestBody.is4k, is4k: requestBody.is4k,
isAnime: requestBody.isAnime,
label: 'Media Request', label: 'Media Request',
}); });
@ -231,6 +232,7 @@ export class MediaRequest {
? user ? user
: undefined, : undefined,
is4k: requestBody.is4k, is4k: requestBody.is4k,
isAnime: requestBody.isAnime,
serverId: requestBody.serverId, serverId: requestBody.serverId,
profileId: requestBody.profileId, profileId: requestBody.profileId,
rootFolder: requestBody.rootFolder, rootFolder: requestBody.rootFolder,
@ -260,6 +262,7 @@ export class MediaRequest {
.filter( .filter(
(request) => (request) =>
request.is4k === requestBody.is4k && request.is4k === requestBody.is4k &&
request.isAnime === requestBody.isAnime &&
request.status !== MediaRequestStatus.DECLINED request.status !== MediaRequestStatus.DECLINED
) )
.reduce((seasons, request) => { .reduce((seasons, request) => {
@ -334,6 +337,7 @@ export class MediaRequest {
? user ? user
: undefined, : undefined,
is4k: requestBody.is4k, is4k: requestBody.is4k,
isAnime: requestBody.isAnime,
serverId: requestBody.serverId, serverId: requestBody.serverId,
profileId: requestBody.profileId, profileId: requestBody.profileId,
rootFolder: requestBody.rootFolder, rootFolder: requestBody.rootFolder,
@ -414,6 +418,9 @@ export class MediaRequest {
@Column({ default: false }) @Column({ default: false })
public is4k: boolean; public is4k: boolean;
@Column({ default: false })
public isAnime: boolean;
@Column({ nullable: true }) @Column({ nullable: true })
public serverId: number; public serverId: number;
@ -665,9 +672,20 @@ export class MediaRequest {
} }
let radarrSettings = settings.radarr.find( 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 ( if (
this.serverId !== null && this.serverId !== null &&
this.serverId >= 0 && this.serverId >= 0 &&
@ -689,9 +707,9 @@ export class MediaRequest {
if (!radarrSettings) { if (!radarrSettings) {
logger.warn( logger.warn(
`There is no default ${ `There is no default ${
this.is4k ? '4K ' : '' this.isAnime ? 'Anime ' : this.is4k ? '4K ' : ''
}Radarr server configured. Did you set any of your ${ }Radarr server configured. Did you set any of your ${
this.is4k ? '4K ' : '' this.isAnime ? 'Anime ' : this.is4k ? '4K ' : ''
}Radarr servers as default?`, }Radarr servers as default?`,
{ {
label: 'Media Request', label: 'Media Request',
@ -900,9 +918,20 @@ export class MediaRequest {
} }
let sonarrSettings = settings.sonarr.find( 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 ( if (
this.serverId !== null && this.serverId !== null &&
this.serverId >= 0 && this.serverId >= 0 &&
@ -924,9 +953,9 @@ export class MediaRequest {
if (!sonarrSettings) { if (!sonarrSettings) {
logger.warn( logger.warn(
`There is no default ${ `There is no default ${
this.is4k ? '4K ' : '' this.isAnime ? 'Anime ' : this.is4k ? '4K ' : ''
}Sonarr server configured. Did you set any of your ${ }Sonarr server configured. Did you set any of your ${
this.is4k ? '4K ' : '' this.isAnime ? 'Anime ' : this.is4k ? '4K ' : ''
}Sonarr servers as default?`, }Sonarr servers as default?`,
{ {
label: 'Media Request', label: 'Media Request',
@ -979,11 +1008,7 @@ export class MediaRequest {
let seriesType: SonarrSeries['seriesType'] = 'standard'; let seriesType: SonarrSeries['seriesType'] = 'standard';
// Change series type to anime if the anime keyword is present on tmdb // Change series type to anime if the anime keyword is present on tmdb
if ( if (this.isAnime) {
series.keywords.results.some(
(keyword) => keyword.id === ANIME_KEYWORD_ID
)
) {
seriesType = sonarrSettings.animeSeriesType ?? 'anime'; seriesType = sonarrSettings.animeSeriesType ?? 'anime';
} }
@ -1171,30 +1196,38 @@ export class MediaRequest {
switch (type) { switch (type) {
case Notification.MEDIA_APPROVED: case Notification.MEDIA_APPROVED:
event = `${this.is4k ? '4K ' : ''}${mediaType} Request Approved`; event = `${
this.isAnime ? 'Anime ' : this.is4k ? '4K ' : ''
}${mediaType} Request Approved`;
notifyAdmin = false; notifyAdmin = false;
break; break;
case Notification.MEDIA_DECLINED: case Notification.MEDIA_DECLINED:
event = `${this.is4k ? '4K ' : ''}${mediaType} Request Declined`; event = `${
this.isAnime ? 'Anime ' : this.is4k ? '4K ' : ''
}${mediaType} Request Declined`;
notifyAdmin = false; notifyAdmin = false;
break; break;
case Notification.MEDIA_PENDING: case Notification.MEDIA_PENDING:
event = `New ${this.is4k ? '4K ' : ''}${mediaType} Request`; event = `New ${
this.isAnime ? 'Anime ' : this.is4k ? '4K ' : ''
}${mediaType} Request`;
break; break;
case Notification.MEDIA_AUTO_REQUESTED: case Notification.MEDIA_AUTO_REQUESTED:
event = `${ event = `${
this.is4k ? '4K ' : '' this.isAnime ? 'Anime ' : this.is4k ? '4K ' : ''
}${mediaType} Request Automatically Submitted`; }${mediaType} Request Automatically Submitted`;
notifyAdmin = false; notifyAdmin = false;
notifySystem = false; notifySystem = false;
break; break;
case Notification.MEDIA_AUTO_APPROVED: case Notification.MEDIA_AUTO_APPROVED:
event = `${ event = `${
this.is4k ? '4K ' : '' this.isAnime ? 'Anime ' : this.is4k ? '4K ' : ''
}${mediaType} Request Automatically Approved`; }${mediaType} Request Automatically Approved`;
break; break;
case Notification.MEDIA_FAILED: case Notification.MEDIA_FAILED:
event = `${this.is4k ? '4K ' : ''}${mediaType} Request Failed`; event = `${
this.isAnime ? 'Anime ' : this.is4k ? '4K ' : ''
}${mediaType} Request Failed`;
break; break;
} }

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

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

@ -57,6 +57,7 @@ export interface DVRSettings {
activeDirectory: string; activeDirectory: string;
tags: number[]; tags: number[];
is4k: boolean; is4k: boolean;
isAnime: boolean;
isDefault: boolean; isDefault: boolean;
externalUrl?: string; externalUrl?: string;
syncEnabled: boolean; 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, id: radarr.id,
name: radarr.name, name: radarr.name,
is4k: radarr.is4k, is4k: radarr.is4k,
isAnime: radarr.isAnime,
isDefault: radarr.isDefault, isDefault: radarr.isDefault,
activeDirectory: radarr.activeDirectory, activeDirectory: radarr.activeDirectory,
activeProfileId: radarr.activeProfileId, activeProfileId: radarr.activeProfileId,
@ -59,6 +60,7 @@ serviceRoutes.get<{ radarrId: string }>(
id: radarrSettings.id, id: radarrSettings.id,
name: radarrSettings.name, name: radarrSettings.name,
is4k: radarrSettings.is4k, is4k: radarrSettings.is4k,
isAnime: radarrSettings.isAnime,
isDefault: radarrSettings.isDefault, isDefault: radarrSettings.isDefault,
activeDirectory: radarrSettings.activeDirectory, activeDirectory: radarrSettings.activeDirectory,
activeProfileId: radarrSettings.activeProfileId, activeProfileId: radarrSettings.activeProfileId,
@ -87,6 +89,7 @@ serviceRoutes.get('/sonarr', async (req, res) => {
id: sonarr.id, id: sonarr.id,
name: sonarr.name, name: sonarr.name,
is4k: sonarr.is4k, is4k: sonarr.is4k,
isAnime: sonarr.isAnime,
isDefault: sonarr.isDefault, isDefault: sonarr.isDefault,
activeDirectory: sonarr.activeDirectory, activeDirectory: sonarr.activeDirectory,
activeProfileId: sonarr.activeProfileId, activeProfileId: sonarr.activeProfileId,
@ -133,6 +136,7 @@ serviceRoutes.get<{ sonarrId: string }>(
id: sonarrSettings.id, id: sonarrSettings.id,
name: sonarrSettings.name, name: sonarrSettings.name,
is4k: sonarrSettings.is4k, is4k: sonarrSettings.is4k,
isAnime: sonarrSettings.isAnime,
isDefault: sonarrSettings.isDefault, isDefault: sonarrSettings.isDefault,
activeDirectory: sonarrSettings.activeDirectory, activeDirectory: sonarrSettings.activeDirectory,
activeProfileId: sonarrSettings.activeProfileId, activeProfileId: sonarrSettings.activeProfileId,

@ -24,7 +24,11 @@ radarrRoutes.post('/', (req, res) => {
// and are the default // and are the default
if (req.body.isDefault) { if (req.body.isDefault) {
settings.radarr settings.radarr
.filter((radarrInstance) => radarrInstance.is4k === req.body.is4k) .filter(
(radarrInstance) =>
radarrInstance.is4k === req.body.is4k &&
radarrInstance.isAnime === req.body.isAnime
)
.forEach((radarrInstance) => { .forEach((radarrInstance) => {
radarrInstance.isDefault = false; radarrInstance.isDefault = false;
}); });
@ -92,7 +96,11 @@ radarrRoutes.put<{ id: string }, RadarrSettings, RadarrSettings>(
// and are the default // and are the default
if (req.body.isDefault) { if (req.body.isDefault) {
settings.radarr settings.radarr
.filter((radarrInstance) => radarrInstance.is4k === req.body.is4k) .filter(
(radarrInstance) =>
radarrInstance.is4k === req.body.is4k &&
radarrInstance.isAnime === req.body.isAnime
)
.forEach((radarrInstance) => { .forEach((radarrInstance) => {
radarrInstance.isDefault = false; radarrInstance.isDefault = false;
}); });

@ -24,7 +24,11 @@ sonarrRoutes.post('/', (req, res) => {
// and are the default // and are the default
if (req.body.isDefault) { if (req.body.isDefault) {
settings.sonarr settings.sonarr
.filter((sonarrInstance) => sonarrInstance.is4k === req.body.is4k) .filter(
(sonarrInstance) =>
sonarrInstance.is4k === req.body.is4k &&
sonarrInstance.isAnime === req.body.isAnime
)
.forEach((sonarrInstance) => { .forEach((sonarrInstance) => {
sonarrInstance.isDefault = false; sonarrInstance.isDefault = false;
}); });
@ -90,7 +94,11 @@ sonarrRoutes.put<{ id: string }>('/:id', (req, res) => {
// and are the default // and are the default
if (req.body.isDefault) { if (req.body.isDefault) {
settings.sonarr settings.sonarr
.filter((sonarrInstance) => sonarrInstance.is4k === req.body.is4k) .filter(
(sonarrInstance) =>
sonarrInstance.is4k === req.body.is4k &&
sonarrInstance.isAnime === req.body.isAnime
)
.forEach((sonarrInstance) => { .forEach((sonarrInstance) => {
sonarrInstance.isDefault = false; sonarrInstance.isDefault = false;
}); });

@ -78,6 +78,7 @@ const RequestBlock = ({ request, onUpdate }: RequestBlockProps) => {
tmdbId={request.media.tmdbId} tmdbId={request.media.tmdbId}
type={request.type} type={request.type}
is4k={request.is4k} is4k={request.is4k}
isAnime={request.isAnime}
editRequest={request} editRequest={request}
onCancel={() => setShowEditModal(false)} onCancel={() => setShowEditModal(false)}
onComplete={() => { onComplete={() => {

@ -308,6 +308,7 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
tmdbId={request.media.tmdbId} tmdbId={request.media.tmdbId}
type={request.type} type={request.type}
is4k={request.is4k} is4k={request.is4k}
isAnime={request.isAnime}
editRequest={request} editRequest={request}
onCancel={() => setShowEditModal(false)} onCancel={() => setShowEditModal(false)}
onComplete={() => { onComplete={() => {

@ -368,6 +368,7 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
tmdbId={request.media.tmdbId} tmdbId={request.media.tmdbId}
type={request.type} type={request.type}
is4k={request.is4k} is4k={request.is4k}
isAnime={request.isAnime}
editRequest={request} editRequest={request}
onCancel={() => setShowEditModal(false)} onCancel={() => setShowEditModal(false)}
onComplete={() => { onComplete={() => {

@ -152,7 +152,8 @@ const AdvancedRequester = ({
useEffect(() => { useEffect(() => {
let defaultServer = data?.find( let defaultServer = data?.find(
(server) => server.isDefault && is4k === server.is4k (server) =>
server.isDefault && is4k === server.is4k && isAnime === server.isAnime
); );
if (!defaultServer && (data ?? []).length > 0) { if (!defaultServer && (data ?? []).length > 0) {
@ -293,7 +294,9 @@ const AdvancedRequester = ({
if ( if (
(!data || (!data ||
selectedServer === null || selectedServer === null ||
(data.filter((server) => server.is4k === is4k).length < 2 && (data.filter(
(server) => server.is4k === is4k && server.isAnime === isAnime
).length < 2 &&
(!serverData || (!serverData ||
(serverData.profiles.length < 2 && (serverData.profiles.length < 2 &&
serverData.rootFolders.length < 2 && serverData.rootFolders.length < 2 &&
@ -312,7 +315,9 @@ const AdvancedRequester = ({
<div className="rounded-md"> <div className="rounded-md">
{!!data && selectedServer !== null && ( {!!data && selectedServer !== null && (
<div className="flex flex-col md:flex-row"> <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"> <div className="mb-3 w-full flex-shrink-0 flex-grow last:pr-0 md:w-1/4 md:pr-4">
<label htmlFor="server"> <label htmlFor="server">
{intl.formatMessage(messages.destinationserver)} {intl.formatMessage(messages.destinationserver)}
@ -326,7 +331,10 @@ const AdvancedRequester = ({
className="border-gray-700 bg-gray-800" className="border-gray-700 bg-gray-800"
> >
{data {data
.filter((server) => server.is4k === is4k) .filter(
(server) =>
server.is4k === is4k && server.isAnime === isAnime
)
.map((server) => ( .map((server) => (
<option <option
key={`server-list-${server.id}`} key={`server-list-${server.id}`}

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

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

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

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

@ -27,7 +27,11 @@ const messages = defineMessages({
ssl: 'SSL', ssl: 'SSL',
default: 'Default', default: 'Default',
default4k: 'Default 4K', default4k: 'Default 4K',
defaultAnime: 'Default Anime',
default4kAnime: 'Default 4K Anime',
is4k: '4K', is4k: '4K',
isAnime: 'Anime',
is4kAnime: '4K Anime',
address: 'Address', address: 'Address',
activeProfile: 'Active Profile', activeProfile: 'Active Profile',
addradarr: 'Add Radarr Server', 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.', 'At least one {serverType} server must be marked as default in order for {mediaType} requests to be processed.',
noDefaultNon4kServer: 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.', '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: noDefault4kServer:
'A 4K {serverType} server must be marked as default in order to enable users to submit 4K {mediaType} requests.', 'A 4K {serverType} server must be marked as default in order to enable users to submit 4K {mediaType} requests.',
mediaTypeMovie: 'movie', mediaTypeMovie: 'movie',
@ -47,6 +53,7 @@ interface ServerInstanceProps {
name: string; name: string;
isDefault?: boolean; isDefault?: boolean;
is4k?: boolean; is4k?: boolean;
isAnime?: boolean;
hostname: string; hostname: string;
port: number; port: number;
isSSL?: boolean; isSSL?: boolean;
@ -63,6 +70,7 @@ const ServerInstance = ({
port, port,
profileName, profileName,
is4k = false, is4k = false,
isAnime = false,
isDefault = false, isDefault = false,
isSSL = false, isSSL = false,
isSonarr = false, isSonarr = false,
@ -89,17 +97,33 @@ const ServerInstance = ({
{name} {name}
</a> </a>
</h3> </h3>
{isDefault && !is4k && ( {isDefault && !is4k && !isAnime && (
<Badge>{intl.formatMessage(messages.default)}</Badge> <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> <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"> <Badge badgeType="warning">
{intl.formatMessage(messages.is4k)} {intl.formatMessage(messages.is4k)}
</Badge> </Badge>
)} )}
{!isDefault && !is4k && isAnime && (
<Badge badgeType="warning">
{intl.formatMessage(messages.isAnime)}
</Badge>
)}
{isSSL && ( {isSSL && (
<Badge badgeType="success"> <Badge badgeType="success">
{intl.formatMessage(messages.ssl)} {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((radarr) => radarr.is4k) &&
!radarrData.some( !radarrData.some(
@ -306,7 +343,7 @@ const SettingsServices = () => {
<Alert <Alert
title={intl.formatMessage(messages.noDefault4kServer, { title={intl.formatMessage(messages.noDefault4kServer, {
serverType: 'Radarr', serverType: 'Radarr',
mediaType: intl.formatMessage(messages.mediaTypeMovie), mediaType: intl.formatMessage(messages.mediaTypeSeries),
})} })}
/> />
) )
@ -322,6 +359,7 @@ const SettingsServices = () => {
isSSL={radarr.useSsl} isSSL={radarr.useSsl}
isDefault={radarr.isDefault} isDefault={radarr.isDefault}
is4k={radarr.is4k} is4k={radarr.is4k}
isAnime={radarr.isAnime}
externalUrl={radarr.externalUrl} externalUrl={radarr.externalUrl}
onEdit={() => setEditRadarrModal({ open: true, radarr })} onEdit={() => setEditRadarrModal({ open: true, radarr })}
onDelete={() => 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((sonarr) => sonarr.is4k) &&
!sonarrData.some( !sonarrData.some(
@ -411,6 +462,7 @@ const SettingsServices = () => {
isSonarr isSonarr
isDefault={sonarr.isDefault} isDefault={sonarr.isDefault}
is4k={sonarr.is4k} is4k={sonarr.is4k}
isAnime={sonarr.isAnime}
externalUrl={sonarr.externalUrl} externalUrl={sonarr.externalUrl}
onEdit={() => setEditSonarrModal({ open: true, sonarr })} onEdit={() => setEditSonarrModal({ open: true, sonarr })}
onDelete={() => onDelete={() =>

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

@ -28,7 +28,10 @@ const useRequestOverride = (request: MediaRequest): OverrideStatus => {
} }
const defaultServer = allServers.find( 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( const activeServer = allServers.find(

@ -660,11 +660,17 @@
"components.Settings.RadarrModal.announced": "Announced", "components.Settings.RadarrModal.announced": "Announced",
"components.Settings.RadarrModal.apiKey": "API Key", "components.Settings.RadarrModal.apiKey": "API Key",
"components.Settings.RadarrModal.baseUrl": "URL Base", "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.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.createradarr": "Add New Radarr Server",
"components.Settings.RadarrModal.default4kAnimeserver": "Default 4K Anime Server",
"components.Settings.RadarrModal.default4kserver": "Default 4K Server", "components.Settings.RadarrModal.default4kserver": "Default 4K Server",
"components.Settings.RadarrModal.defaultAnimeserver": "Default Anime Server",
"components.Settings.RadarrModal.defaultserver": "Default 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.edit4kradarr": "Edit 4K Radarr Server",
"components.Settings.RadarrModal.editAnimeradarr": "Edit Anime Radarr Server",
"components.Settings.RadarrModal.editradarr": "Edit Radarr Server", "components.Settings.RadarrModal.editradarr": "Edit Radarr Server",
"components.Settings.RadarrModal.enableSearch": "Enable Automatic Search", "components.Settings.RadarrModal.enableSearch": "Enable Automatic Search",
"components.Settings.RadarrModal.externalUrl": "External URL", "components.Settings.RadarrModal.externalUrl": "External URL",
@ -684,6 +690,7 @@
"components.Settings.RadarrModal.selectRootFolder": "Select root folder", "components.Settings.RadarrModal.selectRootFolder": "Select root folder",
"components.Settings.RadarrModal.selecttags": "Select tags", "components.Settings.RadarrModal.selecttags": "Select tags",
"components.Settings.RadarrModal.server4k": "4K Server", "components.Settings.RadarrModal.server4k": "4K Server",
"components.Settings.RadarrModal.serverAnime": "Anime Server",
"components.Settings.RadarrModal.servername": "Server Name", "components.Settings.RadarrModal.servername": "Server Name",
"components.Settings.RadarrModal.ssl": "Use SSL", "components.Settings.RadarrModal.ssl": "Use SSL",
"components.Settings.RadarrModal.syncEnabled": "Enable Scan", "components.Settings.RadarrModal.syncEnabled": "Enable Scan",
@ -840,11 +847,17 @@
"components.Settings.SonarrModal.animerootfolder": "Anime Root Folder", "components.Settings.SonarrModal.animerootfolder": "Anime Root Folder",
"components.Settings.SonarrModal.apiKey": "API Key", "components.Settings.SonarrModal.apiKey": "API Key",
"components.Settings.SonarrModal.baseUrl": "URL Base", "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.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.createsonarr": "Add New Sonarr Server",
"components.Settings.SonarrModal.default4kAnimeserver": "Default 4K Anime Server",
"components.Settings.SonarrModal.default4kserver": "Default 4K Server", "components.Settings.SonarrModal.default4kserver": "Default 4K Server",
"components.Settings.SonarrModal.defaultAnimeserver": "Default Anime Server",
"components.Settings.SonarrModal.defaultserver": "Default 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.edit4ksonarr": "Edit 4K Sonarr Server",
"components.Settings.SonarrModal.editAnimesonarr": "Edit Anime Sonarr Server",
"components.Settings.SonarrModal.editsonarr": "Edit Sonarr Server", "components.Settings.SonarrModal.editsonarr": "Edit Sonarr Server",
"components.Settings.SonarrModal.enableSearch": "Enable Automatic Search", "components.Settings.SonarrModal.enableSearch": "Enable Automatic Search",
"components.Settings.SonarrModal.externalUrl": "External URL", "components.Settings.SonarrModal.externalUrl": "External URL",
@ -865,6 +878,7 @@
"components.Settings.SonarrModal.selecttags": "Select tags", "components.Settings.SonarrModal.selecttags": "Select tags",
"components.Settings.SonarrModal.seriesType": "Series Type", "components.Settings.SonarrModal.seriesType": "Series Type",
"components.Settings.SonarrModal.server4k": "4K Server", "components.Settings.SonarrModal.server4k": "4K Server",
"components.Settings.SonarrModal.serverAnime": "Anime Server",
"components.Settings.SonarrModal.servername": "Server Name", "components.Settings.SonarrModal.servername": "Server Name",
"components.Settings.SonarrModal.ssl": "Use SSL", "components.Settings.SonarrModal.ssl": "Use SSL",
"components.Settings.SonarrModal.syncEnabled": "Enable Scan", "components.Settings.SonarrModal.syncEnabled": "Enable Scan",
@ -898,6 +912,8 @@
"components.Settings.currentlibrary": "Current Library: {name}", "components.Settings.currentlibrary": "Current Library: {name}",
"components.Settings.default": "Default", "components.Settings.default": "Default",
"components.Settings.default4k": "Default 4K", "components.Settings.default4k": "Default 4K",
"components.Settings.default4kAnime": "Default 4K Anime",
"components.Settings.defaultAnime": "Default Anime",
"components.Settings.deleteServer": "Delete {serverType} Server", "components.Settings.deleteServer": "Delete {serverType} Server",
"components.Settings.deleteserverconfirm": "Are you sure you want to delete this server?", "components.Settings.deleteserverconfirm": "Are you sure you want to delete this server?",
"components.Settings.email": "Email", "components.Settings.email": "Email",
@ -906,6 +922,8 @@
"components.Settings.externalUrl": "External URL", "components.Settings.externalUrl": "External URL",
"components.Settings.hostname": "Hostname or IP Address", "components.Settings.hostname": "Hostname or IP Address",
"components.Settings.is4k": "4K", "components.Settings.is4k": "4K",
"components.Settings.is4kAnime": "4K Anime",
"components.Settings.isAnime": "Anime",
"components.Settings.librariesRemaining": "Libraries Remaining: {count}", "components.Settings.librariesRemaining": "Libraries Remaining: {count}",
"components.Settings.manualscan": "Manual Library Scan", "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!", "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.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.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.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.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.notificationAgentSettingsDescription": "Configure and enable notification agents.",
"components.Settings.notifications": "Notifications", "components.Settings.notifications": "Notifications",

Loading…
Cancel
Save