feat(requests): add language profile support (#860)
This commit is contained in:
@@ -112,6 +112,7 @@ interface AddSeriesOptions {
|
||||
tvdbid: number;
|
||||
title: string;
|
||||
profileId: number;
|
||||
languageProfileId?: number;
|
||||
seasons: number[];
|
||||
seasonFolder: boolean;
|
||||
rootFolderPath: string;
|
||||
@@ -120,6 +121,11 @@ interface AddSeriesOptions {
|
||||
searchNow?: boolean;
|
||||
}
|
||||
|
||||
export interface LanguageProfile {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
class SonarrAPI extends ExternalAPI {
|
||||
static buildSonarrUrl(sonarrSettings: SonarrSettings, path?: string): string {
|
||||
return `${sonarrSettings.useSsl ? 'https' : 'http'}://${
|
||||
@@ -236,6 +242,7 @@ class SonarrAPI extends ExternalAPI {
|
||||
tvdbId: options.tvdbid,
|
||||
title: options.title,
|
||||
profileId: options.profileId,
|
||||
languageProfileId: options.languageProfileId,
|
||||
seasons: this.buildSeasonList(
|
||||
options.seasons,
|
||||
series.seasons.map((season) => ({
|
||||
@@ -321,6 +328,28 @@ class SonarrAPI extends ExternalAPI {
|
||||
}
|
||||
}
|
||||
|
||||
public async getLanguageProfiles(): Promise<LanguageProfile[]> {
|
||||
try {
|
||||
const data = await this.getRolling<LanguageProfile[]>(
|
||||
'/v3/languageprofile',
|
||||
undefined,
|
||||
3600
|
||||
);
|
||||
|
||||
return data;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while retrieving Sonarr language profiles.',
|
||||
{
|
||||
label: 'Sonarr API',
|
||||
message: e.message,
|
||||
}
|
||||
);
|
||||
|
||||
throw new Error('Failed to get language profiles');
|
||||
}
|
||||
}
|
||||
|
||||
private buildSeasonList(
|
||||
seasons: number[],
|
||||
existingSeasons?: SonarrSeason[]
|
||||
|
||||
@@ -78,6 +78,9 @@ export class MediaRequest {
|
||||
@Column({ nullable: true })
|
||||
public rootFolder: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public languageProfileId: number;
|
||||
|
||||
constructor(init?: Partial<MediaRequest>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
@@ -559,6 +562,11 @@ export class MediaRequest {
|
||||
? sonarrSettings.activeAnimeProfileId
|
||||
: sonarrSettings.activeProfileId;
|
||||
|
||||
let languageProfile =
|
||||
seriesType === 'anime' && sonarrSettings.activeAnimeLanguageProfileId
|
||||
? sonarrSettings.activeAnimeLanguageProfileId
|
||||
: sonarrSettings.activeLanguageProfileId;
|
||||
|
||||
if (
|
||||
this.rootFolder &&
|
||||
this.rootFolder !== '' &&
|
||||
@@ -577,10 +585,24 @@ export class MediaRequest {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
this.languageProfileId &&
|
||||
this.languageProfileId !== languageProfile
|
||||
) {
|
||||
languageProfile = this.languageProfileId;
|
||||
logger.info(
|
||||
`Request has an override Language Profile: ${languageProfile}`,
|
||||
{
|
||||
label: 'Media Request',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Run this asynchronously so we don't wait for it on the UI side
|
||||
sonarr
|
||||
.addSeries({
|
||||
profileId: qualityProfile,
|
||||
languageProfileId: languageProfile,
|
||||
rootFolderPath: rootFolder,
|
||||
title: series.name,
|
||||
tvdbid: tvdbId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { RadarrProfile, RadarrRootFolder } from '../../api/radarr';
|
||||
import { LanguageProfile } from '../../api/sonarr';
|
||||
|
||||
export interface ServiceCommonServer {
|
||||
id: number;
|
||||
@@ -7,12 +8,15 @@ export interface ServiceCommonServer {
|
||||
isDefault: boolean;
|
||||
activeProfileId: number;
|
||||
activeDirectory: string;
|
||||
activeLanguageProfileId?: number;
|
||||
activeAnimeProfileId?: number;
|
||||
activeAnimeDirectory?: string;
|
||||
activeAnimeLanguageProfileId?: number;
|
||||
}
|
||||
|
||||
export interface ServiceCommonServerWithDetails {
|
||||
server: ServiceCommonServer;
|
||||
profiles: RadarrProfile[];
|
||||
rootFolders: Partial<RadarrRootFolder>[];
|
||||
languageProfiles?: LanguageProfile[];
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ export interface SonarrSettings extends DVRSettings {
|
||||
activeAnimeProfileId?: number;
|
||||
activeAnimeProfileName?: string;
|
||||
activeAnimeDirectory?: string;
|
||||
activeAnimeLanguageProfileId?: number;
|
||||
activeLanguageProfileId?: number;
|
||||
enableSeasonFolders: boolean;
|
||||
}
|
||||
|
||||
|
||||
31
server/migration/1612571545781-AddLanguageProfileId.ts
Normal file
31
server/migration/1612571545781-AddLanguageProfileId.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddLanguageProfileId1612571545781 implements MigrationInterface {
|
||||
name = 'AddLanguageProfileId1612571545781';
|
||||
|
||||
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), "serverId" integer, "profileId" integer, "rootFolder" varchar, "languageProfileId" integer, 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") SELECT "id", "status", "createdAt", "updatedAt", "type", "mediaId", "requestedById", "modifiedById", "is4k", "serverId", "profileId", "rootFolder" 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, 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") SELECT "id", "status", "createdAt", "updatedAt", "type", "mediaId", "requestedById", "modifiedById", "is4k", "serverId", "profileId", "rootFolder" FROM "temporary_media_request"`
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "temporary_media_request"`);
|
||||
}
|
||||
}
|
||||
@@ -250,6 +250,7 @@ requestRoutes.post(
|
||||
serverId: req.body.serverId,
|
||||
profileId: req.body.profileId,
|
||||
rootFolder: req.body.rootFolder,
|
||||
languageProfileId: req.body.languageProfileId,
|
||||
seasons: finalSeasons.map(
|
||||
(sn) =>
|
||||
new SeasonRequest({
|
||||
|
||||
@@ -90,6 +90,8 @@ serviceRoutes.get('/sonarr', async (req, res) => {
|
||||
activeProfileId: sonarr.activeProfileId,
|
||||
activeAnimeProfileId: sonarr.activeAnimeProfileId,
|
||||
activeAnimeDirectory: sonarr.activeAnimeDirectory,
|
||||
activeLanguageProfileId: sonarr.activeLanguageProfileId,
|
||||
activeAnimeLanguageProfileId: sonarr.activeAnimeLanguageProfileId,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -119,31 +121,40 @@ serviceRoutes.get<{ sonarrId: string }>(
|
||||
}:${sonarrSettings.port}${sonarrSettings.baseUrl ?? ''}/api`,
|
||||
});
|
||||
|
||||
const profiles = await sonarr.getProfiles();
|
||||
const rootFolders = await sonarr.getRootFolders();
|
||||
try {
|
||||
const profiles = await sonarr.getProfiles();
|
||||
const rootFolders = await sonarr.getRootFolders();
|
||||
const languageProfiles = await sonarr.getLanguageProfiles();
|
||||
|
||||
return res.status(200).json({
|
||||
server: {
|
||||
id: sonarrSettings.id,
|
||||
name: sonarrSettings.name,
|
||||
is4k: sonarrSettings.is4k,
|
||||
isDefault: sonarrSettings.isDefault,
|
||||
activeDirectory: sonarrSettings.activeDirectory,
|
||||
activeProfileId: sonarrSettings.activeProfileId,
|
||||
activeAnimeProfileId: sonarrSettings.activeAnimeProfileId,
|
||||
activeAnimeDirectory: sonarrSettings.activeAnimeDirectory,
|
||||
},
|
||||
profiles: profiles.map((profile) => ({
|
||||
id: profile.id,
|
||||
name: profile.name,
|
||||
})),
|
||||
rootFolders: rootFolders.map((folder) => ({
|
||||
id: folder.id,
|
||||
freeSpace: folder.freeSpace,
|
||||
path: folder.path,
|
||||
totalSpace: folder.totalSpace,
|
||||
})),
|
||||
} as ServiceCommonServerWithDetails);
|
||||
return res.status(200).json({
|
||||
server: {
|
||||
id: sonarrSettings.id,
|
||||
name: sonarrSettings.name,
|
||||
is4k: sonarrSettings.is4k,
|
||||
isDefault: sonarrSettings.isDefault,
|
||||
activeDirectory: sonarrSettings.activeDirectory,
|
||||
activeProfileId: sonarrSettings.activeProfileId,
|
||||
activeAnimeProfileId: sonarrSettings.activeAnimeProfileId,
|
||||
activeAnimeDirectory: sonarrSettings.activeAnimeDirectory,
|
||||
activeLanguageProfileId: sonarrSettings.activeLanguageProfileId,
|
||||
activeAnimeLanguageProfileId:
|
||||
sonarrSettings.activeAnimeLanguageProfileId,
|
||||
},
|
||||
profiles: profiles.map((profile) => ({
|
||||
id: profile.id,
|
||||
name: profile.name,
|
||||
})),
|
||||
rootFolders: rootFolders.map((folder) => ({
|
||||
id: folder.id,
|
||||
freeSpace: folder.freeSpace,
|
||||
path: folder.path,
|
||||
totalSpace: folder.totalSpace,
|
||||
})),
|
||||
languageProfiles: languageProfiles,
|
||||
} as ServiceCommonServerWithDetails);
|
||||
} catch (e) {
|
||||
next({ status: 500, message: e.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ sonarrRoutes.post('/test', async (req, res, next) => {
|
||||
|
||||
const profiles = await sonarr.getProfiles();
|
||||
const folders = await sonarr.getRootFolders();
|
||||
const languageProfiles = await sonarr.getLanguageProfiles();
|
||||
|
||||
return res.status(200).json({
|
||||
profiles,
|
||||
@@ -53,6 +54,7 @@ sonarrRoutes.post('/test', async (req, res, next) => {
|
||||
id: folder.id,
|
||||
path: folder.path,
|
||||
})),
|
||||
languageProfiles,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('Failed to test Sonarr', {
|
||||
|
||||
Reference in New Issue
Block a user