Merge branch 'develop' into feature/request-queue

This commit is contained in:
Jamie 2018-12-05 21:32:38 +00:00 committed by GitHub
commit 72c411d5d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 575 additions and 198 deletions

View file

@ -2,6 +2,43 @@
## (unreleased) ## (unreleased)
### **New Features**
- Updated the emby api since we no longer need the extra parameters to send to emby to log in a local user #2546. [Jamie]
- Added the ability to get the ombi user via a Plex Token #2591. [Jamie]
### **Fixes**
- Made the subscribe/unsubscribe button more obvious on the UI #2309. [Jamie]
- Fixed #2603. [Jamie]
- Fixed the issue with the user overrides #2646. [Jamie]
- Fixed the issue where we could sometimes allow the request of a whole series when the user shouldn't be able to. [Jamie]
- Fixed the issue where we were marking episodes as available with the Emby connection when they have not yet aired #2417 #2623. [TidusJar]
- Fixed the issue where we were marking the whole season as wanted in Sonarr rather than the individual episode #2629. [TidusJar]
- Fixed #2623. [Jamie]
- Fixed #2633. [TidusJar]
- Fixed #2639. [Jamie]
- Show the TV show as available when we have all the episodes but future episodes have not aired. #2585. [Jamie]
## v3.0.3945 (2018-10-25)
### **New Features**
- Update Readme for Lidarr. [Qstick]
- Update CHANGELOG.md. [Jamie]
### **Fixes** ### **Fixes**
- New translations en.json (French) [Jamie] - New translations en.json (French) [Jamie]

View file

@ -53,8 +53,6 @@ namespace Ombi.Api.Emby
{ {
username, username,
pw = password, pw = password,
password = password.GetSha1Hash().ToLower(),
passwordMd5 = password.CalcuateMd5Hash()
}; };
request.AddJsonBody(body); request.AddJsonBody(body);

View file

@ -0,0 +1,13 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Sonarr.Models;
using System.Net.Http;
using Ombi.Api.Sonarr.Models.V3;
namespace Ombi.Api.Sonarr
{
public interface ISonarrV3Api : ISonarrApi
{
Task<IEnumerable<LanguageProfiles>> LanguageProfiles(string apiKey, string baseUrl);
}
}

View file

@ -27,6 +27,9 @@ namespace Ombi.Api.Sonarr.Models
public int id { get; set; } public int id { get; set; }
public List<SonarrImage> images { get; set; } public List<SonarrImage> images { get; set; }
// V3 Property
public int languageProfileId { get; set; }
/// <summary> /// <summary>
/// This is for us /// This is for us
/// </summary> /// </summary>

View file

@ -0,0 +1,30 @@
namespace Ombi.Api.Sonarr.Models.V3
{
public class LanguageProfiles
{
public string name { get; set; }
public bool upgradeAllowed { get; set; }
public Cutoff cutoff { get; set; }
public Languages[] languages { get; set; }
public int id { get; set; }
}
public class Cutoff
{
public int id { get; set; }
public string name { get; set; }
}
public class Languages
{
public Language languages { get; set; }
public bool allowed { get; set; }
}
public class Language
{
public int id { get; set; }
public string name { get; set; }
}
}

View file

