mirror of
https://github.com/sct/overseerr.git
synced 2025-09-17 17:24:35 +02:00
feat: radarr/sonarr tag support (#1366)
This commit is contained in:
169
server/api/servarr/base.ts
Normal file
169
server/api/servarr/base.ts
Normal 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;
|
@@ -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;
|
@@ -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;
|
Reference in New Issue
Block a user