feat: radarr/sonarr tag support (#1366)

This commit is contained in:
sct
2021-04-05 21:16:55 +09:00
committed by GitHub
parent db5af6d24b
commit a306ebc2d1
24 changed files with 760 additions and 366 deletions

169
server/api/servarr/base.ts Normal file
View File

@@ -0,0 +1,169 @@
import cacheManager, { AvailableCacheIds } from '../../lib/cache';
import { DVRSettings } from '../../lib/settings';
import ExternalAPI from '../externalapi';
export interface RootFolder {
id: number;
path: string;
freeSpace: number;
totalSpace: number;
unmappedFolders: {
name: string;
path: string;
}[];
}
export interface QualityProfile {
id: number;
name: string;
}
interface QueueItem {
size: number;
title: string;
sizeleft: number;
timeleft: string;
estimatedCompletionTime: string;
status: string;
trackedDownloadStatus: string;
trackedDownloadState: string;
downloadId: string;
protocol: string;
downloadClient: string;
indexer: string;
id: number;
}
export interface Tag {
id: number;
label: string;
}
interface QueueResponse<QueueItemAppendT> {
page: number;
pageSize: number;
sortKey: string;
sortDirection: string;
totalRecords: number;
records: (QueueItem & QueueItemAppendT)[];
}
class ServarrBase<QueueItemAppendT> extends ExternalAPI {
static buildUrl(settings: DVRSettings, path?: string): string {
return `${settings.useSsl ? 'https' : 'http'}://${settings.hostname}:${
settings.port
}${settings.baseUrl ?? ''}${path}`;
}
protected apiName: string;
constructor({
url,
apiKey,
cacheName,
apiName,
}: {
url: string;
apiKey: string;
cacheName: AvailableCacheIds;
apiName: string;
}) {
super(
url,
{
apikey: apiKey,
},
{
nodeCache: cacheManager.getCache(cacheName).data,
}
);
this.apiName = apiName;
}
public getProfiles = async (): Promise<QualityProfile[]> => {
try {
const data = await this.getRolling<QualityProfile[]>(
`/qualityProfile`,
undefined,
3600
);
return data;
} catch (e) {
throw new Error(
`[${this.apiName}] Failed to retrieve profiles: ${e.message}`
);
}
};
public getRootFolders = async (): Promise<RootFolder[]> => {
try {
const data = await this.getRolling<RootFolder[]>(
`/rootfolder`,
undefined,
3600
);
return data;
} catch (e) {
throw new Error(
`[${this.apiName}] Failed to retrieve root folders: ${e.message}`
);
}
};
public getQueue = async (): Promise<(QueueItem & QueueItemAppendT)[]> => {
try {
const response = await this.axios.get<QueueResponse<QueueItemAppendT>>(
`/queue`
);
return response.data.records;
} catch (e) {
throw new Error(
`[${this.apiName}] Failed to retrieve queue: ${e.message}`
);
}
};
public getTags = async (): Promise<Tag[]> => {
try {
const response = await this.axios.get<Tag[]>(`/tag`);
return response.data;
} catch (e) {
throw new Error(
`[${this.apiName}] Failed to retrieve tags: ${e.message}`
);
}
};
public createTag = async ({ label }: { label: string }): Promise<Tag> => {
try {
const response = await this.axios.post<Tag>(`/tag`, {
label,
});
return response.data;
} catch (e) {
throw new Error(`[${this.apiName}] Failed to create tag: ${e.message}`);
}
};
protected async runCommand(
commandName: string,
options: Record<string, unknown>
): Promise<void> {
try {
await this.axios.post(`/command`, {
name: commandName,
...options,
});
} catch (e) {
throw new Error(`[${this.apiName}] Failed to run command: ${e.message}`);
}
}
}
export default ServarrBase;

View File

@@ -1,12 +1,11 @@
import cacheManager from '../lib/cache';
import { RadarrSettings } from '../lib/settings';
import logger from '../logger';
import ExternalAPI from './externalapi';
import logger from '../../logger';
import ServarrBase from './base';
interface RadarrMovieOptions {
title: string;
qualityProfileId: number;
minimumAvailability: string;
tags: number[];
profileId: number;
year: number;
rootFolderPath: string;
@@ -32,65 +31,9 @@ export interface RadarrMovie {
hasFile: boolean;
}
export interface RadarrRootFolder {
id: number;
path: string;
freeSpace: number;
totalSpace: number;
unmappedFolders: {
name: string;
path: string;
}[];
}
export interface RadarrProfile {
id: number;
name: string;
}
interface QueueItem {
movieId: number;
size: number;
title: string;
sizeleft: number;
timeleft: string;
estimatedCompletionTime: string;
status: string;
trackedDownloadStatus: string;
trackedDownloadState: string;
downloadId: string;
protocol: string;
downloadClient: string;
indexer: string;
id: number;
}
interface QueueResponse {
page: number;
pageSize: number;
sortKey: string;
sortDirection: string;
totalRecords: number;
records: QueueItem[];
}
class RadarrAPI extends ExternalAPI {
static buildRadarrUrl(radarrSettings: RadarrSettings, path?: string): string {
return `${radarrSettings.useSsl ? 'https' : 'http'}://${
radarrSettings.hostname
}:${radarrSettings.port}${radarrSettings.baseUrl ?? ''}${path}`;
}
class RadarrAPI extends ServarrBase<{ movieId: number }> {
constructor({ url, apiKey }: { url: string; apiKey: string }) {
super(
url,
{
apikey: apiKey,
},
{
nodeCache: cacheManager.getCache('radarr').data,
}
);
super({ url, apiKey, cacheName: 'radarr', apiName: 'Radarr' });
}
public getMovies = async (): Promise<RadarrMovie[]> => {
@@ -162,6 +105,7 @@ class RadarrAPI extends ExternalAPI {
minimumAvailability: options.minimumAvailability,
tmdbId: options.tmdbId,
year: options.year,
tags: options.tags,
rootFolderPath: options.rootFolderPath,
monitored: options.monitored,
addOptions: {
@@ -206,6 +150,7 @@ class RadarrAPI extends ExternalAPI {
year: options.year,
rootFolderPath: options.rootFolderPath,
monitored: options.monitored,
tags: options.tags,
addOptions: {
searchForMovie: options.searchNow,
},
@@ -238,44 +183,6 @@ class RadarrAPI extends ExternalAPI {
throw new Error('Failed to add movie to Radarr');
}
};
public getProfiles = async (): Promise<RadarrProfile[]> => {
try {
const data = await this.getRolling<RadarrProfile[]>(
`/qualityProfile`,
undefined,
3600
);
return data;
} catch (e) {
throw new Error(`[Radarr] Failed to retrieve profiles: ${e.message}`);
}
};
public getRootFolders = async (): Promise<RadarrRootFolder[]> => {
try {
const data = await this.getRolling<RadarrRootFolder[]>(
`/rootfolder`,
undefined,
3600
);
return data;
} catch (e) {
throw new Error(`[Radarr] Failed to retrieve root folders: ${e.message}`);
}
};
public getQueue = async (): Promise<QueueItem[]> => {
try {
const response = await this.axios.get<QueueResponse>(`/queue`);
return response.data.records;
} catch (e) {
throw new Error(`[Radarr] Failed to retrieve queue: ${e.message}`);
}
};
}
export default RadarrAPI;

View File

@@ -1,7 +1,5 @@
import cacheManager from '../lib/cache';
import { SonarrSettings } from '../lib/settings';
import logger from '../logger';
import ExternalAPI from './externalapi';
import logger from '../../logger';
import ServarrBase from './base';
interface SonarrSeason {
seasonNumber: number;
@@ -49,7 +47,7 @@ export interface SonarrSeries {
titleSlug: string;
certification: string;
genres: string[];
tags: string[];
tags: number[];
added: string;
ratings: {
votes: number;
@@ -65,49 +63,6 @@ export interface SonarrSeries {
};
}
interface QueueItem {
seriesId: number;
episodeId: number;
size: number;
title: string;
sizeleft: number;
timeleft: string;
estimatedCompletionTime: string;
status: string;
trackedDownloadStatus: string;
trackedDownloadState: string;
downloadId: string;
protocol: string;
downloadClient: string;
indexer: string;
id: number;
}
interface QueueResponse {
page: number;
pageSize: number;
sortKey: string;
sortDirection: string;
totalRecords: number;
records: QueueItem[];
}
interface SonarrProfile {
id: number;
name: string;
}
interface SonarrRootFolder {
id: number;
path: string;
freeSpace: number;
totalSpace: number;
unmappedFolders: {
name: string;
path: string;
}[];
}
interface AddSeriesOptions {
tvdbid: number;
title: string;
@@ -116,6 +71,7 @@ interface AddSeriesOptions {
seasons: number[];
seasonFolder: boolean;
rootFolderPath: string;
tags?: number[];
seriesType: SonarrSeries['seriesType'];
monitored?: boolean;
searchNow?: boolean;
@@ -126,23 +82,9 @@ export interface LanguageProfile {
name: string;
}
class SonarrAPI extends ExternalAPI {
static buildSonarrUrl(sonarrSettings: SonarrSettings, path?: string): string {
return `${sonarrSettings.useSsl ? 'https' : 'http'}://${
sonarrSettings.hostname
}:${sonarrSettings.port}${sonarrSettings.baseUrl ?? ''}${path}`;
}
class SonarrAPI extends ServarrBase<{ seriesId: number; episodeId: number }> {
constructor({ url, apiKey }: { url: string; apiKey: string }) {
super(
url,
{
apikey: apiKey,
},
{
nodeCache: cacheManager.getCache('sonarr').data,
}
);
super({ url, apiKey, apiName: 'Sonarr', cacheName: 'sonarr' });
}
public async getSeries(): Promise<SonarrSeries[]> {
@@ -151,7 +93,7 @@ class SonarrAPI extends ExternalAPI {
return response.data;
} catch (e) {
throw new Error(`[Radarr] Failed to retrieve series: ${e.message}`);
throw new Error(`[Sonarr] Failed to retrieve series: ${e.message}`);
}
}
@@ -205,6 +147,7 @@ class SonarrAPI extends ExternalAPI {
// If the series already exists, we will simply just update it
if (series.id) {
series.tags = options.tags ?? series.tags;
series.seasons = this.buildSeasonList(options.seasons, series.seasons);
const newSeriesResponse = await this.axios.put<SonarrSeries>(
@@ -249,6 +192,7 @@ class SonarrAPI extends ExternalAPI {
monitored: false,
}))
),
tags: options.tags,
seasonFolder: options.seasonFolder,
monitored: options.monitored,
rootFolderPath: options.rootFolderPath,
@@ -286,46 +230,6 @@ class SonarrAPI extends ExternalAPI {
}
}
public async getProfiles(): Promise<SonarrProfile[]> {
try {
const data = await this.getRolling<SonarrProfile[]>(
'/qualityProfile',
undefined,
3600
);
return data;
} catch (e) {
logger.error('Something went wrong while retrieving Sonarr profiles.', {
label: 'Sonarr API',
message: e.message,
});
throw new Error('Failed to get profiles');
}
}
public async getRootFolders(): Promise<SonarrRootFolder[]> {
try {
const data = await this.getRolling<SonarrRootFolder[]>(
'/rootfolder',
undefined,
3600
);
return data;
} catch (e) {
logger.error(
'Something went wrong while retrieving Sonarr root folders.',
{
label: 'Sonarr API',
message: e.message,
}
);
throw new Error('Failed to get root folders');
}
}
public async getLanguageProfiles(): Promise<LanguageProfile[]> {
try {
const data = await this.getRolling<LanguageProfile[]>(
@@ -356,25 +260,6 @@ class SonarrAPI extends ExternalAPI {
await this.runCommand('SeriesSearch', { seriesId });
}
private async runCommand(
commandName: string,
options: Record<string, unknown>
): Promise<void> {
try {
await this.axios.post(`/command`, {
name: commandName,
...options,
});
} catch (e) {
logger.error('Something went wrong attempting to run a Sonarr command.', {
label: 'Sonarr API',
message: e.message,
});
throw new Error('Failed to run Sonarr command.');
}
}
private buildSeasonList(
seasons: number[],
existingSeasons?: SonarrSeason[]
@@ -399,16 +284,6 @@ class SonarrAPI extends ExternalAPI {
return newSeasons;
}
public getQueue = async (): Promise<QueueItem[]> => {
try {
const response = await this.axios.get<QueueResponse>(`/queue`);
return response.data.records;
} catch (e) {
throw new Error(`[Radarr] Failed to retrieve queue: ${e.message}`);
}
};
}
export default SonarrAPI;