@ -16,18 +16,19 @@ namespace Ombi.Api.Sonarr
Api = api; Api = api;
} }
private IApi Api { get; } protected IApi Api { get; }
protected virtual string ApiBaseUrl => "/api/";
public async Task<IEnumerable<SonarrProfile>> GetProfiles(string apiKey, string baseUrl) public async Task<IEnumerable<SonarrProfile>> GetProfiles(string apiKey, string baseUrl)
{ {
var request = new Request("/api/profile", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}profile", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<SonarrProfile>>(request); return await Api.Request<List<SonarrProfile>>(request);
} }
public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders(string apiKey, string baseUrl) public async Task<IEnumerable<SonarrRootFolder>> GetRootFolders(string apiKey, string baseUrl)
{ {
var request = new Request("/api/rootfolder", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}rootfolder", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<SonarrRootFolder>>(request); return await Api.Request<List<SonarrRootFolder>>(request);
} }
@ -40,7 +41,7 @@ namespace Ombi.Api.Sonarr
/// <returns></returns> /// <returns></returns>
public async Task<IEnumerable<SonarrSeries>> GetSeries(string apiKey, string baseUrl) public async Task<IEnumerable<SonarrSeries>> GetSeries(string apiKey, string baseUrl)
{ {
var request = new Request("/api/series", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}series", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
var results = await Api.Request<List<SonarrSeries>>(request); var results = await Api.Request<List<SonarrSeries>>(request);
@ -63,7 +64,7 @@ namespace Ombi.Api.Sonarr
/// <returns></returns> /// <returns></returns>
public async Task<SonarrSeries> GetSeriesById(int id, string apiKey, string baseUrl) public async Task<SonarrSeries> GetSeriesById(int id, string apiKey, string baseUrl)
{ {
var request = new Request($"/api/series/{id}", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}series/{id}", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
var result = await Api.Request<SonarrSeries>(request); var result = await Api.Request<SonarrSeries>(request);
if (result?.seasons?.Length > 0) if (result?.seasons?.Length > 0)
@ -82,7 +83,7 @@ namespace Ombi.Api.Sonarr
/// <returns></returns> /// <returns></returns>
public async Task<SonarrSeries> UpdateSeries(SonarrSeries updated, string apiKey, string baseUrl) public async Task<SonarrSeries> UpdateSeries(SonarrSeries updated, string apiKey, string baseUrl)
{ {
var request = new Request("/api/series/", baseUrl, HttpMethod.Put); var request = new Request($"{ApiBaseUrl}series/", baseUrl, HttpMethod.Put);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(updated); request.AddJsonBody(updated);
return await Api.Request<SonarrSeries>(request); return await Api.Request<SonarrSeries>(request);
@ -94,7 +95,7 @@ namespace Ombi.Api.Sonarr
{ {
return new NewSeries { ErrorMessages = new List<string> { seriesToAdd.Validate() } }; return new NewSeries { ErrorMessages = new List<string> { seriesToAdd.Validate() } };
} }
var request = new Request("/api/series/", baseUrl, HttpMethod.Post); var request = new Request($"{ApiBaseUrl}series/", baseUrl, HttpMethod.Post);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(seriesToAdd); request.AddJsonBody(seriesToAdd);
@ -120,7 +121,7 @@ namespace Ombi.Api.Sonarr
/// <returns></returns> /// <returns></returns>
public async Task<IEnumerable<Episode>> GetEpisodes(int seriesId, string apiKey, string baseUrl) public async Task<IEnumerable<Episode>> GetEpisodes(int seriesId, string apiKey, string baseUrl)
{ {
var request = new Request($"/api/Episode?seriesId={seriesId}", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}Episode?seriesId={seriesId}", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<Episode>>(request); return await Api.Request<List<Episode>>(request);
} }
@ -134,14 +135,14 @@ namespace Ombi.Api.Sonarr
/// <returns></returns> /// <returns></returns>
public async Task<Episode> GetEpisodeById(int episodeId, string apiKey, string baseUrl) public async Task<Episode> GetEpisodeById(int episodeId, string apiKey, string baseUrl)
{ {
var request = new Request($"/api/Episode/{episodeId}", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}Episode/{episodeId}", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<Episode>(request); return await Api.Request<Episode>(request);
} }
public async Task<EpisodeUpdateResult> UpdateEpisode(Episode episodeToUpdate, string apiKey, string baseUrl) public async Task<EpisodeUpdateResult> UpdateEpisode(Episode episodeToUpdate, string apiKey, string baseUrl)
{ {
var request = new Request($"/api/Episode/", baseUrl, HttpMethod.Put); var request = new Request($"{ApiBaseUrl}Episode/", baseUrl, HttpMethod.Put);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(episodeToUpdate); request.AddJsonBody(episodeToUpdate);
return await Api.Request<EpisodeUpdateResult>(request); return await Api.Request<EpisodeUpdateResult>(request);
@ -189,7 +190,7 @@ namespace Ombi.Api.Sonarr
private async Task<CommandResult> Command(string apiKey, string baseUrl, object body) private async Task<CommandResult> Command(string apiKey, string baseUrl, object body)
{ {
var request = new Request($"/api/Command/", baseUrl, HttpMethod.Post); var request = new Request($"{ApiBaseUrl}Command/", baseUrl, HttpMethod.Post);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(body); request.AddJsonBody(body);
return await Api.Request<CommandResult>(request); return await Api.Request<CommandResult>(request);
@ -197,7 +198,7 @@ namespace Ombi.Api.Sonarr
public async Task<SystemStatus> SystemStatus(string apiKey, string baseUrl) public async Task<SystemStatus> SystemStatus(string apiKey, string baseUrl)
{ {
var request = new Request("/api/system/status", baseUrl, HttpMethod.Get); var request = new Request($"{ApiBaseUrl}system/status", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<SystemStatus>(request); return await Api.Request<SystemStatus>(request);
@ -217,7 +218,7 @@ namespace Ombi.Api.Sonarr
ignoreEpisodesWithoutFiles = false, ignoreEpisodesWithoutFiles = false,
} }
}; };
var request = new Request("/api/seasonpass", baseUrl, HttpMethod.Post); var request = new Request($"{ApiBaseUrl}seasonpass", baseUrl, HttpMethod.Post);
request.AddHeader("X-Api-Key", apiKey); request.AddHeader("X-Api-Key", apiKey);
request.AddJsonBody(seasonPass); request.AddJsonBody(seasonPass);

View file

@ -0,0 +1,25 @@
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using Ombi.Api.Sonarr.Models.V3;
namespace Ombi.Api.Sonarr
{
public class SonarrV3Api : SonarrApi, ISonarrV3Api
{
public SonarrV3Api(IApi api) : base(api)
{
}
protected override string ApiBaseUrl => "/api/v3/";
public async Task<IEnumerable<LanguageProfiles>> LanguageProfiles(string apiKey, string baseUrl)
{
var request = new Request($"{ApiBaseUrl}languageprofile", baseUrl, HttpMethod.Get);
request.AddHeader("X-Api-Key", apiKey);
return await Api.Request<List<LanguageProfiles>>(request);
}
}
}

View file

@ -116,6 +116,7 @@ namespace Ombi.Core.Engine
} }
// Remove the ID since this is a new child // Remove the ID since this is a new child
// This was a TVDBID for the request rules to run
tvBuilder.ChildRequest.Id = 0; tvBuilder.ChildRequest.Id = 0;
if (!tvBuilder.ChildRequest.SeasonRequests.Any()) if (!tvBuilder.ChildRequest.SeasonRequests.Any())
{ {

View file

@ -41,7 +41,7 @@ namespace Ombi.Core.Helpers
ShowInfo = await TvApi.ShowLookupByTheTvDbId(id); ShowInfo = await TvApi.ShowLookupByTheTvDbId(id);
Results = await MovieDbApi.SearchTv(ShowInfo.name); Results = await MovieDbApi.SearchTv(ShowInfo.name);
foreach (TvSearchResult result in Results) { foreach (TvSearchResult result in Results) {
if (result.Name == ShowInfo.name) if (result.Name.Equals(ShowInfo.name, StringComparison.InvariantCultureIgnoreCase))
{ {
var showIds = await MovieDbApi.GetTvExternals(result.Id); var showIds = await MovieDbApi.GetTvExternals(result.Id);
ShowInfo.externals.imdb = showIds.imdb_id; ShowInfo.externals.imdb = showIds.imdb_id;
@ -64,14 +64,15 @@ namespace Ombi.Core.Helpers
{ {
ChildRequest = new ChildRequests ChildRequest = new ChildRequests
{ {
Id = model.TvDbId, Id = model.TvDbId, // This is set to 0 after the request rules have run, the request rules needs it to identify the request
RequestType = RequestType.TvShow, RequestType = RequestType.TvShow,
RequestedDate = DateTime.UtcNow, RequestedDate = DateTime.UtcNow,
Approved = false, Approved = false,
RequestedUserId = userId, RequestedUserId = userId,
SeasonRequests = new List<SeasonRequests>(), SeasonRequests = new List<SeasonRequests>(),
Title = ShowInfo.name, Title = ShowInfo.name,
SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.OrdinalIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard ReleaseYear = FirstAir,
SeriesType = ShowInfo.genres.Any( s => s.Equals("Anime", StringComparison.InvariantCultureIgnoreCase)) ? SeriesType.Anime : SeriesType.Standard
}; };
return this; return this;

View file

@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Ombi.Core.Rule.Interfaces;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
namespace Ombi.Core.Rule.Rules.Request
{
public class ExistingPlexRequestRule : BaseRequestRule, IRules<BaseRequest>
{
public ExistingPlexRequestRule(IPlexContentRepository rv)
{
_plexContent = rv;
}
private readonly IPlexContentRepository _plexContent;
/// <summary>
/// We check if the request exists, if it does then we don't want to re-request it.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns></returns>
public async Task<RuleResult> Execute(BaseRequest obj)
{
if (obj.RequestType == RequestType.TvShow)
{
var tvRequest = (ChildRequests) obj;
var tvContent = _plexContent.GetAll().Where(x => x.Type == PlexMediaTypeEntity.Show);
// We need to do a check on the TVDBId
var anyTvDbMatches = await tvContent.Include(x => x.Episodes).FirstOrDefaultAsync(x => x.HasTvDb && x.TvDbId.Equals(tvRequest.Id.ToString())); // the Id on the child is the tvdbid at this point
if (anyTvDbMatches == null)
{
// So we do not have a TVDB Id, that really sucks.
// Let's try and match on the title and year of the show
var titleAndYearMatch = await tvContent.Include(x=> x.Episodes).FirstOrDefaultAsync(x =>
x.Title.Equals(tvRequest.Title, StringComparison.InvariantCultureIgnoreCase)
&& x.ReleaseYear == tvRequest.ReleaseYear.Year.ToString());
if (titleAndYearMatch != null)
{
// We have a match! Suprise Motherfucker
return CheckExistingContent(tvRequest, titleAndYearMatch);
}
// We do not have this
return Success();
}
// looks like we have a match on the TVDbID
return CheckExistingContent(tvRequest, anyTvDbMatches);
}
return Success();
}
private RuleResult CheckExistingContent(ChildRequests child, PlexServerContent content)
{
foreach (var season in child.SeasonRequests)
{
var currentSeasonRequest =
content.Episodes.Where(x => x.SeasonNumber == season.SeasonNumber).ToList();
if (!currentSeasonRequest.Any())
{
continue;
}
foreach (var e in season.Episodes)
{
var hasEpisode = currentSeasonRequest.Any(x => x.EpisodeNumber == e.EpisodeNumber);
if (hasEpisode)
{
return Fail($"We already have episodes requested from series {child.Title}");
}
}
}
return Success();
}
}
}

View file

@ -129,13 +129,17 @@ namespace Ombi.Core.Senders
var profiles = await _userProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId); var profiles = await _userProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == model.RequestedUserId);
if (profiles != null) if (profiles != null)
{ {
if (profiles.SonarrRootPathAnime > 0) if (profiles.RadarrRootPath > 0)
{ {
rootFolderPath = await RadarrRootPath(profiles.SonarrRootPathAnime, settings); var tempPath = await RadarrRootPath(profiles.RadarrRootPath, settings);
if (tempPath.HasValue())
{
rootFolderPath = tempPath;
} }
if (profiles.SonarrQualityProfileAnime > 0) }
if (profiles.RadarrQualityProfile > 0)
{ {
qualityToUse = profiles.SonarrQualityProfileAnime; qualityToUse = profiles.RadarrQualityProfile;
} }
} }
@ -191,7 +195,7 @@ namespace Ombi.Core.Senders
{ {
var paths = await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri); var paths = await RadarrApi.GetRootFolders(settings.ApiKey, settings.FullUri);
var selectedPath = paths.FirstOrDefault(x => x.id == overrideId); var selectedPath = paths.FirstOrDefault(x => x.id == overrideId);
return selectedPath.path; return selectedPath?.path ?? String.Empty;
} }
} }
} }

View file

@ -21,11 +21,12 @@ namespace Ombi.Core.Senders
{ {
public class TvSender : ITvSender public class TvSender : ITvSender
{ {
public TvSender(ISonarrApi sonarrApi, ILogger<TvSender> log, ISettingsService<SonarrSettings> sonarrSettings, public TvSender(ISonarrApi sonarrApi, ISonarrV3Api sonarrV3Api, ILogger<TvSender> log, ISettingsService<SonarrSettings> sonarrSettings,
ISettingsService<DogNzbSettings> dog, IDogNzbApi dogApi, ISettingsService<SickRageSettings> srSettings, ISettingsService<DogNzbSettings> dog, IDogNzbApi dogApi, ISettingsService<SickRageSettings> srSettings,
ISickRageApi srApi, IRepository<UserQualityProfiles> userProfiles, IRepository<RequestQueue> requestQueue, INotificationHelper notify) ISickRageApi srApi, IRepository<UserQualityProfiles> userProfiles, IRepository<RequestQueue> requestQueue, INotificationHelper notify)
{ {
SonarrApi = sonarrApi; SonarrApi = sonarrApi;
SonarrV3Api = sonarrV3Api;
Logger = log; Logger = log;
SonarrSettings = sonarrSettings; SonarrSettings = sonarrSettings;
DogNzbSettings = dog; DogNzbSettings = dog;
@ -38,6 +39,7 @@ namespace Ombi.Core.Senders
} }
private ISonarrApi SonarrApi { get; } private ISonarrApi SonarrApi { get; }
private ISonarrV3Api SonarrV3Api { get; }
private IDogNzbApi DogNzbApi { get; } private IDogNzbApi DogNzbApi { get; }
private ISickRageApi SickRageApi { get; } private ISickRageApi SickRageApi { get; }
private ILogger<TvSender> Logger { get; } private ILogger<TvSender> Logger { get; }
@ -212,6 +214,10 @@ namespace Ombi.Core.Senders
qualityToUse = model.ParentRequest.QualityOverride.Value; qualityToUse = model.ParentRequest.QualityOverride.Value;
} }
// Are we using v3 sonarr?
var sonarrV3 = s.V3;
var languageProfileId = s.LanguageProfile;
try try
{ {
// Does the series actually exist? // Does the series actually exist?
@ -241,6 +247,11 @@ namespace Ombi.Core.Senders
} }
}; };
if (sonarrV3)
{
newSeries.languageProfileId = languageProfileId;
}
// Montitor the correct seasons, // Montitor the correct seasons,
// If we have that season in the model then it's monitored! // If we have that season in the model then it's monitored!
var seasonsToAdd = GetSeasonsToCreate(model); var seasonsToAdd = GetSeasonsToCreate(model);
@ -392,7 +403,7 @@ namespace Ombi.Core.Senders
var sea = new Season var sea = new Season
{ {
seasonNumber = i, seasonNumber = i,
monitored = model.SeasonRequests.Any(x => x.SeasonNumber == index && x.SeasonNumber != 0) monitored = false
}; };
seasonsToUpdate.Add(sea); seasonsToUpdate.Add(sea);
} }

View file

@ -107,6 +107,7 @@ namespace Ombi.DependencyInjection
services.AddTransient<IPlexApi, PlexApi>(); services.AddTransient<IPlexApi, PlexApi>();
services.AddTransient<IEmbyApi, EmbyApi>(); services.AddTransient<IEmbyApi, EmbyApi>();
services.AddTransient<ISonarrApi, SonarrApi>(); services.AddTransient<ISonarrApi, SonarrApi>();
services.AddTransient<ISonarrV3Api, SonarrV3Api>();
services.AddTransient<ISlackApi, SlackApi>(); services.AddTransient<ISlackApi, SlackApi>();
services.AddTransient<ITvMazeApi, TvMazeApi>(); services.AddTransient<ITvMazeApi, TvMazeApi>();
services.AddTransient<ITraktApi, TraktApi>(); services.AddTransient<ITraktApi, TraktApi>();

View file

@ -82,6 +82,13 @@ namespace Ombi.Schedule.Jobs.Emby
foreach (var ep in allEpisodes.Items) foreach (var ep in allEpisodes.Items)
{ {
processed++; processed++;
if (ep.LocationType.Equals("Virtual", StringComparison.InvariantCultureIgnoreCase))
{
// For some reason Emby is not respecting the `IsVirtualItem` field.
continue;
}
// Let's make sure we have the parent request, stop those pesky forign key errors, // Let's make sure we have the parent request, stop those pesky forign key errors,
// Damn me having data integrity // Damn me having data integrity
var parent = await _repo.GetByEmbyId(ep.SeriesId); var parent = await _repo.GetByEmbyId(ep.SeriesId);

View file

@ -174,7 +174,7 @@ namespace Ombi.Schedule.Processor
var client = new GitHubClient(Octokit.ProductHeaderValue.Parse("OmbiV3")); var client = new GitHubClient(Octokit.ProductHeaderValue.Parse("OmbiV3"));
var releases = await client.Repository.Release.GetAll("tidusjar", "ombi"); var releases = await client.Repository.Release.GetAll("tidusjar", "ombi");
var latest = releases.FirstOrDefault(x => x.TagName == releaseTag); var latest = releases.FirstOrDefault(x => x.TagName.Equals(releaseTag, StringComparison.InvariantCultureIgnoreCase));
if (latest.Name.Contains("V2", CompareOptions.IgnoreCase)) if (latest.Name.Contains("V2", CompareOptions.IgnoreCase))
{ {
latest = null; latest = null;

View file

@ -18,5 +18,7 @@
public string QualityProfileAnime { get; set; } public string QualityProfileAnime { get; set; }
public string RootPathAnime { get; set; } public string RootPathAnime { get; set; }
public bool AddOnly { get; set; } public bool AddOnly { get; set; }
public bool V3 { get; set; }
public int LanguageProfile { get; set; }
} }
} }

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using Ombi.Store.Repository.Requests; using Ombi.Store.Repository.Requests;
@ -22,6 +23,8 @@ namespace Ombi.Store.Entities.Requests
[NotMapped] [NotMapped]
public bool ShowSubscribe { get; set; } public bool ShowSubscribe { get; set; }
[NotMapped]
public DateTime ReleaseYear { get; set; } // Used in the ExistingPlexRequestRule.cs
[ForeignKey(nameof(IssueId))] [ForeignKey(nameof(IssueId))]
public List<Issues> Issues { get; set; } public List<Issues> Issues { get; set; }

View file

@ -72,6 +72,8 @@ export interface ISonarrSettings extends IExternalSettings {
rootPathAnime: string; rootPathAnime: string;
fullRootPath: string; fullRootPath: string;
addOnly: boolean; addOnly: boolean;
v3: boolean;
languageProfile: number;
} }
export interface IRadarrSettings extends IExternalSettings { export interface IRadarrSettings extends IExternalSettings {

View file

@ -7,3 +7,8 @@ export interface ISonarrProfile {
name: string; name: string;
id: number; id: number;
} }
export interface ILanguageProfiles {
name: string;
id: number;
}

View file

@ -3,7 +3,7 @@
<div class="centered col-md-12"> <div class="centered col-md-12">
<div class="row"> <div class="row">
<div class="col-md-push-5 col-md-2"> <div class="col-md-push-3 col-md-6">
<div *ngIf="customizationSettings.logo"> <div *ngIf="customizationSettings.logo">
<img [src]="customizationSettings.logo" style="width:100%"/> <img [src]="customizationSettings.logo" style="width:100%"/>
</div> </div>

View file

@ -9,7 +9,7 @@
<a id="tvTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTvTab()" href="#tvTab"><i class="fa fa-television"></i> {{ 'Requests.TvTab' | translate }}</a> <a id="tvTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectTvTab()" href="#tvTab"><i class="fa fa-television"></i> {{ 'Requests.TvTab' | translate }}</a>
</li> </li>
<li role="presentation"> <li role="presentation" *ngIf="musicEnabled">
<a id="albumTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectMusicTab()" href="#albumTab"><i class="fa fa-music"></i> {{ 'Requests.MusicTab' | translate }}</a> <a id="albumTabButton" aria-controls="profile" role="tab" data-toggle="tab" (click)="selectMusicTab()" href="#albumTab"><i class="fa fa-music"></i> {{ 'Requests.MusicTab' | translate }}</a>
</li> </li>

View file

@ -15,6 +15,7 @@ export class RequestComponent implements OnInit {
public issueCategories: IIssueCategory[]; public issueCategories: IIssueCategory[];
public issuesEnabled = false; public issuesEnabled = false;
public musicEnabled: boolean;
constructor(private issuesService: IssuesService, constructor(private issuesService: IssuesService,
private settingsService: SettingsService) { private settingsService: SettingsService) {
@ -23,6 +24,7 @@ export class RequestComponent implements OnInit {
public ngOnInit(): void { public ngOnInit(): void {
this.issuesService.getCategories().subscribe(x => this.issueCategories = x); this.issuesService.getCategories().subscribe(x => this.issueCategories = x);
this.settingsService.lidarrEnabled().subscribe(x => this.musicEnabled = x);
this.settingsService.getIssueSettings().subscribe(x => this.issuesEnabled = x.enabled); this.settingsService.getIssueSettings().subscribe(x => this.issuesEnabled = x.enabled);
} }

View file

@ -2,7 +2,8 @@
<div role="tabpanel" class="tab-pane active" id="MoviesTab"> <div role="tabpanel" class="tab-pane active" id="MoviesTab">
<div class="input-group"> <div class="input-group">
<input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons" (keyup)="search($event)"> <input id="search" type="text" class="form-control form-control-custom form-control-search form-control-withbuttons"
(keyup)="search($event)">
<div class="input-group-addon right-radius"> <div class="input-group-addon right-radius">
<div class="btn-group"> <div class="btn-group">
<a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false"> <a href="#" class="btn btn-sm btn-primary-outline dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
@ -25,7 +26,8 @@
<!-- Movie content --> <!-- Movie content -->
<div id="movieList"> <div id="movieList">
<div *ngIf="searchApplied && movieResults?.length <= 0" class='no-search-results'> <div *ngIf="searchApplied && movieResults?.length <= 0" class='no-search-results'>
<i class='fa fa-film no-search-results-icon'></i><div class='no-search-results-text' [translate]="'Search.NoResults'"></div> <i class='fa fa-film no-search-results-icon'></i>
<div class='no-search-results-text' [translate]="'Search.NoResults'"></div>
</div> </div>
<div *ngFor="let result of movieResults"> <div *ngFor="let result of movieResults">
@ -44,18 +46,28 @@
<h4>{{result.title}} ({{result.releaseDate | amLocal | amDateFormat: 'YYYY'}})</h4> <h4>{{result.title}} ({{result.releaseDate | amLocal | amDateFormat: 'YYYY'}})</h4>
</a> </a>
<span class="tags"> <span class="tags">
<span *ngIf="result.releaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{ 'Search.TheatricalRelease' | translate: {date: result.releaseDate | amLocal | amDateFormat: 'LL'} }}</span> <span *ngIf="result.releaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{
<span *ngIf="result.digitalReleaseDate" class="label label-info" id="releaseDateLabel" target="_blank">{{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate | amLocal | amDateFormat: 'LL'} }}</span> 'Search.TheatricalRelease' | translate: {date: result.releaseDate | amLocal |
amDateFormat: 'LL'} }}</span>
<span *ngIf="result.digitalReleaseDate" class="label label-info" id="releaseDateLabel"
target="_blank">{{ 'Search.DigitalDate' | translate: {date: result.digitalReleaseDate |
amLocal | amDateFormat: 'LL'} }}</span>
<a *ngIf="result.homepage" href="{{result.homepage}}" id="homePageLabel" target="_blank"><span class="label label-info" [translate]="'Search.Movies.HomePage'"></span></a> <a *ngIf="result.homepage" href="{{result.homepage}}" id="homePageLabel" target="_blank"><span
class="label label-info" [translate]="'Search.Movies.HomePage'"></span></a>
<a *ngIf="result.trailer" href="{{result.trailer}}" id="trailerLabel" target="_blank"><span class="label label-info" [translate]="'Search.Movies.Trailer'"></span></a> <a *ngIf="result.trailer" href="{{result.trailer}}" id="trailerLabel" target="_blank"><span
class="label label-info" [translate]="'Search.Movies.Trailer'"></span></a>
<span *ngIf="result.quality" id="qualityLabel" class="label label-success">{{result.quality}}p</span> <span *ngIf="result.quality" id="qualityLabel" class="label label-success">{{result.quality}}p</span>
<ng-template [ngIf]="result.available"><span class="label label-success" id="availableLabel" [translate]="'Common.Available'"></span></ng-template> <ng-template [ngIf]="result.available"><span class="label label-success" id="availableLabel"
<ng-template [ngIf]="result.approved && !result.available"><span class="label label-info" id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span></ng-template> [translate]="'Common.Available'"></span></ng-template>
<ng-template [ngIf]="result.requested && !result.approved && !result.available"><span class="label label-warning" id="pendingApprovalLabel" [translate]="'Common.PendingApproval'"></span></ng-template> <ng-template [ngIf]="result.approved && !result.available"><span class="label label-info"
<ng-template [ngIf]="!result.requested && !result.available && !result.approved"><span class="label label-danger" id="notRequestedLabel" [translate]="'Common.NotRequested'"></span></ng-template> id="processingRequestLabel" [translate]="'Common.ProcessingRequest'"></span></ng-template>
<ng-template [ngIf]="result.requested && !result.approved && !result.available"><span class="label label-warning"
id="pendingApprovalLabel" [translate]="'Common.PendingApproval'"></span></ng-template>
<ng-template [ngIf]="!result.requested && !result.available && !result.approved"><span
class="label label-danger" id="notRequestedLabel" [translate]="'Common.NotRequested'"></span></ng-template>
</span> </span>
@ -67,37 +79,44 @@
<div class="col-sm-2 small-padding"> <div class="col-sm-2 small-padding">
<div class="row" *ngIf="result.requested">
<div class="col-md-2 col-md-push-10">
<a *ngIf="result.showSubscribe && !result.subscribed" style="color:white" (click)="subscribe(result)" pTooltip="Subscribe for notifications"> <i class="fa fa-rss"></i></a>
<a *ngIf="result.showSubscribe && result.subscribed" style="color:red" (click)="unSubscribe(result)" pTooltip="Unsubscribe notification"> <i class="fa fa-rss"></i></a>
</div>
</div>
<div *ngIf="result.available"> <div *ngIf="result.available">
<button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i> {{ 'Common.Available' | translate }}</button> <button style="text-align: right" class="btn btn-success-outline disabled" disabled><i class="fa fa-check"></i>
{{ 'Common.Available' | translate }}</button>
</div> </div>
<div *ngIf="!result.available"> <div *ngIf="!result.available">
<div *ngIf="result.requested || result.approved; then requestedBtn else notRequestedBtn"></div> <div *ngIf="result.requested || result.approved; then requestedBtn else notRequestedBtn"></div>
<ng-template #requestedBtn> <ng-template #requestedBtn>
<button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]><i class="fa fa-check"></i> {{ 'Common.Requested' | translate }}</button> <button style="text-align: right" class="btn btn-primary-outline disabled" [disabled]><i
class="fa fa-check"></i> {{ 'Common.Requested' | translate }}</button>
</ng-template> </ng-template>
<ng-template #notRequestedBtn> <ng-template #notRequestedBtn>
<button id="{{result.id}}" style="text-align: right" class="btn btn-primary-outline" (click)="request(result)"> <button id="{{result.id}}" style="text-align: right" class="btn btn-primary-outline"
<i *ngIf="result.requestProcessing" class="fa fa-circle-o-notch fa-spin fa-fw"></i> <i *ngIf="!result.requestProcessing && !result.processed" class="fa fa-plus"></i> (click)="request(result)">
<i *ngIf="result.processed && !result.requestProcessing" class="fa fa-check"></i> {{ 'Common.Request' | translate }}</button> <i *ngIf="result.requestProcessing" class="fa fa-circle-o-notch fa-spin fa-fw"></i> <i
*ngIf="!result.requestProcessing && !result.processed" class="fa fa-plus"></i>
<i *ngIf="result.processed && !result.requestProcessing" class="fa fa-check"></i> {{
'Common.Request' | translate }}</button>
</ng-template> </ng-template>
</div> </div>
<button style="text-align: right" class="btn btn-sm btn-info-outline" (click)="similarMovies(result.id)"> <i class="fa fa-eye"></i> {{ 'Search.Similar' | translate }}</button> <div *ngIf="result.requested">
<a *ngIf="result.showSubscribe && !result.subscribed" style="text-align: right" class="btn btn btn-success-outline"
(click)="subscribe(result)" pTooltip="Subscribe for notifications when this movie becomes available">
<i class="fa fa-rss"></i> Subscribe</a>
<a *ngIf="result.showSubscribe && result.subscribed" style="text-align: right;" class="btn btn btn-warning-outline"
(click)="unSubscribe(result)" pTooltip="Unsubscribe notifications when this movie becomes available">
<i class="fa fa-rss"></i> Unsubscribe</a>
</div>
<button style="text-align: right" class="btn btn-sm btn-info-outline" (click)="similarMovies(result.id)">
<i class="fa fa-eye"></i> {{ 'Search.Similar' | translate }}</button>
<br /> <br />
<div *ngIf="result.available"> <div *ngIf="result.available">
<a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Plex</a> <a *ngIf="result.plexUrl" style="text-align: right" class="btn btn-sm btn-success-outline" href="{{result.plexUrl}}" target="_blank"><i class="fa fa-eye"></i> {{'Search.ViewOnPlex' | translate}}</a>
<a *ngIf="result.embyUrl" style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline" href="{{result.embyUrl}}" target="_blank"><i class="fa fa-eye"></i> View On Emby</a> <a *ngIf="result.embyUrl" style="text-align: right" id="embybtn" class="btn btn-sm btn-success-outline" href="{{result.embyUrl}}" target="_blank"><i class="fa fa-eye"></i> {{'Search.ViewOnEmby' | translate}}</a>
</div> </div>
<div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled"> <div class="dropdown" *ngIf="result.available && issueCategories && issuesEnabled">
<button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"> <button class="btn btn-sm btn-primary-outline dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
<i class="fa fa-plus"></i> Report Issue <i class="fa fa-plus"></i> {{'Request.ReportIssue' | translate}}
<span class="caret"></span> <span class="caret"></span>
</button> </button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1"> <ul class="dropdown-menu" aria-labelledby="dropdownMenu1">

View file

@ -172,7 +172,7 @@ export class MovieSearchComponent implements OnInit {
r.subscribed = true; r.subscribed = true;
this.requestService.subscribeToMovie(r.requestId) this.requestService.subscribeToMovie(r.requestId)
.subscribe(x => { .subscribe(x => {
this.notificationService.success("Subscribed To Movie!"); this.notificationService.success(`Subscribed To Movie ${r.title}!`);
}); });
} }

View file

@ -5,7 +5,7 @@ import { HttpClient } from "@angular/common/http";
import { Observable } from "rxjs"; import { Observable } from "rxjs";
import { ISonarrSettings } from "../../interfaces"; import { ISonarrSettings } from "../../interfaces";
import { ISonarrProfile, ISonarrRootFolder } from "../../interfaces"; import { ILanguageProfiles, ISonarrProfile, ISonarrRootFolder } from "../../interfaces";
import { ServiceHelpers } from "../service.helpers"; import { ServiceHelpers } from "../service.helpers";
@Injectable() @Injectable()
@ -27,4 +27,8 @@ export class SonarrService extends ServiceHelpers {
public getQualityProfilesWithoutSettings(): Observable<ISonarrProfile[]> { public getQualityProfilesWithoutSettings(): Observable<ISonarrProfile[]> {
return this.http.get<ISonarrProfile[]>(`${this.url}/Profiles/`, {headers: this.headers}); return this.http.get<ISonarrProfile[]>(`${this.url}/Profiles/`, {headers: this.headers});
} }
public getV3LanguageProfiles(settings: ISonarrSettings): Observable<ILanguageProfiles[]> {
return this.http.post<ILanguageProfiles[]>(`${this.url}/v3/languageprofiles/`, JSON.stringify(settings), {headers: this.headers});
}
} }

View file

@ -19,25 +19,28 @@
<div class="form-group"> <div class="form-group">
<label for="Ip" class="control-label">Hostname or IP</label> <label for="Ip" class="control-label">Hostname or IP
<i *ngIf="form.get('ip').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="IP/Hostname is required"></i>
</label>
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" formControlName="ip" [ngClass]="{'form-error': form.get('ip').hasError('required')}"> <input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" formControlName="ip" [ngClass]="{'form-error': form.get('ip').hasError('required')}">
<small *ngIf="form.get('ip').hasError('required')" class="error-text">The IP/Hostname is required</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="portNumber" class="control-label">Port</label> <label for="portNumber" class="control-label">Port
<i *ngIf="form.get('port').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Port is required"></i></label>
<input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber" name="Port" placeholder="Port Number" [ngClass]="{'form-error': form.get('port').hasError('required')}"> <input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber" name="Port" placeholder="Port Number" [ngClass]="{'form-error': form.get('port').hasError('required')}">
<small *ngIf="form.get('port').hasError('required')" class="error-text">The Port is required</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="ApiKey" class="control-label">API Key</label> <label for="ApiKey" class="control-label">API Key <i *ngIf="form.get('apiKey').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="API Key is required"></i></label>
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}" id="ApiKey" name="ApiKey" formControlName="apiKey"> <input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}" id="ApiKey" name="ApiKey" formControlName="apiKey">
<small *ngIf="form.get('apiKey').hasError('required')" class="error-text">The API Key is required</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
@ -56,19 +59,22 @@
<div class="col-md-6"> <div class="col-md-6">
<div class="form-group"> <div class="form-group">
<label for="select" class="control-label">Quality Profiles</label> <label for="select" class="control-label">Quality Profiles
<i *ngIf="form.get('defaultQualityProfile').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Quality Profile is required"></i>
</label>
<div id="profiles"> <div id="profiles">
<select formControlName="defaultQualityProfile" class="form-control form-control-custom col-md-5 form-half" id="select" [ngClass]="{'form-error': form.get('defaultQualityProfile').hasError('required')}"> <select formControlName="defaultQualityProfile" class="form-control form-control-custom col-md-5 form-half" id="select" [ngClass]="{'form-error': form.get('defaultQualityProfile').hasError('required')}">
<option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option> <option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option>
</select> </select>
<button (click)="getProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button> <button (click)="getProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button>
<small *ngIf="form.get('defaultQualityProfile').hasError('required')" class="error-text">A Default Quality Profile is required</small>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="rootFolders" class="control-label">Default Root Folders</label> <label for="rootFolders" class="control-label">Default Root Folders
<i *ngIf="form.get('defaultRootPath').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Root Path is required"></i>
</label>
<div id="rootFolders"> <div id="rootFolders">
<select formControlName="defaultRootPath" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('defaultRootPath').hasError('required')}"> <select formControlName="defaultRootPath" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('defaultRootPath').hasError('required')}">
<option *ngFor="let folder of rootFolders" value="{{folder.path}}" >{{folder.path}}</option> <option *ngFor="let folder of rootFolders" value="{{folder.path}}" >{{folder.path}}</option>
@ -76,12 +82,14 @@
<button (click)="getRootFolders(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button> <button (click)="getRootFolders(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
</div> </div>
<small *ngIf="form.get('defaultRootPath').hasError('required')" class="error-text">A Default Root Path is required</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="languageProfileId" class="control-label">Language Profile</label> <label for="languageProfileId" class="control-label">Language Profile
<i *ngIf="form.get('languageProfileId').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Language Profile is required"></i>
</label>
<div id="languageProfileId"> <div id="languageProfileId">
<select formControlName="languageProfileId" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('languageProfileId').hasError('required')}"> <select formControlName="languageProfileId" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('languageProfileId').hasError('required')}">
<option *ngFor="let folder of languageProfiles" value="{{folder.id}}" >{{folder.name}}</option> <option *ngFor="let folder of languageProfiles" value="{{folder.id}}" >{{folder.name}}</option>
@ -89,12 +97,14 @@
<button (click)="getLanguageProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Languages <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button> <button (click)="getLanguageProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Languages <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
</div> </div>
<small *ngIf="form.get('languageProfileId').hasError('required')" class="error-text">A Language profile is required</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="metadataProfileId" class="control-label">Metadata Profile</label> <label for="metadataProfileId" class="control-label">Metadata Profile
<i *ngIf="form.get('metadataProfileId').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="Metadata Profile is required"></i>
</label>
<div id="metadataProfileId"> <div id="metadataProfileId">
<select formControlName="metadataProfileId" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('metadataProfileId').hasError('required')}"> <select formControlName="metadataProfileId" class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('metadataProfileId').hasError('required')}">
@ -102,7 +112,6 @@
</select> </select>
<button (click)="getMetadataProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Metadata <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button> <button (click)="getMetadataProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get Metadata <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
<small *ngIf="form.get('metadataProfileId').hasError('required')" class="error-text">A Metadata profile is required</small>
</div> </div>
</div> </div>

View file

@ -1,5 +1,4 @@
 <settings-menu></settings-menu>
<settings-menu></settings-menu>
<div *ngIf="form"> <div *ngIf="form">
<fieldset> <fieldset>
<legend>Radarr Settings</legend> <legend>Radarr Settings</legend>
@ -19,25 +18,34 @@
<div class="form-group"> <div class="form-group">
<label for="Ip" class="control-label">Hostname or IP</label> <label for="Ip" class="control-label">Hostname or IP
<i *ngIf="form.get('ip').hasError('required')" class="fa fa-exclamation-circle error-text"
pTooltip="IP/Hostname is required"></i>
</label>
<input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost" formControlName="ip" [ngClass]="{'form-error': form.get('ip').hasError('required')}"> <input type="text" class="form-control form-control-custom " id="Ip" name="Ip" placeholder="localhost"
<small *ngIf="form.get('ip').hasError('required')" class="error-text">The IP/Hostname is required</small> formControlName="ip" [ngClass]="{'form-error': form.get('ip').hasError('required')}">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="portNumber" class="control-label">Port</label> <label for="portNumber" class="control-label">Port
<i *ngIf="form.get('port').hasError('required')" class="fa fa-exclamation-circle error-text"
pTooltip="Port is required"></i>
</label>
<input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber" name="Port" placeholder="Port Number" [ngClass]="{'form-error': form.get('port').hasError('required')}"> <input type="text" class="form-control form-control-custom " formControlName="port" id="portNumber"
<small *ngIf="form.get('port').hasError('required')" class="error-text">The Port is required</small> name="Port" placeholder="Port Number" [ngClass]="{'form-error': form.get('port').hasError('required')}">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="ApiKey" class="control-label">API Key</label> <label for="ApiKey" class="control-label">API Key
<i *ngIf="form.get('apiKey').hasError('required')" class="fa fa-exclamation-circle error-text"
pTooltip="Api Key is required"></i>
</label>
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}" id="ApiKey" name="ApiKey" formControlName="apiKey"> <input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}"
<small *ngIf="form.get('apiKey').hasError('required')" class="error-text">The API Key is required</small> id="ApiKey" name="ApiKey" formControlName="apiKey">
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
@ -49,63 +57,73 @@
<div class="form-group"> <div class="form-group">
<label for="SubDir" class="control-label">Base Url</label> <label for="SubDir" class="control-label">Base Url</label>
<div> <div>
<input type="text" class="form-control form-control-custom" formControlName="subDir" id="SubDir" name="SubDir"> <input type="text" class="form-control form-control-custom" formControlName="subDir" id="SubDir"
name="SubDir">
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="form-group"> <div class="form-group">
<div> <label for="select" class="control-label">Quality Profiles
<button (click)="getProfiles(form)" type="button" class="btn btn-primary-outline">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button> <i *ngIf="form.get('defaultQualityProfile').hasError('required')" class="fa fa-exclamation-circle error-text"
</div> pTooltip="Quality Profile is required"></i>
</div> </label>
<div class="form-group">
<label for="select" class="control-label">Quality Profiles</label>
<div id="profiles"> <div id="profiles">
<select formControlName="defaultQualityProfile" class="form-control form-control-custom" id="select" [ngClass]="{'form-error': form.get('defaultQualityProfile').hasError('required')}"> <select formControlName="defaultQualityProfile" class="form-control form-control-custom col-md-5 form-half"
id="select" [ngClass]="{'form-error': form.get('defaultQualityProfile').hasError('required')}">
<option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option> <option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option>
</select> </select>
<button (click)="getProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get
Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"> </span></button>
</div> </div>
<small *ngIf="form.get('defaultQualityProfile').hasError('required')" class="error-text">A Default Quality Profile is required</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<div> <label for="rootFolders" class="control-label">Default Root Folders
<button (click)="getRootFolders(form)" type="button" class="btn btn-primary-outline">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
</div> <i *ngIf="form.get('defaultRootPath').hasError('required')" class="fa fa-exclamation-circle error-text"
pTooltip="Root Path is required"></i>
</div> </label>
<div class="form-group">
<label for="rootFolders" class="control-label">Default Root Folders</label>
<div id="rootFolders"> <div id="rootFolders">
<select formControlName="defaultRootPath" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('defaultRootPath').hasError('required')}"> <select formControlName="defaultRootPath" class="form-control form-control-custom col-md-5 form-half"
[ngClass]="{'form-error': form.get('defaultRootPath').hasError('required')}">
<option *ngFor="let folder of rootFolders" value="{{folder.path}}">{{folder.path}}</option> <option *ngFor="let folder of rootFolders" value="{{folder.path}}">{{folder.path}}</option>
</select> </select>
<button (click)="getRootFolders(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Get
Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
</div> </div>
<small *ngIf="form.get('defaultRootPath').hasError('required')" class="error-text">A Default Root Path is required</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="rootFolders" class="control-label">Default Minimum Availability</label> <label for="rootFolders" class="control-label">Default Minimum Availability
<i *ngIf="form.get('minimumAvailability').hasError('required')" class="fa fa-exclamation-circle error-text"
pTooltip="Minimum Availability is required"></i>
</label>
<div id="rootFolders"> <div id="rootFolders">
<select formControlName="minimumAvailability" class="form-control form-control-custom" [ngClass]="{'form-error': form.get('minimumAvailability').hasError('required')}"> <select formControlName="minimumAvailability" class="form-control form-control-custom col-md-5 form-half"
[ngClass]="{'form-error': form.get('minimumAvailability').hasError('required')}">
<option *ngFor="let min of minimumAvailabilityOptions" value="{{min.value}}">{{min.name}}</option> <option *ngFor="let min of minimumAvailabilityOptions" value="{{min.value}}">{{min.name}}</option>
</select> </select>
</div> </div>
<small *ngIf="form.get('minimumAvailability').hasError('required')" type="button" class="error-text">A Default Minimum Availability is required</small>
</div> </div>
</div>
<div class="col-md-6">
<div class="form-group" *ngIf="advanced" style="color:#ff761b"> <div class="form-group" *ngIf="advanced" style="color:#ff761b">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="addOnly" formControlName="addOnly"> <input type="checkbox" id="addOnly" formControlName="addOnly">
<label for="addOnly">Do not search</label> <label for="addOnly">Do not search</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div> <div>
<button type="button" [disabled]="form.invalid" (click)="test(form)" class="btn btn-primary-outline">Test Connectivity <span id="spinner"></span></button> <button type="button" [disabled]="form.invalid" (click)="test(form)" class="btn btn-primary-outline">Test
Connectivity <span id="spinner"></span></button>
</div> </div>
</div> </div>

