Updated the Radarr API to support the new V3 endpoints. There's now a new V3 flag on the radarr settings

pull/3816/head
tidusjar 4 years ago
parent b31f8dd962
commit 26b2a574be

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Radarr.Models;
using Ombi.Api.Radarr.Models.V3;
namespace Ombi.Api.Radarr
{
public interface IRadarrV3Api
{
Task<List<MovieResponse>> GetMovies(string apiKey, string baseUrl);
Task<List<RadarrV3QualityProfile>> GetProfiles(string apiKey, string baseUrl);
Task<List<RadarrRootFolder>> GetRootFolders(string apiKey, string baseUrl);
Task<SystemStatus> SystemStatus(string apiKey, string baseUrl);
Task<MovieResponse> GetMovie(int id, string apiKey, string baseUrl);
Task<MovieResponse> UpdateMovie(MovieResponse movie, string apiKey, string baseUrl);
Task<bool> MovieSearch(int[] movieIds, string apiKey, string baseUrl);
Task<RadarrAddMovie> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath,string apiKey, string baseUrl, bool searchNow, string minimumAvailability);
Task<List<Tag>> GetTags(string apiKey, string baseUrl);
}
}

@ -1,25 +0,0 @@
namespace Ombi.Api.Radarr.Models
{
public class SystemStatus
{
public string version { get; set; }
public string buildTime { get; set; }
public bool isDebug { get; set; }
public bool isProduction { get; set; }
public bool isAdmin { get; set; }
public bool isUserInteractive { get; set; }
public string startupPath { get; set; }
public string appData { get; set; }
public string osVersion { get; set; }
public bool isMonoRuntime { get; set; }
public bool isMono { get; set; }
public bool isLinux { get; set; }
public bool isOsx { get; set; }
public bool isWindows { get; set; }
public string branch { get; set; }
public string authentication { get; set; }
public string sqliteVersion { get; set; }
public string urlBase { get; set; }
public string runtimeVersion { get; set; }
}
}

@ -0,0 +1,7 @@
namespace Ombi.Api.Radarr.Models
{
public class SystemStatus
{
public string version { get; set; }
}
}

@ -0,0 +1,29 @@
namespace Ombi.Api.Radarr.Models.V3
{
public class RadarrV3QualityProfile
{
public string name { get; set; }
public bool upgradeAllowed { get; set; }
public int cutoff { get; set; }
public string preferredTags { get; set; }
public Item[] items { get; set; }
public int id { get; set; }
}
public class Item
{
public Quality quality { get; set; }
public object[] items { get; set; }
public bool allowed { get; set; }
}
public class Quality
{
public int id { get; set; }
public string name { get; set; }
public string source { get; set; }
public int resolution { get; set; }
public string modifier { get; set; }
}
}

