Skip to content

Commit

Permalink
feat: Radarr & Sonarr Sync (#734)
Browse files Browse the repository at this point in the history
  • Loading branch information
sct committed Jan 27, 2021
1 parent 86efcd8 commit ec5fb83
Show file tree
Hide file tree
Showing 32 changed files with 2,376 additions and 407 deletions.
97 changes: 97 additions & 0 deletions overseerr-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,15 @@ components:
isDefault:
type: boolean
example: false
externalUrl:
type: string
example: http://radarr.example.com
syncEnabled:
type: boolean
example: false
preventSearch:
type: boolean
example: false
required:
- name
- hostname
Expand Down Expand Up @@ -352,6 +361,15 @@ components:
isDefault:
type: boolean
example: false
externalUrl:
type: string
example: http://radarr.example.com
syncEnabled:
type: boolean
example: false
preventSearch:
type: boolean
example: false
required:
- name
- hostname
Expand Down Expand Up @@ -1918,12 +1936,91 @@ paths:
items:
type: object
properties:
id:
type: string
example: job-name
name:
type: string
example: A Job Name
type:
type: string
enum: [process, command]
nextExecutionTime:
type: string
example: '2020-09-02T05:02:23.000Z'
running:
type: boolean
example: false
/settings/jobs/{jobId}/run:
get:
summary: Invoke a specific job
description: Invokes a specific job to run. Will return the new job status in JSON format.
tags:
- settings
parameters:
- in: path
name: jobId
required: true
schema:
type: string
responses:
'200':
description: Invoked job returned
content:
application/json:
schema:
type: object
properties:
id:
type: string
example: job-name
type:
type: string
enum: [process, command]
name:
type: string
example: A Job Name
nextExecutionTime:
type: string
example: '2020-09-02T05:02:23.000Z'
running:
type: boolean
example: false
/settings/jobs/{jobId}/cancel:
get:
summary: Cancel a specific job
description: Cancels a specific job. Will return the new job status in JSON format.
tags:
- settings
parameters:
- in: path
name: jobId
required: true
schema:
type: string
responses:
'200':
description: Cancelled job returned
content:
application/json:
schema:
type: object
properties:
id:
type: string
example: job-name
type:
type: string
enum: [process, command]
name:
type: string
example: A Job Name
nextExecutionTime:
type: string
example: '2020-09-02T05:02:23.000Z'
running:
type: boolean
example: false
/settings/notifications:
get:
summary: Return notification settings
Expand Down
92 changes: 84 additions & 8 deletions server/api/radarr.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Axios, { AxiosInstance } from 'axios';
import { RadarrSettings } from '../lib/settings';
import logger from '../logger';

interface RadarrMovieOptions {
Expand All @@ -13,12 +14,13 @@ interface RadarrMovieOptions {
searchNow?: boolean;
}

interface RadarrMovie {
export interface RadarrMovie {
id: number;
title: string;
isAvailable: boolean;
monitored: boolean;
tmdbId: number;
imdbId: string;
titleSlug: string;
folderName: string;
path: string;
Expand All @@ -45,7 +47,39 @@ export interface RadarrProfile {
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 {
static buildRadarrUrl(radarrSettings: RadarrSettings, path?: string): string {
return `${radarrSettings.useSsl ? 'https' : 'http'}://${
radarrSettings.hostname
}:${radarrSettings.port}${radarrSettings.baseUrl ?? ''}${path}`;
}

private axios: AxiosInstance;
constructor({ url, apiKey }: { url: string; apiKey: string }) {
this.axios = Axios.create({
Expand Down Expand Up @@ -76,8 +110,43 @@ class RadarrAPI {
}
};

public addMovie = async (options: RadarrMovieOptions): Promise<boolean> => {
public async getMovieByTmdbId(id: number): Promise<RadarrMovie> {
try {
const response = await this.axios.get<RadarrMovie[]>('/movie/lookup', {
params: {
term: `tmdb:${id}`,
},
});

if (!response.data[0]) {
throw new Error('Movie not found');
}

return response.data[0];
} catch (e) {
logger.error('Error retrieving movie by TMDb ID', {
label: 'Radarr API',
message: e.message,
});
throw new Error('Movie not found');
}
}

public addMovie = async (
options: RadarrMovieOptions
): Promise<RadarrMovie> => {
try {
// Check if movie already exists
const existing = await this.getMovieByTmdbId(options.tmdbId);

if (existing) {
logger.info(
'Movie already exists in Radarr. Skipping add and returning success',
{ label: 'Radarr' }
);
return existing;
}

const response = await this.axios.post<RadarrMovie>(`/movie`, {
title: options.title,
qualityProfileId: options.qualityProfileId,
Expand All @@ -104,9 +173,9 @@ class RadarrAPI {
label: 'Radarr',
options,
});
return false;
throw new Error('Failed to add movie to Radarr');
}
return true;
return response.data;
} catch (e) {
logger.error(
'Failed to add movie to Radarr. This might happen if the movie already exists, in which case you can safely ignore this error.',
Expand All @@ -117,10 +186,7 @@ class RadarrAPI {
response: e?.response?.data,
}
);
if (e?.response?.data?.[0]?.errorCode === 'MovieExistsValidator') {
return true;
}
return false;
throw new Error('Failed to add movie to Radarr');
}
};

Expand All @@ -143,6 +209,16 @@ class RadarrAPI {
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;

0 comments on commit ec5fb83

Please sign in to comment.