View file

@ -54,7 +54,7 @@
<i class="fa fa-music" aria-hidden="true"></i> Music <span class="caret"></span> <i class="fa fa-music" aria-hidden="true"></i> Music <span class="caret"></span>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Lidarr']">Lidarr (beta)</a></li> <li [routerLinkActive]="['active']"><a [routerLink]="['/Settings/Lidarr']">Lidarr</a></li>
</ul> </ul>
</li> </li>

View file

@ -1,5 +1,4 @@
 <settings-menu></settings-menu>
<settings-menu></settings-menu>
<div *ngIf="form"> <div *ngIf="form">
<fieldset> <fieldset>
<legend>Sonarr Settings</legend> <legend>Sonarr Settings</legend>
@ -9,6 +8,7 @@
</div> </div>
<form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;"> <form novalidate [formGroup]="form" (ngSubmit)="onSubmit(form)" style="padding-top:5%;">
<div class="col-md-6">
<div class="col-md-6"> <div class="col-md-6">
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
@ -16,27 +16,46 @@
<label for="enable">Enable</label> <label for="enable">Enable</label>
</div> </div>
</div> </div>
</div>
<div class="col-md-6">
<div class="form-group"> <div class="form-group">
<label for="Ip" class="control-label">Sonarr Hostname or IP</label> <div class="checkbox">
<input type="checkbox" id="v3" formControlName="v3">
<input type="text" class="form-control form-control-custom " formControlName="ip" id="Ip" name="Ip" placeholder="localhost" [ngClass]="{'form-error': form.get('ip').hasError('required')}"> <label for="v3">V3</label>
<small *ngIf="form.get('ip').hasError('required')" class="error-text">The IP/Hostname is required</small> </div>
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="portNumber" class="control-label">Port</label> <label for="Ip" class="control-label">Sonarr Hostname or IP
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('port').hasError('required')}" formControlName="port" id="portNumber" name="Port" placeholder="Port Number"> <i *ngIf="form.get('ip').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="The IP/Hostname is required"></i>
<small *ngIf="form.get('port').hasError('required')" class="error-text">The Port is required</small> </label>
<input type="text" class="form-control form-control-custom " formControlName="ip" id="Ip" name="Ip"
placeholder="localhost" [ngClass]="{'form-error': form.get('ip').hasError('required')}">
</div>
<div class="form-group">
<label for="portNumber" class="control-label">Port
<i *ngIf="form.get('port').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="The Port is required"></i>
</label>
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('port').hasError('required')}"
formControlName="port" id="portNumber" name="Port" placeholder="Port Number">
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="ApiKey" class="control-label">Sonarr API Key</label> <label for="ApiKey" class="control-label">Sonarr API Key
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}" formControlName="apiKey" id="ApiKey" name="ApiKey"> <i *ngIf="form.get('apiKey').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="The API Key is required"></i>
<small *ngIf="form.get('apiKey').hasError('required')" class="error-text">The API Key is required</small> </label>
<input type="text" class="form-control form-control-custom " [ngClass]="{'form-error': form.get('apiKey').hasError('required')}"
formControlName="apiKey" id="ApiKey" name="ApiKey">
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="checkbox"> <div class="checkbox">
@ -48,63 +67,82 @@
<div class="form-group"> <div class="form-group">
<label for="SubDir" class="control-label">Sonarr Base Url</label> <label for="SubDir" class="control-label">Sonarr Base Url</label>
<div> <div>
<input type="text" class="form-control form-control-custom" formControlName="subDir" id="SubDir" name="SubDir"> <input type="text" class="form-control form-control-custom" formControlName="subDir" id="SubDir"
name="SubDir">
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="form-group">
<div> <div class="form-group col-md-12">
<button type="button" (click)="getProfiles(form)" class="btn btn-primary-outline">Get Quality Profiles <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"></span></button> <label for="profiles" class="control-label">Quality Profiles
</div> <i *ngIf="form.get('qualityProfile').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="A Default Quality Profile is required"></i>
</div> </label>
<div class="form-group">
<label for="select" class="control-label">Quality Profiles</label>
<div id="profiles"> <div id="profiles">
<select class="form-control form-control-custom" [ngClass]="{'form-error': form.get('qualityProfile').hasError('required')}" id="select" formControlName="qualityProfile"> <select class="form-control form-control-custom col-md-5 form-half" [ngClass]="{'form-error': form.get('qualityProfile').hasError('required')}"
id="select" formControlName="qualityProfile">
<option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option> <option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option>
</select> </select>
</div> <button type="button" (click)="getProfiles(form)" class="btn btn-primary-outline col-md-4 col-md-push-1">
<small *ngIf="form.get('qualityProfile').hasError('required')" class="error-text">A Default Quality Profile is required</small> Load Qualities <span *ngIf="profilesRunning" class="fa fa-spinner fa-spin"></span></button>
</div> </div>
<div class="form-group">
</div>
<div class="form-group col-md-12">
<label for="select" class="control-label">Quality Profiles (Anime)</label> <label for="select" class="control-label">Quality Profiles (Anime)</label>
<div id="qualityProfileAnime"> <div id="qualityProfileAnime">
<select class="form-control form-control-custom" id="qualityProfileAnime" formControlName="qualityProfileAnime"> <select class="form-control form-control-custom col-md-5 form-half" id="qualityProfileAnime" formControlName="qualityProfileAnime">
<option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option> <option *ngFor="let quality of qualities" value="{{quality.id}}">{{quality.name}}</option>
</select> </select>
</div> </div>
</div> </div>
<div class="form-group">
<div>
<button type="button" (click)="getRootFolders(form)" class="btn btn-primary-outline">Get Root Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
</div>
</div> <div class="form-group col-md-12">
<div class="form-group"> <label for="rootFolders" class="control-label">Default Root Folders
<label for="rootFolders" class="control-label">Default Root Folders</label>
<i *ngIf="form.get('rootPath').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="A Default Root Path is required"></i>
</label>
<div id="rootFolders"> <div id="rootFolders">
<select class="form-control form-control-custom" formControlName="rootPath" [ngClass]="{'form-error': form.get('rootPath').hasError('required')}"> <select class="form-control form-control-custom col-md-5 form-half" formControlName="rootPath"
[ngClass]="{'form-error': form.get('rootPath').hasError('required')}">
<option *ngFor="let folder of rootFolders" value="{{folder.id}}">{{folder.path}}</option> <option *ngFor="let folder of rootFolders" value="{{folder.id}}">{{folder.path}}</option>
</select> </select>
<button type="button" (click)="getRootFolders(form)" class="btn btn-primary-outline col-md-4 col-md-push-1">
Load Folders <span *ngIf="rootFoldersRunning" class="fa fa-spinner fa-spin"></span></button>
</div>
</div> </div>
<small *ngIf="form.get('rootPath').hasError('required')" class="error-text">A Default Root Path is required</small>
</div> <div class="form-group col-md-12">
<div class="form-group">
<label for="rootFoldersAnime" class="control-label">Default Root Folders (Anime)</label> <label for="rootFoldersAnime" class="control-label">Default Root Folders (Anime)</label>
<div id="rootFoldersAnime"> <div id="rootFoldersAnime">
<select class="form-control form-control-custom" formControlName="rootPathAnime"> <select class="form-control form-control-custom col-md-5 form-half" formControlName="rootPathAnime">
<option *ngFor="let folder of rootFoldersAnime" value="{{folder.id}}">{{folder.path}}</option> <option *ngFor="let folder of rootFoldersAnime" value="{{folder.id}}">{{folder.path}}</option>
</select> </select>
</div> </div>
</div> </div>
<div class="form-group col-md-12" *ngIf="form.controls.v3.value">
<label for="select" class="control-label">Language Profiles
<i *ngIf="form.get('languageProfile').hasError('required')" class="fa fa-exclamation-circle error-text" pTooltip="A Language Profile is required"></i>
</label>
<div id="langaugeProfile">
<select formControlName="languageProfile" class="form-control form-control-custom col-md-5 form-half"
id="select" [ngClass]="{'form-error': form.get('languageProfile').hasError('required')}">
<option *ngFor="let lang of languageProfiles" [ngValue]="lang.id">{{lang.name}}</option>
</select>
<button (click)="getLanguageProfiles(form)" type="button" class="btn btn-primary-outline col-md-4 col-md-push-1">Load
Languages <span *ngIf="langRunning" class="fa fa-spinner fa-spin"> </span></button>
<div class="form-group"> </div>
</div>
<div class="form-group col-md-12">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="SeasonFolders" name="SeasonFolders" formControlName="seasonFolders"> <input type="checkbox" id="SeasonFolders" name="SeasonFolders" formControlName="seasonFolders">
<label for="SeasonFolders">Enable season folders</label> <label for="SeasonFolders">Enable season folders</label>
@ -112,25 +150,26 @@
<label>Enabled Season Folders to organize seasons into individual folders within a show.</label> <label>Enabled Season Folders to organize seasons into individual folders within a show.</label>
</div> </div>
<div class="form-group" *ngIf="advanced" style="color:#ff761b"> <div class="form-group col-md-12" *ngIf="advanced" style="color:#ff761b">
<div class="checkbox"> <div class="checkbox">
<input type="checkbox" id="addOnly" formControlName="addOnly"> <input type="checkbox" id="addOnly" formControlName="addOnly">
<label for="addOnly">Do not search</label> <label for="addOnly">Do not search</label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group col-md-6">
<div>
<button type="button" (click)="test(form)" class="btn btn-primary-outline">Test Connectivity <span id="spinner"> </span></button>
</div>
</div>
<div class="form-group">
<div> <div>
<button type="submit" class="btn btn-primary-outline ">Submit</button> <button type="submit" class="btn btn-primary-outline ">Submit</button>
</div> </div>
</div> </div>
<div class="form-group col-md-6">
<div>
<button type="button" (click)="test(form)" class="btn btn-primary-outline">Test Connectivity
<span id="spinner"> </span></button>
</div>
</div>
</div> </div>
</form> </form>
</fieldset> </fieldset>