@ -0,0 +1,159 @@
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Ombi.Api.Radarr.Models;
using Ombi.Api.Radarr.Models.V3;
using Ombi.Helpers;
namespace Ombi.Api.Radarr
{
//https://radarr.video/docs/api/
public class RadarrV3Api : IRadarrV3Api
{
public RadarrV3Api(ILogger<RadarrV3Api> logger, IApi api)
{
Api = api;
Logger = logger;
}
private IApi Api { get; }
private ILogger Logger { get; }
public async Task<List<RadarrV3QualityProfile>> GetProfiles(string apiKey, string baseUrl)
{
var request = new Request("/api/v3/qualityProfile", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return await Api.Request<List<RadarrV3QualityProfile>>(request);
}
// TODO
public async Task<List<RadarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
{
var request = new Request("/api/v3/rootfolder", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return await Api.Request<List<RadarrRootFolder>>(request);
}
public async Task<SystemStatus> SystemStatus(string apiKey, string baseUrl)
{
var request = new Request("/api/v3/status", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return await Api.Request<SystemStatus>(request);
}
public async Task<List<MovieResponse>> GetMovies(string apiKey, string baseUrl)
{
var request = new Request("/api/v3/movie", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return await Api.Request<List<MovieResponse>>(request);
}
public async Task<MovieResponse> GetMovie(int id, string apiKey, string baseUrl)
{
var request = new Request($"/api/v3/movie/{id}", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return await Api.Request<MovieResponse>(request);
}
public async Task<MovieResponse> UpdateMovie(MovieResponse movie, string apiKey, string baseUrl)
{
var request = new Request($"/api/v3/movie/", baseUrl, HttpMethod.Put);
AddHeaders(request, apiKey);
request.AddJsonBody(movie);
return await Api.Request<MovieResponse>(request);
}
public async Task<RadarrAddMovie> AddMovie(int tmdbId, string title, int year, int qualityId, string rootPath, string apiKey, string baseUrl, bool searchNow, string minimumAvailability)
{
var request = new Request("/api/v3/movie", baseUrl, HttpMethod.Post);
var options = new RadarrAddMovieResponse
{
title = title,
tmdbId = tmdbId,
qualityProfileId = qualityId,
rootFolderPath = rootPath,
titleSlug = title + year,
monitored = true,
year = year,
minimumAvailability = minimumAvailability
};
if (searchNow)
{
options.addOptions = new RadarrAddOptions
{
searchForMovie = true
};
}
request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(options);
var response = await Api.RequestContent(request);
// TODO check if this is still correct, new API docs show validation as a 405 now
try
{
if (response.Contains("\"message\":"))
{
var error = JsonConvert.DeserializeObject<RadarrError>(response);
return new RadarrAddMovieResponse { Error = error };
}
if (response.Contains("\"errorMessage\":"))
{
var error = JsonConvert.DeserializeObject<List<RadarrErrorResponse>>(response).FirstOrDefault();
return new RadarrAddMovieResponse { Error = new RadarrError { message = error?.errorMessage } };
}
return JsonConvert.DeserializeObject<RadarrAddMovie>(response);
}
catch (JsonSerializationException jse)
{
Logger.LogError(LoggingEvents.RadarrApi, jse, "Error When adding movie to Radarr, Reponse: {0}", response);
}
return null;
}
public async Task<bool> MovieSearch(int[] movieIds, string apiKey, string baseUrl)
{
var result = await Command(apiKey, baseUrl, new { name = "MoviesSearch", movieIds });
return result != null;
}
public async Task<List<Tag>> GetTags(string apiKey, string baseUrl)
{
var request = new Request("/api/v3/tag", baseUrl, HttpMethod.Get);
AddHeaders(request, apiKey);
return await Api.Request<List<Tag>>(request);
}
private async Task<CommandResult> Command(string apiKey, string baseUrl, object body)
{
var request = new Request($"/api/v3/Command/", baseUrl, HttpMethod.Post);
request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(body);
return await Api.Request<CommandResult>(request);
}
/// <summary>
/// Adds the required headers and also the authorization header
/// </summary>
/// <param name="request"></param>
/// <param name="key"></param>
private void AddHeaders(Request request, string key)
{
request.AddHeader("X-Api-Key", key);
}
}
}

@ -13,6 +13,8 @@ using Ombi.Store.Entities.Requests;
using Ombi.Api.DogNzb;
using Ombi.Store.Entities;
using Ombi.Store.Repository;
using System.Collections.Generic;
using Ombi.Api.Radarr.Models;
namespace Ombi.Core.Senders
{
@ -20,44 +22,47 @@ namespace Ombi.Core.Senders
{
public MovieSender(ISettingsService<RadarrSettings> radarrSettings, IRadarrApi api, ILogger<MovieSender> log,
ISettingsService<DogNzbSettings> dogSettings, IDogNzbApi dogApi, ISettingsService<CouchPotatoSettings> cpSettings,
ICouchPotatoApi cpApi, IRepository<UserQualityProfiles> userProfiles, IRepository<RequestQueue> requestQueue, INotificationHelper notify)
ICouchPotatoApi cpApi, IRepository<UserQualityProfiles> userProfiles, IRepository<RequestQueue> requestQueue, INotificationHelper notify,
IRadarrV3Api radarrV3Api)
{
RadarrSettings = radarrSettings;
RadarrApi = api;
Log = log;
DogNzbSettings = dogSettings;
DogNzbApi = dogApi;
CouchPotatoSettings = cpSettings;
CouchPotatoApi = cpApi;
_radarrSettings = radarrSettings;
_radarrV2Api = api;
_log = log;
_dogNzbSettings = dogSettings;
_dogNzbApi = dogApi;
_couchPotatoSettings = cpSettings;
_couchPotatoApi = cpApi;
_userProfiles = userProfiles;
_requestQueuRepository = requestQueue;
_notificationHelper = notify;
_radarrV3Api = radarrV3Api;
}
private ISettingsService<RadarrSettings> RadarrSettings { get; }
private IRadarrApi RadarrApi { get; }
private ILogger<MovieSender> Log { get; }
private IDogNzbApi DogNzbApi { get; }
private ISettingsService<DogNzbSettings> DogNzbSettings { get; }
private ISettingsService<CouchPotatoSettings> CouchPotatoSettings { get; }
private ICouchPotatoApi CouchPotatoApi { get; }
private readonly ISettingsService<RadarrSettings> _radarrSettings;
private readonly IRadarrApi _radarrV2Api;
private readonly ILogger<MovieSender> _log;
private readonly IDogNzbApi _dogNzbApi;
private readonly ISettingsService<DogNzbSettings> _dogNzbSettings;
private readonly ISettingsService<CouchPotatoSettings> _couchPotatoSettings;
private readonly ICouchPotatoApi _couchPotatoApi;
private readonly IRepository<UserQualityProfiles> _userProfiles;
private readonly IRepository<RequestQueue> _requestQueuRepository;
private readonly INotificationHelper _notificationHelper;
private readonly IRadarrV3Api _radarrV3Api;
public async Task<SenderResult> Send(MovieRequests model)
{
try
{
var cpSettings = await CouchPotatoSettings.GetSettingsAsync();
var cpSettings = await _couchPotatoSettings.GetSettingsAsync();
//var watcherSettings = await WatcherSettings.GetSettingsAsync();
var radarrSettings = await RadarrSettings.GetSettingsAsync();
var radarrSettings = await _radarrSettings.GetSettingsAsync();
if (radarrSettings.Enabled)
{
return await SendToRadarr(model, radarrSettings);
}
var dogSettings = await DogNzbSettings.GetSettingsAsync();
var dogSettings = await _dogNzbSettings.GetSettingsAsync();
if (dogSettings.Enabled)
{
await SendToDogNzb(model, dogSettings);
@ -75,7 +80,7 @@ namespace Ombi.Core.Senders
}
catch (Exception e)
{
Log.LogError(e, "Error when sending movie to DVR app, added to the request queue");
_log.LogError(e, "Error when sending movie to DVR app, added to the request queue");
// Check if already in request quee
var existingQueue = await _requestQueuRepository.FirstOrDefaultAsync(x => x.RequestId == model.Id);
@ -108,19 +113,19 @@ namespace Ombi.Core.Senders
private async Task<SenderResult> SendToCp(FullBaseRequest model, CouchPotatoSettings cpSettings, string cpSettingsDefaultProfileId)
{
var result = await CouchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, cpSettings.FullUri, cpSettingsDefaultProfileId);
var result = await _couchPotatoApi.AddMovie(model.ImdbId, cpSettings.ApiKey, model.Title, cpSettings.FullUri, cpSettingsDefaultProfileId);
return new SenderResult { Success = result, Sent = true };
}
private async Task<DogNzbMovieAddResult> SendToDogNzb(FullBaseRequest model, DogNzbSettings settings)
{
var id = model.ImdbId;
return await DogNzbApi.AddMovie(settings.ApiKey, id);
return await _dogNzbApi.AddMovie(settings.ApiKey, id);
}
private async Task<SenderResult> SendToRadarr(MovieRequests model, RadarrSettings settings)
{
var v3 = settings.V3;
var qualityToUse = int.Parse(settings.DefaultQualityProfile);
var rootFolderPath = settings.DefaultRootPath;
@ -129,7 +134,7 @@ namespace Ombi.Core.Senders
if (profiles != null)
{
if (profiles.RadarrRootPath > 0)
{
{
var tempPath = await RadarrRootPath(profiles.RadarrRootPath, settings);
if (tempPath.HasValue())
{
@ -152,18 +157,35 @@ namespace Ombi.Core.Senders
rootFolderPath = await RadarrRootPath(model.RootPathOverride, settings);
}
List<MovieResponse> movies;
// Check if the movie already exists? Since it could be unmonitored
var movies = await RadarrApi.GetMovies(settings.ApiKey, settings.FullUri);
if (settings.V3)
{
movies = await _radarrV3Api.GetMovies(settings.ApiKey, settings.FullUri);
}
else
{
movies = await _radarrV2Api.GetMovies(settings.ApiKey, settings.FullUri);
}
var existingMovie = movies.FirstOrDefault(x => x.tmdbId == model.TheMovieDbId);
if (existingMovie == null)
{
var result = await RadarrApi.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year,
qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly,
settings.MinimumAvailability);
RadarrAddMovie result;
if (v3)
{
result = await _radarrV3Api.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year,
qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly,
settings.MinimumAvailability);
}
else
{
result = await _radarrV2Api.AddMovie(model.TheMovieDbId, model.Title, model.ReleaseDate.Year,
qualityToUse, rootFolderPath, settings.ApiKey, settings.FullUri, !settings.AddOnly,
settings.MinimumAvailability);
}
if (!string.IsNullOrEmpty(result.Error?.message))
{
Log.LogError(LoggingEvents.RadarrCacher, result.Error.message);
_log.LogError(LoggingEvents.RadarrCacher, result.Error.message);
return new SenderResult { Success = false, Message = result.Error.message, Sent = false };
}
if (!string.IsNullOrEmpty(result.title))
@ -177,11 +199,23 @@ namespace Ombi.Core.Senders
{
// let's set it to monitored and search for it
existingMovie.monitored = true;
await RadarrApi.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri);
// Search for it
if (!settings.AddOnly)
if (v3)
{
await RadarrApi.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri);
await _radarrV3Api.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri);
// Search for it
if (!settings.AddOnly)
{
await _radarrV3Api.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri);
}
}
else
{
await _radarrV2Api.UpdateMovie(existingMovie, settings.ApiKey, settings.FullUri);
// Search for it
if (!settings.AddOnly)
{
await _radarrV2Api.MovieSearch(new[] { existingMovie.id }, settings.ApiKey, settings.FullUri);
}
}
return new SenderResult { Success = true, Sent = true };
@ -192,9 +226,18 @@ namespace Ombi.Core.Senders
private async Task<string> RadarrRootPath(int overrideId, RadarrSettings settings)
{
var paths = await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
var selectedPath = paths.FirstOrDefault(x => x.id == overrideId);
return selectedPath?.path ?? String.Empty;
if (settings.V3)
{
var paths = await _radarrV3Api.GetRootFolders(settings.ApiKey, settings.FullUri);
var selectedPath = paths.FirstOrDefault(x => x.id == overrideId);
return selectedPath?.path ?? string.Empty;
}
else
{
var paths = await _radarrV2Api.GetRootFolders(settings.ApiKey, settings.FullUri);
var selectedPath = paths.FirstOrDefault(x => x.id == overrideId);
return selectedPath?.path ?? string.Empty;
}
}
}
}

@ -132,6 +132,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<ITvMazeApi, TvMazeApi>();
services.AddTransient<ITraktApi, TraktApi>();
services.AddTransient<IRadarrApi, RadarrApi>();
services.AddTransient<IRadarrV3Api, RadarrV3Api>();
services.AddTransient<IDiscordApi, DiscordApi>();
services.AddTransient<IPushbulletApi, PushbulletApi>();
services.AddTransient<IOmbiService, OmbiService>();

@ -1,10 +1,9 @@
using Ombi.Core.Settings.Models.External;
namespace Ombi.Settings.Settings.Models.External
namespace Ombi.Settings.Settings.Models.External
{
public class RadarrSettings : ExternalSettings
{
public bool Enabled { get; set; }
public bool V3 { get; set; }
public string ApiKey { get; set; }
public string DefaultQualityProfile { get; set; }
public string DefaultRootPath { get; set; }

@ -97,6 +97,7 @@ export interface IRadarrSettings extends IExternalSettings {
addOnly: boolean;
minimumAvailability: string;
scanForAvailability: boolean;
v3: boolean;
}
export interface ILidarrSettings extends IExternalSettings {

@ -11,11 +11,16 @@
<mat-slide-toggle formControlName="enabled" id="enable">Enable</mat-slide-toggle>
</div>
<div class="md-form-field">
<mat-slide-toggle [(ngModel)]="advanced" [ngModelOptions]="{standalone: true}">Advanced</mat-slide-toggle>
<mat-slide-toggle formControlName="v3">V3</mat-slide-toggle>
</div>
<div class="md-form-field">
<mat-slide-toggle formControlName="scanForAvailability">Scan for Availability</mat-slide-toggle>
</div>
<div class="md-form-field" >
<mat-slide-toggle formControlName="addOnly">
Do not search for Movies
</mat-slide-toggle>
</div>
<div class="md-form-field" style="margin-top:1em;"></div>
</div>
</div>
@ -92,11 +97,6 @@
</mat-select>
</mat-form-field>
</div>
<div class="md-form-field" *ngIf="advanced" style="color:#ff761b">
<mat-slide-toggle formControlName="addOnly">
Do not search
</mat-slide-toggle>
</div>
</div>
</div>
<div class="row">

@ -19,7 +19,6 @@ export class RadarrComponent implements OnInit {
public minimumAvailabilityOptions: IMinimumAvailability[];
public profilesRunning: boolean;
public rootFoldersRunning: boolean;
public advanced = false;
public form: FormGroup;
constructor(private settingsService: SettingsService,
@ -43,7 +42,8 @@ export class RadarrComponent implements OnInit {
port: [x.port, [Validators.required]],
addOnly: [x.addOnly],
minimumAvailability: [x.minimumAvailability, [Validators.required]],
scanForAvailability: [x.scanForAvailability]
scanForAvailability: [x.scanForAvailability],
v3: [x.v3]
});
if (x.defaultQualityProfile) {

@ -16,18 +16,21 @@ namespace Ombi.Controllers.V1.External
[ApiController]
[Produces("application/json")]
public class RadarrController : ControllerBase
{
{
public RadarrController(IRadarrApi radarr, ISettingsService<RadarrSettings> settings,
ICacheService mem)
ICacheService mem, IRadarrV3Api radarrV3Api)
{
RadarrApi = radarr;
RadarrSettings = settings;
Cache = mem;
_radarrApi = radarr;
_radarrSettings = settings;
_cache = mem;
_radarrV3Api = radarrV3Api;
}
private IRadarrApi RadarrApi { get; }
private ISettingsService<RadarrSettings> RadarrSettings { get; }
private ICacheService Cache { get; }
private readonly IRadarrApi _radarrApi;
private readonly ISettingsService<RadarrSettings> _radarrSettings;
private readonly ICacheService _cache;
private readonly IRadarrV3Api _radarrV3Api;
/// <summary>
/// Gets the Radarr profiles.
/// </summary>
@ -35,16 +38,20 @@ namespace Ombi.Controllers.V1.External
/// <returns></returns>
[HttpPost("Profiles")]
[PowerUser]
public async Task<IEnumerable<RadarrProfile>> GetProfiles([FromBody] RadarrSettings settings)
public async Task<IActionResult> GetProfiles([FromBody] RadarrSettings settings)
{
return await RadarrApi.GetProfiles(settings.ApiKey, settings.FullUri);
if (settings.V3)
{
return Ok(await _radarrV3Api.GetProfiles(settings.ApiKey, settings.FullUri));
}
return Ok(await _radarrApi.GetProfiles(settings.ApiKey, settings.FullUri));
}
[HttpGet("enabled")]
[PowerUser]
public async Task<bool> Enabled()
{
var settings = await RadarrSettings.GetSettingsAsync();
var settings = await _radarrSettings.GetSettingsAsync();
return settings.Enabled;
}
@ -57,7 +64,11 @@ namespace Ombi.Controllers.V1.External
[PowerUser]
public async Task<IEnumerable<RadarrRootFolder>> GetRootFolders([FromBody] RadarrSettings settings)
{
return await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
if (settings.V3)
{
return await _radarrV3Api.GetRootFolders(settings.ApiKey, settings.FullUri);
}
return await _radarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
}
/// <summary>
@ -67,12 +78,16 @@ namespace Ombi.Controllers.V1.External
/// <returns></returns>
[HttpGet("Profiles")]
[PowerUser]
public async Task<IEnumerable<RadarrProfile>> GetProfiles()
public async Task<IActionResult> GetProfiles()
{
var settings = await RadarrSettings.GetSettingsAsync();
var settings = await _radarrSettings.GetSettingsAsync();
if (settings.Enabled)
{
return await RadarrApi.GetProfiles(settings.ApiKey, settings.FullUri);
if (settings.V3)
{
return Ok(await _radarrV3Api.GetProfiles(settings.ApiKey, settings.FullUri));
}
return Ok(await _radarrApi.GetProfiles(settings.ApiKey, settings.FullUri));
}
return null;
}
@ -86,14 +101,18 @@ namespace Ombi.Controllers.V1.External
[PowerUser]
public async Task<IEnumerable<RadarrRootFolder>> GetRootFolders()
{
var settings = await RadarrSettings.GetSettingsAsync();
var settings = await _radarrSettings.GetSettingsAsync();
if (settings.Enabled)
{
return await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
if (settings.V3)
{
return await _radarrV3Api.GetRootFolders(settings.ApiKey, settings.FullUri);
}
return await _radarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
}
return null;
}
/// <summary>
/// Gets the Radarr tags
/// </summary>
@ -103,10 +122,10 @@ namespace Ombi.Controllers.V1.External
[PowerUser]
public async Task<IEnumerable<Tag>> GetTags([FromBody] SonarrSettings settings)
{
return await RadarrApi.GetTags(settings.ApiKey, settings.FullUri);
return await _radarrV3Api.GetTags(settings.ApiKey, settings.FullUri);
}
/// <summary>
/// Gets the Radarr tags
/// </summary>
@ -115,10 +134,10 @@ namespace Ombi.Controllers.V1.External
[PowerUser]
public async Task<IEnumerable<Tag>> GetTags()
{
var settings = await RadarrSettings.GetSettingsAsync();
var settings = await _radarrSettings.GetSettingsAsync();
if (settings.Enabled)
{
return await RadarrApi.GetTags(settings.ApiKey, settings.FullUri);
return await _radarrV3Api.GetTags(settings.ApiKey, settings.FullUri);
}
return null;

Loading…
Cancel
Save