View file

@ -1,7 +1,7 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormGroup, Validators } from "@angular/forms"; import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { ISonarrProfile, ISonarrRootFolder } from "../../interfaces"; import { ILanguageProfiles, ISonarrProfile, ISonarrRootFolder } from "../../interfaces";
import { ISonarrSettings } from "../../interfaces"; import { ISonarrSettings } from "../../interfaces";
import { SonarrService } from "../../services"; import { SonarrService } from "../../services";
@ -18,10 +18,13 @@ export class SonarrComponent implements OnInit {
public qualitiesAnime: ISonarrProfile[]; public qualitiesAnime: ISonarrProfile[];
public rootFolders: ISonarrRootFolder[]; public rootFolders: ISonarrRootFolder[];
public rootFoldersAnime: ISonarrRootFolder[]; public rootFoldersAnime: ISonarrRootFolder[];
public languageProfiles: ILanguageProfiles[];
public selectedRootFolder: ISonarrRootFolder; public selectedRootFolder: ISonarrRootFolder;
public selectedQuality: ISonarrProfile; public selectedQuality: ISonarrProfile;
public selectedLanguageProfiles: ILanguageProfiles;
public profilesRunning: boolean; public profilesRunning: boolean;
public rootFoldersRunning: boolean; public rootFoldersRunning: boolean;
public langRunning: boolean;
public form: FormGroup; public form: FormGroup;
public advanced = false; public advanced = false;
@ -47,6 +50,8 @@ export class SonarrComponent implements OnInit {
port: [x.port, [Validators.required]], port: [x.port, [Validators.required]],
addOnly: [x.addOnly], addOnly: [x.addOnly],
seasonFolders: [x.seasonFolders], seasonFolders: [x.seasonFolders],
v3: [x.v3],
languageProfile: [x.languageProfile],
}); });
if (x.qualityProfile) { if (x.qualityProfile) {
@ -55,11 +60,19 @@ export class SonarrComponent implements OnInit {
if (x.rootPath) { if (x.rootPath) {
this.getRootFolders(this.form); this.getRootFolders(this.form);
} }
if(x.languageProfile) {
this.getLanguageProfiles(this.form);
}
if(x.v3) {
this.form.controls.languageProfile.setValidators([Validators.required]);
}
}); });
this.rootFolders = []; this.rootFolders = [];
this.qualities = []; this.qualities = [];
this.languageProfiles = [];
this.rootFolders.push({ path: "Please Select", id: -1 }); this.rootFolders.push({ path: "Please Select", id: -1 });
this.qualities.push({ name: "Please Select", id: -1 }); this.qualities.push({ name: "Please Select", id: -1 });
this.languageProfiles.push({ name: "Please Select", id: -1 });
} }
public getProfiles(form: FormGroup) { public getProfiles(form: FormGroup) {
@ -88,6 +101,18 @@ export class SonarrComponent implements OnInit {
}); });
} }
public getLanguageProfiles(form: FormGroup) {
this.langRunning = true;
this.sonarrService.getV3LanguageProfiles(form.value)
.subscribe(x => {
this.languageProfiles = x;
this.languageProfiles.unshift({ name: "Please Select", id: -1 });
this.langRunning = false;
this.notificationService.success("Successfully retrieved the Languge Profiles");
});
}
public test(form: FormGroup) { public test(form: FormGroup) {
if (form.invalid) { if (form.invalid) {
this.notificationService.error("Please check your entered values"); this.notificationService.error("Please check your entered values");

View file

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Ombi.Api.Sonarr; using Ombi.Api.Sonarr;
using Ombi.Api.Sonarr.Models; using Ombi.Api.Sonarr.Models;
using Ombi.Api.Sonarr.Models.V3;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External; using Ombi.Core.Settings.Models.External;
@ -16,14 +17,16 @@ namespace Ombi.Controllers.External
[Produces("application/json")] [Produces("application/json")]
public class SonarrController : Controller public class SonarrController : Controller
{ {
public SonarrController(ISonarrApi sonarr, ISettingsService<SonarrSettings> settings) public SonarrController(ISonarrApi sonarr, ISonarrV3Api sonarrv3, ISettingsService<SonarrSettings> settings)
{ {
SonarrApi = sonarr; SonarrApi = sonarr;
SonarrV3Api = sonarrv3;
SonarrSettings = settings; SonarrSettings = settings;
SonarrSettings.ClearCache(); SonarrSettings.ClearCache();
} }
private ISonarrApi SonarrApi { get; } private ISonarrApi SonarrApi { get; }
private ISonarrV3Api SonarrV3Api { get; }
private ISettingsService<SonarrSettings> SonarrSettings { get; } private ISettingsService<SonarrSettings> SonarrSettings { get; }
/// <summary> /// <summary>
@ -82,5 +85,36 @@ namespace Ombi.Controllers.External
return null; return null;
} }
/// <summary>
/// Gets the Sonarr V3 language profiles
/// </summary>
/// <returns></returns>
[HttpGet("v3/LanguageProfiles")]
[PowerUser]
public async Task<IEnumerable<LanguageProfiles>> GetLanguageProfiles()
{
var settings = await SonarrSettings.GetSettingsAsync();
if (settings.Enabled)
{
return await SonarrV3Api.LanguageProfiles(settings.ApiKey, settings.FullUri);
}
return null;
}
/// <summary>
/// Gets the Sonarr V3 language profiles
/// </summary>
/// <param name="settings">The settings.</param>
/// <returns></returns>
[HttpPost("v3/LanguageProfiles")]
[PowerUser]
public async Task<IEnumerable<LanguageProfiles>> GetLanguageProfiles([FromBody] SonarrSettings settings)
{
return await SonarrV3Api.LanguageProfiles(settings.ApiKey, settings.FullUri);
}
} }
} }

View file

@ -863,6 +863,7 @@ namespace Ombi.Controllers
{ {
var ombiUser = new OmbiUser var ombiUser = new OmbiUser
{ {
Alias = user.Alias,
Email = user.EmailAddress, Email = user.EmailAddress,
UserName = user.UserName UserName = user.UserName
}; };