New: Improved Plex Media Server authentication (Manually update settings)
This commit is contained in:
parent
897f3fea99
commit
07be9cf47a
|
@ -1,4 +1,4 @@
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Annotations;
|
using NzbDrone.Core.Annotations;
|
||||||
using NzbDrone.Test.Common;
|
using NzbDrone.Test.Common;
|
||||||
|
@ -28,8 +28,8 @@ namespace NzbDrone.Api.Test.ClientSchemaTests
|
||||||
|
|
||||||
var schema = SchemaBuilder.ToSchema(model);
|
var schema = SchemaBuilder.ToSchema(model);
|
||||||
|
|
||||||
schema.Should().Contain(c => c.Order == 1 && c.Name == "LastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string)c.Value == "Poop");
|
schema.Should().Contain(c => c.Order == 1 && c.Name == "lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string)c.Value == "Poop");
|
||||||
schema.Should().Contain(c => c.Order == 0 && c.Name == "FirstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string)c.Value == "Bob");
|
schema.Should().Contain(c => c.Order == 0 && c.Name == "firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string)c.Value == "Bob");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,9 +42,9 @@ namespace NzbDrone.Api.Test.ClientSchemaTests
|
||||||
|
|
||||||
var schema = SchemaBuilder.ToSchema(model);
|
var schema = SchemaBuilder.ToSchema(model);
|
||||||
|
|
||||||
schema.Should().Contain(c => c.Order == 0 && c.Name == "Name.FirstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string)c.Value == "Bob");
|
schema.Should().Contain(c => c.Order == 0 && c.Name == "name.firstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string)c.Value == "Bob");
|
||||||
schema.Should().Contain(c => c.Order == 1 && c.Name == "Name.LastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string)c.Value == "Poop");
|
schema.Should().Contain(c => c.Order == 1 && c.Name == "name.lastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string)c.Value == "Poop");
|
||||||
schema.Should().Contain(c => c.Order == 2 && c.Name == "Quote" && c.Label == "Quote" && c.HelpText == "Your Favorite Quote");
|
schema.Should().Contain(c => c.Order == 2 && c.Name == "quote" && c.Label == "Quote" && c.HelpText == "Your Favorite Quote");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using FluentValidation.Results;
|
using FluentValidation.Results;
|
||||||
|
@ -8,6 +8,7 @@ using NzbDrone.Common.Reflection;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
using Sonarr.Http;
|
using Sonarr.Http;
|
||||||
using Sonarr.Http.ClientSchema;
|
using Sonarr.Http.ClientSchema;
|
||||||
using Sonarr.Http.Mapping;
|
using Sonarr.Http.Mapping;
|
||||||
|
@ -191,7 +192,7 @@ namespace NzbDrone.Api
|
||||||
var query = ((IDictionary<string, object>)Request.Query.ToDictionary()).ToDictionary(k => k.Key, k => k.Value.ToString());
|
var query = ((IDictionary<string, object>)Request.Query.ToDictionary()).ToDictionary(k => k.Key, k => k.Value.ToString());
|
||||||
|
|
||||||
var data = _providerFactory.RequestAction(providerDefinition, action, query);
|
var data = _providerFactory.RequestAction(providerDefinition, action, query);
|
||||||
Response resp = JsonConvert.SerializeObject(data);
|
Response resp = data.ToJson();
|
||||||
resp.ContentType = "application/json";
|
resp.ContentType = "application/json";
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
using Moq;
|
using Moq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
using NzbDrone.Core.Notifications.Plex;
|
using NzbDrone.Core.Notifications.Plex.HomeTheater;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.NotificationTests
|
namespace NzbDrone.Core.Test.NotificationTests
|
||||||
|
@ -69,4 +69,4 @@ namespace NzbDrone.Core.Test.NotificationTests
|
||||||
fakeHttp.Verify(v => v.DownloadString(expectedUrl, "plex", "plex"), Times.Once());
|
fakeHttp.Verify(v => v.DownloadString(expectedUrl, "plex", "plex"), Times.Once());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,6 +306,8 @@ namespace NzbDrone.Core.Configuration
|
||||||
set { SetValue("CleanupMetadataImages", value); }
|
set { SetValue("CleanupMetadataImages", value); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string PlexClientIdentifier => GetValue("PlexClientIdentifier", Guid.NewGuid().ToString(), true);
|
||||||
|
|
||||||
public string RijndaelPassphrase => GetValue("RijndaelPassphrase", Guid.NewGuid().ToString(), true);
|
public string RijndaelPassphrase => GetValue("RijndaelPassphrase", Guid.NewGuid().ToString(), true);
|
||||||
|
|
||||||
public string HmacPassphrase => GetValue("HmacPassphrase", Guid.NewGuid().ToString(), true);
|
public string HmacPassphrase => GetValue("HmacPassphrase", Guid.NewGuid().ToString(), true);
|
||||||
|
|
|
@ -59,7 +59,7 @@ namespace NzbDrone.Core.Configuration
|
||||||
|
|
||||||
//Internal
|
//Internal
|
||||||
bool CleanupMetadataImages { get; set; }
|
bool CleanupMetadataImages { get; set; }
|
||||||
|
string PlexClientIdentifier { get; }
|
||||||
|
|
||||||
//Forms Auth
|
//Forms Auth
|
||||||
string RijndaelPassphrase { get; }
|
string RijndaelPassphrase { get; }
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FluentValidation.Results;
|
using FluentValidation.Results;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Tv;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex
|
namespace NzbDrone.Core.Notifications.Plex.HomeTheater
|
||||||
{
|
{
|
||||||
public class PlexClient : NotificationBase<PlexClientSettings>
|
public class PlexClient : NotificationBase<PlexClientSettings>
|
||||||
{
|
{
|
|
@ -3,7 +3,7 @@ using FluentValidation.Results;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Http;
|
using NzbDrone.Common.Http;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex
|
namespace NzbDrone.Core.Notifications.Plex.HomeTheater
|
||||||
{
|
{
|
||||||
public interface IPlexClientService
|
public interface IPlexClientService
|
||||||
{
|
{
|
|
@ -3,7 +3,7 @@ using NzbDrone.Core.Annotations;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex
|
namespace NzbDrone.Core.Notifications.Plex.HomeTheater
|
||||||
{
|
{
|
||||||
public class PlexClientSettingsValidator : AbstractValidator<PlexClientSettings>
|
public class PlexClientSettingsValidator : AbstractValidator<PlexClientSettings>
|
||||||
{
|
{
|
|
@ -4,9 +4,8 @@ using FluentValidation.Results;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Notifications.Xbmc;
|
using NzbDrone.Core.Notifications.Xbmc;
|
||||||
using NzbDrone.Core.Tv;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex
|
namespace NzbDrone.Core.Notifications.Plex.HomeTheater
|
||||||
{
|
{
|
||||||
public class PlexHomeTheater : NotificationBase<PlexHomeTheaterSettings>
|
public class PlexHomeTheater : NotificationBase<PlexHomeTheaterSettings>
|
||||||
{
|
{
|
|
@ -1,7 +1,7 @@
|
||||||
using NzbDrone.Core.Annotations;
|
using NzbDrone.Core.Annotations;
|
||||||
using NzbDrone.Core.Notifications.Xbmc;
|
using NzbDrone.Core.Notifications.Xbmc;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex
|
namespace NzbDrone.Core.Notifications.Plex.HomeTheater
|
||||||
{
|
{
|
||||||
public class PlexHomeTheaterSettings : XbmcSettings
|
public class PlexHomeTheaterSettings : XbmcSettings
|
||||||
{
|
{
|
|
@ -1,47 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using FluentValidation.Results;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Core.Tv;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex
|
|
||||||
{
|
|
||||||
public class PlexServer : NotificationBase<PlexServerSettings>
|
|
||||||
{
|
|
||||||
private readonly IPlexServerService _plexServerService;
|
|
||||||
|
|
||||||
public PlexServer(IPlexServerService plexServerService)
|
|
||||||
{
|
|
||||||
_plexServerService = plexServerService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Link => "https://www.plex.tv/";
|
|
||||||
public override string Name => "Plex Media Server";
|
|
||||||
|
|
||||||
public override void OnDownload(DownloadMessage message)
|
|
||||||
{
|
|
||||||
UpdateIfEnabled(message.Series);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnRename(Series series)
|
|
||||||
{
|
|
||||||
UpdateIfEnabled(series);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateIfEnabled(Series series)
|
|
||||||
{
|
|
||||||
if (Settings.UpdateLibrary)
|
|
||||||
{
|
|
||||||
_plexServerService.UpdateLibrary(series, Settings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ValidationResult Test()
|
|
||||||
{
|
|
||||||
var failures = new List<ValidationFailure>();
|
|
||||||
|
|
||||||
failures.AddIfNotNull(_plexServerService.Test(Settings));
|
|
||||||
|
|
||||||
return new ValidationResult(failures);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,268 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using NLog;
|
|
||||||
using NzbDrone.Common.Cache;
|
|
||||||
using NzbDrone.Common.EnvironmentInfo;
|
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
using NzbDrone.Common.Serializer;
|
|
||||||
using NzbDrone.Core.Notifications.Plex.Models;
|
|
||||||
using NzbDrone.Core.Rest;
|
|
||||||
using RestSharp;
|
|
||||||
using RestSharp.Authenticators;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex
|
|
||||||
{
|
|
||||||
public interface IPlexServerProxy
|
|
||||||
{
|
|
||||||
List<PlexSection> GetTvSections(PlexServerSettings settings);
|
|
||||||
void Update(int sectionId, PlexServerSettings settings);
|
|
||||||
void UpdateSeries(int metadataId, PlexServerSettings settings);
|
|
||||||
string Version(PlexServerSettings settings);
|
|
||||||
List<PlexPreference> Preferences(PlexServerSettings settings);
|
|
||||||
int? GetMetadataId(int sectionId, int tvdbId, string language, PlexServerSettings settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class PlexServerProxy : IPlexServerProxy
|
|
||||||
{
|
|
||||||
private readonly ICached<string> _authCache;
|
|
||||||
private readonly Logger _logger;
|
|
||||||
|
|
||||||
public PlexServerProxy(ICacheManager cacheManager, Logger logger)
|
|
||||||
{
|
|
||||||
_authCache = cacheManager.GetCache<string>(GetType(), "authCache");
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PlexSection> GetTvSections(PlexServerSettings settings)
|
|
||||||
{
|
|
||||||
var request = GetPlexServerRequest("library/sections", Method.GET, settings);
|
|
||||||
var client = GetPlexServerClient(settings);
|
|
||||||
var response = client.Execute(request);
|
|
||||||
|
|
||||||
_logger.Trace("Sections response: {0}", response.Content);
|
|
||||||
CheckForError(response, settings);
|
|
||||||
|
|
||||||
if (response.Content.Contains("_children"))
|
|
||||||
{
|
|
||||||
return Json.Deserialize<PlexMediaContainerLegacy>(response.Content)
|
|
||||||
.Sections
|
|
||||||
.Where(d => d.Type == "show")
|
|
||||||
.Select(s => new PlexSection
|
|
||||||
{
|
|
||||||
Id = s.Id,
|
|
||||||
Language = s.Language,
|
|
||||||
Locations = s.Locations,
|
|
||||||
Type = s.Type
|
|
||||||
})
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Json.Deserialize<PlexResponse<PlexSectionsContainer>>(response.Content)
|
|
||||||
.MediaContainer
|
|
||||||
.Sections
|
|
||||||
.Where(d => d.Type == "show")
|
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update(int sectionId, PlexServerSettings settings)
|
|
||||||
{
|
|
||||||
var resource = string.Format("library/sections/{0}/refresh", sectionId);
|
|
||||||
var request = GetPlexServerRequest(resource, Method.GET, settings);
|
|
||||||
var client = GetPlexServerClient(settings);
|
|
||||||
var response = client.Execute(request);
|
|
||||||
|
|
||||||
_logger.Trace("Update response: {0}", response.Content);
|
|
||||||
CheckForError(response, settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateSeries(int metadataId, PlexServerSettings settings)
|
|
||||||
{
|
|
||||||
var resource = string.Format("library/metadata/{0}/refresh", metadataId);
|
|
||||||
var request = GetPlexServerRequest(resource, Method.PUT, settings);
|
|
||||||
var client = GetPlexServerClient(settings);
|
|
||||||
var response = client.Execute(request);
|
|
||||||
|
|
||||||
_logger.Trace("Update Series response: {0}", response.Content);
|
|
||||||
CheckForError(response, settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Version(PlexServerSettings settings)
|
|
||||||
{
|
|
||||||
var request = GetPlexServerRequest("identity", Method.GET, settings);
|
|
||||||
var client = GetPlexServerClient(settings);
|
|
||||||
var response = client.Execute(request);
|
|
||||||
|
|
||||||
_logger.Trace("Version response: {0}", response.Content);
|
|
||||||
CheckForError(response, settings);
|
|
||||||
|
|
||||||
if (response.Content.Contains("_children"))
|
|
||||||
{
|
|
||||||
return Json.Deserialize<PlexIdentity>(response.Content)
|
|
||||||
.Version;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Json.Deserialize<PlexResponse<PlexIdentity>>(response.Content)
|
|
||||||
.MediaContainer
|
|
||||||
.Version;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<PlexPreference> Preferences(PlexServerSettings settings)
|
|
||||||
{
|
|
||||||
var request = GetPlexServerRequest(":/prefs", Method.GET, settings);
|
|
||||||
var client = GetPlexServerClient(settings);
|
|
||||||
var response = client.Execute(request);
|
|
||||||
|
|
||||||
_logger.Trace("Preferences response: {0}", response.Content);
|
|
||||||
CheckForError(response, settings);
|
|
||||||
|
|
||||||
if (response.Content.Contains("_children"))
|
|
||||||
{
|
|
||||||
return Json.Deserialize<PlexPreferencesLegacy>(response.Content)
|
|
||||||
.Preferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Json.Deserialize<PlexResponse<PlexPreferences>>(response.Content)
|
|
||||||
.MediaContainer
|
|
||||||
.Preferences;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int? GetMetadataId(int sectionId, int tvdbId, string language, PlexServerSettings settings)
|
|
||||||
{
|
|
||||||
var guid = string.Format("com.plexapp.agents.thetvdb://{0}?lang={1}", tvdbId, language);
|
|
||||||
var resource = string.Format("library/sections/{0}/all?guid={1}", sectionId, System.Web.HttpUtility.UrlEncode(guid));
|
|
||||||
var request = GetPlexServerRequest(resource, Method.GET, settings);
|
|
||||||
var client = GetPlexServerClient(settings);
|
|
||||||
var response = client.Execute(request);
|
|
||||||
|
|
||||||
_logger.Trace("Sections response: {0}", response.Content);
|
|
||||||
CheckForError(response, settings);
|
|
||||||
|
|
||||||
List<PlexSectionItem> items;
|
|
||||||
|
|
||||||
if (response.Content.Contains("_children"))
|
|
||||||
{
|
|
||||||
items = Json.Deserialize<PlexSectionResponseLegacy>(response.Content)
|
|
||||||
.Items;
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
items = Json.Deserialize<PlexResponse<PlexSectionResponse>>(response.Content)
|
|
||||||
.MediaContainer
|
|
||||||
.Items;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (items == null || items.Empty())
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return items.First().Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string Authenticate(PlexServerSettings settings)
|
|
||||||
{
|
|
||||||
var request = GetPlexTvRequest("users/sign_in.json", Method.POST);
|
|
||||||
var client = GetPlexTvClient(settings.Username, settings.Password);
|
|
||||||
|
|
||||||
var response = client.Execute(request);
|
|
||||||
|
|
||||||
_logger.Debug("Authentication Response: {0}", response.Content);
|
|
||||||
CheckForError(response, settings);
|
|
||||||
|
|
||||||
var user = Json.Deserialize<PlexUser>(JObject.Parse(response.Content).SelectToken("user").ToString());
|
|
||||||
|
|
||||||
return user.AuthenticationToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RestClient GetPlexTvClient(string username, string password)
|
|
||||||
{
|
|
||||||
var client = RestClientFactory.BuildClient("https://plex.tv");
|
|
||||||
client.Authenticator = new HttpBasicAuthenticator(username, password);
|
|
||||||
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RestRequest GetPlexTvRequest(string resource, Method method)
|
|
||||||
{
|
|
||||||
var request = new RestRequest(resource, method);
|
|
||||||
request.AddHeader("X-Plex-Platform", "Windows");
|
|
||||||
request.AddHeader("X-Plex-Platform-Version", "7");
|
|
||||||
request.AddHeader("X-Plex-Provides", "player");
|
|
||||||
request.AddHeader("X-Plex-Client-Identifier", "AB6CCCC7-5CF5-4523-826A-B969E0FFD8A0");
|
|
||||||
request.AddHeader("X-Plex-Device-Name", "Sonarr");
|
|
||||||
request.AddHeader("X-Plex-Product", "Sonarr");
|
|
||||||
request.AddHeader("X-Plex-Version", BuildInfo.Version.ToString());
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
private RestClient GetPlexServerClient(PlexServerSettings settings)
|
|
||||||
{
|
|
||||||
var protocol = settings.UseSsl ? "https" : "http";
|
|
||||||
|
|
||||||
return RestClientFactory.BuildClient(string.Format("{0}://{1}:{2}", protocol, settings.Host, settings.Port));
|
|
||||||
}
|
|
||||||
|
|
||||||
private RestRequest GetPlexServerRequest(string resource, Method method, PlexServerSettings settings)
|
|
||||||
{
|
|
||||||
var request = new RestRequest(resource, method);
|
|
||||||
request.AddHeader("Accept", "application/json");
|
|
||||||
|
|
||||||
if (settings.Username.IsNotNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
request.AddParameter("X-Plex-Token", GetAuthenticationToken(settings), ParameterType.HttpHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetAuthenticationToken(PlexServerSettings settings)
|
|
||||||
{
|
|
||||||
var token = _authCache.Get(settings.Username + settings.Password, () => Authenticate(settings));
|
|
||||||
|
|
||||||
if (token.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
throw new PlexAuthenticationException("Invalid Token - Update your username and password");
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckForError(IRestResponse response, PlexServerSettings settings)
|
|
||||||
{
|
|
||||||
_logger.Trace("Checking for error");
|
|
||||||
|
|
||||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
|
||||||
{
|
|
||||||
if (settings.Username.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
throw new PlexAuthenticationException("Unauthorized - Username and password required");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Set the token to null in the cache so we don't keep trying with bad credentials
|
|
||||||
_authCache.Set(settings.Username + settings.Password, null);
|
|
||||||
throw new PlexAuthenticationException("Unauthorized - Username or password is incorrect");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (response.Content.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
_logger.Trace("No response body returned, no error detected");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var error = response.Content.Contains("_children") ?
|
|
||||||
Json.Deserialize<PlexError>(response.Content) :
|
|
||||||
Json.Deserialize<PlexResponse<PlexError>>(response.Content).MediaContainer;
|
|
||||||
|
|
||||||
if (error != null && !error.Error.IsNullOrWhiteSpace())
|
|
||||||
{
|
|
||||||
throw new PlexException(error.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.Trace("No error detected");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||||
|
{
|
||||||
|
public class PlexTvPinResponse
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Code { get; set; }
|
||||||
|
public string AuthToken { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||||
|
{
|
||||||
|
public class PlexTvPinUrlResponse
|
||||||
|
{
|
||||||
|
public string Url { get; set; }
|
||||||
|
public string Method => "POST";
|
||||||
|
public Dictionary<string, string> Headers { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
using System.Net;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||||
|
{
|
||||||
|
public interface IPlexTvProxy
|
||||||
|
{
|
||||||
|
string GetAuthToken(string clientIdentifier, int pinId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlexTvProxy : IPlexTvProxy
|
||||||
|
{
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public PlexTvProxy(IHttpClient httpClient, Logger logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAuthToken(string clientIdentifier, int pinId)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(clientIdentifier);
|
||||||
|
request.ResourceUrl = $"/api/v2/pins/{pinId}";
|
||||||
|
|
||||||
|
PlexTvPinResponse response;
|
||||||
|
|
||||||
|
if (!Json.TryDeserialize<PlexTvPinResponse>(ProcessRequest(request), out response))
|
||||||
|
{
|
||||||
|
response = new PlexTvPinResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.AuthToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequestBuilder BuildRequest(string clientIdentifier)
|
||||||
|
{
|
||||||
|
var requestBuilder = new HttpRequestBuilder("https://plex.tv")
|
||||||
|
.Accept(HttpAccept.Json)
|
||||||
|
.AddQueryParam("X-Plex-Client-Identifier", clientIdentifier)
|
||||||
|
.AddQueryParam("X-Plex-Product", "Sonarr")
|
||||||
|
.AddQueryParam("X-Plex-Platform", "Windows")
|
||||||
|
.AddQueryParam("X-Plex-Platform-Version", "7")
|
||||||
|
.AddQueryParam("X-Plex-Device-Name", "Sonarr")
|
||||||
|
.AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString());
|
||||||
|
|
||||||
|
return requestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ProcessRequest(HttpRequestBuilder requestBuilder)
|
||||||
|
{
|
||||||
|
var httpRequest = requestBuilder.Build();
|
||||||
|
|
||||||
|
HttpResponse response;
|
||||||
|
|
||||||
|
_logger.Debug("Url: {0}", httpRequest.Url);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response = _httpClient.Execute(httpRequest);
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
throw new NzbDroneClientException(ex.Response.StatusCode, "Unable to connect to plex.tv");
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
throw new NzbDroneClientException(HttpStatusCode.BadRequest, "Unable to connect to plex.tv");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||||
|
{
|
||||||
|
public interface IPlexTvService
|
||||||
|
{
|
||||||
|
PlexTvPinUrlResponse GetPinUrl();
|
||||||
|
PlexTvSignInUrlResponse GetSignInUrl(string callbackUrl, int pinId, string pinCode);
|
||||||
|
string GetAuthToken(int pinId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlexTvService : IPlexTvService
|
||||||
|
{
|
||||||
|
private readonly IPlexTvProxy _proxy;
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
|
||||||
|
public PlexTvService(IPlexTvProxy proxy, IConfigService configService)
|
||||||
|
{
|
||||||
|
_proxy = proxy;
|
||||||
|
_configService = configService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlexTvPinUrlResponse GetPinUrl()
|
||||||
|
{
|
||||||
|
var clientIdentifier = _configService.PlexClientIdentifier;
|
||||||
|
|
||||||
|
var requestBuilder = new HttpRequestBuilder("https://plex.tv/api/v2/pins")
|
||||||
|
.Accept(HttpAccept.Json)
|
||||||
|
.AddQueryParam("X-Plex-Client-Identifier", clientIdentifier)
|
||||||
|
.AddQueryParam("X-Plex-Product", "Sonarr")
|
||||||
|
.AddQueryParam("X-Plex-Platform", "Windows")
|
||||||
|
.AddQueryParam("X-Plex-Platform-Version", "7")
|
||||||
|
.AddQueryParam("X-Plex-Device-Name", "Sonarr")
|
||||||
|
.AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString())
|
||||||
|
.AddQueryParam("strong", true);
|
||||||
|
|
||||||
|
requestBuilder.Method = HttpMethod.POST;
|
||||||
|
|
||||||
|
var request = requestBuilder.Build();
|
||||||
|
|
||||||
|
return new PlexTvPinUrlResponse
|
||||||
|
{
|
||||||
|
Url = request.Url.ToString(),
|
||||||
|
Headers = request.Headers.ToDictionary(h => h.Key, h => h.Value)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlexTvSignInUrlResponse GetSignInUrl(string callbackUrl, int pinId, string pinCode)
|
||||||
|
{
|
||||||
|
var clientIdentifier = _configService.PlexClientIdentifier;
|
||||||
|
|
||||||
|
var requestBuilder = new HttpRequestBuilder("https://app.plex.tv/auth/hashBang")
|
||||||
|
.AddQueryParam("clientID", clientIdentifier)
|
||||||
|
.AddQueryParam("forwardUrl", callbackUrl)
|
||||||
|
.AddQueryParam("code", pinCode)
|
||||||
|
.AddQueryParam("context[device][product]", "Sonarr")
|
||||||
|
.AddQueryParam("context[device][platform]", "Windows")
|
||||||
|
.AddQueryParam("context[device][platformVersion]", "7")
|
||||||
|
.AddQueryParam("context[device][version]", BuildInfo.Version.ToString());
|
||||||
|
|
||||||
|
// #! is stripped out of the URL when building, this works around it.
|
||||||
|
requestBuilder.Segments.Add("hashBang", "#!");
|
||||||
|
|
||||||
|
var request = requestBuilder.Build();
|
||||||
|
|
||||||
|
return new PlexTvSignInUrlResponse
|
||||||
|
{
|
||||||
|
OauthUrl = request.Url.ToString(),
|
||||||
|
PinId = pinId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAuthToken(int pinId)
|
||||||
|
{
|
||||||
|
var authToken = _proxy.GetAuthToken(_configService.PlexClientIdentifier, pinId);
|
||||||
|
|
||||||
|
return authToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||||
|
{
|
||||||
|
public class PlexTvSignInUrlResponse
|
||||||
|
{
|
||||||
|
public string OauthUrl { get; set; }
|
||||||
|
public int PinId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex
|
|
||||||
{
|
|
||||||
public class PlexUser
|
|
||||||
{
|
|
||||||
[JsonProperty("authentication_token")]
|
|
||||||
public string AuthenticationToken { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
namespace NzbDrone.Core.Notifications.Plex
|
namespace NzbDrone.Core.Notifications.Plex.Server
|
||||||
{
|
{
|
||||||
public class PlexError
|
public class PlexError
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace NzbDrone.Core.Notifications.Plex.Models
|
namespace NzbDrone.Core.Notifications.Plex.Server
|
||||||
{
|
{
|
||||||
public class PlexIdentity
|
public class PlexIdentity
|
||||||
{
|
{
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex.Models
|
namespace NzbDrone.Core.Notifications.Plex.Server
|
||||||
{
|
{
|
||||||
public class PlexPreferences
|
public class PlexPreferences
|
||||||
{
|
{
|
|
@ -1,4 +1,4 @@
|
||||||
namespace NzbDrone.Core.Notifications.Plex.Models
|
namespace NzbDrone.Core.Notifications.Plex.Server
|
||||||
{
|
{
|
||||||
public class PlexResponse<T>
|
public class PlexResponse<T>
|
||||||
{
|
{
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex.Models
|
namespace NzbDrone.Core.Notifications.Plex.Server
|
||||||
{
|
{
|
||||||
public class PlexSectionLocation
|
public class PlexSectionLocation
|
||||||
{
|
{
|
|
@ -1,7 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex.Models
|
namespace NzbDrone.Core.Notifications.Plex.Server
|
||||||
{
|
{
|
||||||
public class PlexSectionItem
|
public class PlexSectionItem
|
||||||
{
|
{
|
|
@ -0,0 +1,102 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Exceptions;
|
||||||
|
using NzbDrone.Core.Notifications.Plex.PlexTv;
|
||||||
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Plex.Server
|
||||||
|
{
|
||||||
|
public class PlexServer : NotificationBase<PlexServerSettings>
|
||||||
|
{
|
||||||
|
private readonly IPlexServerService _plexServerService;
|
||||||
|
private readonly IPlexTvService _plexTvService;
|
||||||
|
|
||||||
|
public PlexServer(IPlexServerService plexServerService, IPlexTvService plexTvService)
|
||||||
|
{
|
||||||
|
_plexServerService = plexServerService;
|
||||||
|
_plexTvService = plexTvService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Link => "https://www.plex.tv/";
|
||||||
|
public override string Name => "Plex Media Server";
|
||||||
|
|
||||||
|
public override void OnDownload(DownloadMessage message)
|
||||||
|
{
|
||||||
|
UpdateIfEnabled(message.Series);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnRename(Series series)
|
||||||
|
{
|
||||||
|
UpdateIfEnabled(series);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateIfEnabled(Series series)
|
||||||
|
{
|
||||||
|
if (Settings.UpdateLibrary)
|
||||||
|
{
|
||||||
|
_plexServerService.UpdateLibrary(series, Settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValidationResult Test()
|
||||||
|
{
|
||||||
|
var failures = new List<ValidationFailure>();
|
||||||
|
|
||||||
|
failures.AddIfNotNull(_plexServerService.Test(Settings));
|
||||||
|
|
||||||
|
return new ValidationResult(failures);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object RequestAction(string action, IDictionary<string, string> query)
|
||||||
|
{
|
||||||
|
if (action == "startOAuth")
|
||||||
|
{
|
||||||
|
Settings.Validate().Filter("ConsumerKey", "ConsumerSecret").ThrowOnError();
|
||||||
|
|
||||||
|
return _plexTvService.GetPinUrl();
|
||||||
|
}
|
||||||
|
else if (action == "continueOAuth")
|
||||||
|
{
|
||||||
|
Settings.Validate().Filter("ConsumerKey", "ConsumerSecret").ThrowOnError();
|
||||||
|
|
||||||
|
if (query["callbackUrl"].IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new BadRequestException("QueryParam callbackUrl invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query["id"].IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new BadRequestException("QueryParam id invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query["code"].IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new BadRequestException("QueryParam code invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _plexTvService.GetSignInUrl(query["callbackUrl"], Convert.ToInt32(query["id"]), query["code"]);
|
||||||
|
}
|
||||||
|
else if (action == "getOAuthToken")
|
||||||
|
{
|
||||||
|
Settings.Validate().Filter("ConsumerKey", "ConsumerSecret").ThrowOnError();
|
||||||
|
|
||||||
|
if (query["pinId"].IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new BadRequestException("QueryParam pinId invalid.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var authToken = _plexTvService.GetAuthToken(Convert.ToInt32(query["pinId"]));
|
||||||
|
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
authToken
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new { };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.EnvironmentInfo;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
|
using NzbDrone.Core.Configuration;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Plex.Server
|
||||||
|
{
|
||||||
|
public interface IPlexServerProxy
|
||||||
|
{
|
||||||
|
List<PlexSection> GetTvSections(PlexServerSettings settings);
|
||||||
|
void Update(int sectionId, PlexServerSettings settings);
|
||||||
|
void UpdateSeries(int metadataId, PlexServerSettings settings);
|
||||||
|
string Version(PlexServerSettings settings);
|
||||||
|
List<PlexPreference> Preferences(PlexServerSettings settings);
|
||||||
|
int? GetMetadataId(int sectionId, int tvdbId, string language, PlexServerSettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PlexServerProxy : IPlexServerProxy
|
||||||
|
{
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
private readonly IConfigService _configService;
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public PlexServerProxy(IHttpClient httpClient, IConfigService configService,Logger logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_configService = configService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PlexSection> GetTvSections(PlexServerSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest("library/sections", HttpMethod.GET, settings);
|
||||||
|
var response = ProcessRequest(request);
|
||||||
|
|
||||||
|
CheckForError(response);
|
||||||
|
|
||||||
|
if (response.Contains("_children"))
|
||||||
|
{
|
||||||
|
return Json.Deserialize<PlexMediaContainerLegacy>(response)
|
||||||
|
.Sections
|
||||||
|
.Where(d => d.Type == "show")
|
||||||
|
.Select(s => new PlexSection
|
||||||
|
{
|
||||||
|
Id = s.Id,
|
||||||
|
Language = s.Language,
|
||||||
|
Locations = s.Locations,
|
||||||
|
Type = s.Type
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json.Deserialize<PlexResponse<PlexSectionsContainer>>(response)
|
||||||
|
.MediaContainer
|
||||||
|
.Sections
|
||||||
|
.Where(d => d.Type == "show")
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(int sectionId, PlexServerSettings settings)
|
||||||
|
{
|
||||||
|
var resource = $"library/sections/{sectionId}/refresh";
|
||||||
|
var request = BuildRequest(resource, HttpMethod.GET, settings);
|
||||||
|
var response = ProcessRequest(request);
|
||||||
|
|
||||||
|
CheckForError(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSeries(int metadataId, PlexServerSettings settings)
|
||||||
|
{
|
||||||
|
var resource = $"library/metadata/{metadataId}/refresh";
|
||||||
|
var request = BuildRequest(resource, HttpMethod.PUT, settings);
|
||||||
|
var response = ProcessRequest(request);
|
||||||
|
|
||||||
|
CheckForError(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Version(PlexServerSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest("identity", HttpMethod.GET, settings);
|
||||||
|
var response = ProcessRequest(request);
|
||||||
|
|
||||||
|
CheckForError(response);
|
||||||
|
|
||||||
|
if (response.Contains("_children"))
|
||||||
|
{
|
||||||
|
return Json.Deserialize<PlexIdentity>(response)
|
||||||
|
.Version;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json.Deserialize<PlexResponse<PlexIdentity>>(response)
|
||||||
|
.MediaContainer
|
||||||
|
.Version;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PlexPreference> Preferences(PlexServerSettings settings)
|
||||||
|
{
|
||||||
|
var request = BuildRequest(":/prefs", HttpMethod.GET, settings);
|
||||||
|
var response = ProcessRequest(request);
|
||||||
|
|
||||||
|
CheckForError(response);
|
||||||
|
|
||||||
|
if (response.Contains("_children"))
|
||||||
|
{
|
||||||
|
return Json.Deserialize<PlexPreferencesLegacy>(response)
|
||||||
|
.Preferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Json.Deserialize<PlexResponse<PlexPreferences>>(response)
|
||||||
|
.MediaContainer
|
||||||
|
.Preferences;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int? GetMetadataId(int sectionId, int tvdbId, string language, PlexServerSettings settings)
|
||||||
|
{
|
||||||
|
var guid = $"com.plexapp.agents.thetvdb://{tvdbId}?lang={language}";
|
||||||
|
var resource = $"library/sections/{sectionId}/all?guid={System.Web.HttpUtility.UrlEncode(guid)}";
|
||||||
|
var request = BuildRequest(resource, HttpMethod.GET, settings);
|
||||||
|
var response = ProcessRequest(request);
|
||||||
|
|
||||||
|
CheckForError(response);
|
||||||
|
|
||||||
|
List<PlexSectionItem> items;
|
||||||
|
|
||||||
|
if (response.Contains("_children"))
|
||||||
|
{
|
||||||
|
items = Json.Deserialize<PlexSectionResponseLegacy>(response)
|
||||||
|
.Items;
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
items = Json.Deserialize<PlexResponse<PlexSectionResponse>>(response)
|
||||||
|
.MediaContainer
|
||||||
|
.Items;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items == null || items.Empty())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return items.First().Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequestBuilder BuildRequest(string resource, HttpMethod method, PlexServerSettings settings)
|
||||||
|
{
|
||||||
|
var scheme = settings.UseSsl ? "https" : "http";
|
||||||
|
|
||||||
|
var requestBuilder = new HttpRequestBuilder($"{scheme}://{settings.Host}:{settings.Port}")
|
||||||
|
.Accept(HttpAccept.Json)
|
||||||
|
.AddQueryParam("X-Plex-Client-Identifier", _configService.PlexClientIdentifier)
|
||||||
|
.AddQueryParam("X-Plex-Product", "Sonarr")
|
||||||
|
.AddQueryParam("X-Plex-Platform", "Windows")
|
||||||
|
.AddQueryParam("X-Plex-Platform-Version", "7")
|
||||||
|
.AddQueryParam("X-Plex-Device-Name", "Sonarr")
|
||||||
|
.AddQueryParam("X-Plex-Version", BuildInfo.Version.ToString());
|
||||||
|
|
||||||
|
if (settings.AuthToken.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
requestBuilder.AddQueryParam("X-Plex-Token", settings.AuthToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestBuilder.ResourceUrl = resource;
|
||||||
|
requestBuilder.Method = method;
|
||||||
|
|
||||||
|
return requestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ProcessRequest(HttpRequestBuilder requestBuilder)
|
||||||
|
{
|
||||||
|
var httpRequest = requestBuilder.Build();
|
||||||
|
|
||||||
|
HttpResponse response;
|
||||||
|
|
||||||
|
_logger.Debug("Url: {0}", httpRequest.Url);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response = _httpClient.Execute(httpRequest);
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
throw new PlexAuthenticationException("Unauthorized - AuthToken is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PlexException("Unable to connect to Plex Media Server");
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
throw new PlexException("Unable to connect to Plex Media Server");
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckForError(string response)
|
||||||
|
{
|
||||||
|
_logger.Trace("Checking for error");
|
||||||
|
|
||||||
|
if (response.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
_logger.Trace("No response body returned, no error detected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = response.Contains("_children") ?
|
||||||
|
Json.Deserialize<PlexError>(response) :
|
||||||
|
Json.Deserialize<PlexResponse<PlexError>>(response).MediaContainer;
|
||||||
|
|
||||||
|
if (error != null && !error.Error.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new PlexException(error.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Trace("No error detected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
@ -6,10 +6,9 @@ using FluentValidation.Results;
|
||||||
using NLog;
|
using NLog;
|
||||||
using NzbDrone.Common.Cache;
|
using NzbDrone.Common.Cache;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using NzbDrone.Core.Notifications.Plex.Models;
|
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex
|
namespace NzbDrone.Core.Notifications.Plex.Server
|
||||||
{
|
{
|
||||||
public interface IPlexServerService
|
public interface IPlexServerService
|
||||||
{
|
{
|
||||||
|
@ -109,13 +108,9 @@ namespace NzbDrone.Core.Notifications.Plex
|
||||||
var rawVersion = _plexServerProxy.Version(settings);
|
var rawVersion = _plexServerProxy.Version(settings);
|
||||||
var version = new Version(Regex.Match(rawVersion, @"^(\d+[.-]){4}").Value.Trim('.', '-'));
|
var version = new Version(Regex.Match(rawVersion, @"^(\d+[.-]){4}").Value.Trim('.', '-'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private List<PlexPreference> GetPreferences(PlexServerSettings settings)
|
private List<PlexPreference> GetPreferences(PlexServerSettings settings)
|
||||||
{
|
{
|
||||||
_logger.Debug("Getting preferences from Plex host: {0}", settings.Host);
|
_logger.Debug("Getting preferences from Plex host: {0}", settings.Host);
|
||||||
|
@ -176,7 +171,7 @@ namespace NzbDrone.Core.Notifications.Plex
|
||||||
catch(PlexAuthenticationException ex)
|
catch(PlexAuthenticationException ex)
|
||||||
{
|
{
|
||||||
_logger.Error(ex, "Unable to connect to Plex Server");
|
_logger.Error(ex, "Unable to connect to Plex Server");
|
||||||
return new ValidationFailure("Username", "Incorrect username or password");
|
return new ValidationFailure("AuthToken", "Invalid authentication token");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
|
@ -1,9 +1,9 @@
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using NzbDrone.Core.Annotations;
|
using NzbDrone.Core.Annotations;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Plex
|
namespace NzbDrone.Core.Notifications.Plex.Server
|
||||||
{
|
{
|
||||||
public class PlexServerSettingsValidator : AbstractValidator<PlexServerSettings>
|
public class PlexServerSettingsValidator : AbstractValidator<PlexServerSettings>
|
||||||
{
|
{
|
||||||
|
@ -22,6 +22,7 @@ namespace NzbDrone.Core.Notifications.Plex
|
||||||
{
|
{
|
||||||
Port = 32400;
|
Port = 32400;
|
||||||
UpdateLibrary = true;
|
UpdateLibrary = true;
|
||||||
|
SignIn = "startOAuth";
|
||||||
}
|
}
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Host")]
|
[FieldDefinition(0, Label = "Host")]
|
||||||
|
@ -30,12 +31,11 @@ namespace NzbDrone.Core.Notifications.Plex
|
||||||
[FieldDefinition(1, Label = "Port")]
|
[FieldDefinition(1, Label = "Port")]
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
//TODO: Change username and password to token and get a plex.tv OAuth token properly
|
[FieldDefinition(2, Label = "Auth Token", Type = FieldType.Textbox, Advanced = true)]
|
||||||
[FieldDefinition(2, Label = "Username")]
|
public string AuthToken { get; set; }
|
||||||
public string Username { get; set; }
|
|
||||||
|
|
||||||
[FieldDefinition(3, Label = "Password", Type = FieldType.Password)]
|
[FieldDefinition(3, Label = "Authenticate with Plex.tv", Type = FieldType.OAuth)]
|
||||||
public string Password { get; set; }
|
public string SignIn { get; set; }
|
||||||
|
|
||||||
[FieldDefinition(4, Label = "Update Library", Type = FieldType.Checkbox)]
|
[FieldDefinition(4, Label = "Update Library", Type = FieldType.Checkbox)]
|
||||||
public bool UpdateLibrary { get; set; }
|
public bool UpdateLibrary { get; set; }
|
|
@ -771,6 +771,11 @@
|
||||||
<Compile Include="Languages\LanguageComparer.cs" />
|
<Compile Include="Languages\LanguageComparer.cs" />
|
||||||
<Compile Include="Languages\LanguagesBelowCutoff.cs" />
|
<Compile Include="Languages\LanguagesBelowCutoff.cs" />
|
||||||
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateLanguage.cs" />
|
<Compile Include="MediaFiles\EpisodeImport\Aggregation\Aggregators\AggregateLanguage.cs" />
|
||||||
|
<Compile Include="Notifications\Plex\PlexTv\PlexTvPinUrlResponse.cs" />
|
||||||
|
<Compile Include="Notifications\Plex\PlexTv\PlexTvSignInUrlResponse.cs" />
|
||||||
|
<Compile Include="Notifications\Plex\PlexTv\PlexTvPinResponse.cs" />
|
||||||
|
<Compile Include="Notifications\Plex\PlexTv\PlexTvProxy.cs" />
|
||||||
|
<Compile Include="Notifications\Plex\PlexTv\PlexTvService.cs" />
|
||||||
<Compile Include="Profiles\Languages\LanguageProfile.cs" />
|
<Compile Include="Profiles\Languages\LanguageProfile.cs" />
|
||||||
<Compile Include="Profiles\Languages\LanguageProfileInUseException.cs" />
|
<Compile Include="Profiles\Languages\LanguageProfileInUseException.cs" />
|
||||||
<Compile Include="Lifecycle\ApplicationShutdownRequested.cs" />
|
<Compile Include="Lifecycle\ApplicationShutdownRequested.cs" />
|
||||||
|
@ -936,18 +941,18 @@
|
||||||
<Compile Include="Notifications\Boxcar\BoxcarProxy.cs" />
|
<Compile Include="Notifications\Boxcar\BoxcarProxy.cs" />
|
||||||
<Compile Include="Notifications\Boxcar\BoxcarSettings.cs" />
|
<Compile Include="Notifications\Boxcar\BoxcarSettings.cs" />
|
||||||
<Compile Include="Notifications\GrabMessage.cs" />
|
<Compile Include="Notifications\GrabMessage.cs" />
|
||||||
<Compile Include="Notifications\Plex\Models\PlexIdentity.cs" />
|
<Compile Include="Notifications\Plex\Server\PlexIdentity.cs" />
|
||||||
<Compile Include="Notifications\Plex\Models\PlexResponse.cs" />
|
<Compile Include="Notifications\Plex\Server\PlexResponse.cs" />
|
||||||
<Compile Include="Notifications\Plex\Models\PlexPreferences.cs" />
|
<Compile Include="Notifications\Plex\Server\PlexPreferences.cs" />
|
||||||
<Compile Include="Notifications\Plex\Models\PlexSectionItem.cs" />
|
<Compile Include="Notifications\Plex\Server\PlexSectionItem.cs" />
|
||||||
<Compile Include="Notifications\Plex\Models\PlexSection.cs" />
|
<Compile Include="Notifications\Plex\Server\PlexSection.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexAuthenticationException.cs" />
|
<Compile Include="Notifications\Plex\PlexAuthenticationException.cs" />
|
||||||
<Compile Include="Notifications\CustomScript\CustomScript.cs" />
|
<Compile Include="Notifications\CustomScript\CustomScript.cs" />
|
||||||
<Compile Include="Notifications\CustomScript\CustomScriptSettings.cs" />
|
<Compile Include="Notifications\CustomScript\CustomScriptSettings.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexVersionException.cs" />
|
<Compile Include="Notifications\Plex\PlexVersionException.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexHomeTheater.cs" />
|
<Compile Include="Notifications\Plex\HomeTheater\PlexHomeTheater.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexHomeTheaterSettings.cs" />
|
<Compile Include="Notifications\Plex\HomeTheater\PlexHomeTheaterSettings.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexClientService.cs" />
|
<Compile Include="Notifications\Plex\HomeTheater\PlexClientService.cs" />
|
||||||
<Compile Include="Notifications\PushBullet\PushBulletException.cs" />
|
<Compile Include="Notifications\PushBullet\PushBulletException.cs" />
|
||||||
<Compile Include="Notifications\Slack\Payloads\Attachment.cs" />
|
<Compile Include="Notifications\Slack\Payloads\Attachment.cs" />
|
||||||
<Compile Include="Notifications\Slack\Payloads\SlackPayload.cs" />
|
<Compile Include="Notifications\Slack\Payloads\SlackPayload.cs" />
|
||||||
|
@ -1038,17 +1043,16 @@
|
||||||
<Compile Include="Notifications\NotifyMyAndroid\NotifyMyAndroidPriority.cs" />
|
<Compile Include="Notifications\NotifyMyAndroid\NotifyMyAndroidPriority.cs" />
|
||||||
<Compile Include="Notifications\NotifyMyAndroid\NotifyMyAndroidProxy.cs" />
|
<Compile Include="Notifications\NotifyMyAndroid\NotifyMyAndroidProxy.cs" />
|
||||||
<Compile Include="Notifications\NotifyMyAndroid\NotifyMyAndroidSettings.cs" />
|
<Compile Include="Notifications\NotifyMyAndroid\NotifyMyAndroidSettings.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexClient.cs">
|
<Compile Include="Notifications\Plex\HomeTheater\PlexClient.cs">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Notifications\Plex\PlexClientSettings.cs" />
|
<Compile Include="Notifications\Plex\HomeTheater\PlexClientSettings.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexError.cs" />
|
<Compile Include="Notifications\Plex\Server\PlexError.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexException.cs" />
|
<Compile Include="Notifications\Plex\PlexException.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexServer.cs" />
|
<Compile Include="Notifications\Plex\Server\PlexServer.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexServerProxy.cs" />
|
<Compile Include="Notifications\Plex\Server\PlexServerProxy.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexServerSettings.cs" />
|
<Compile Include="Notifications\Plex\Server\PlexServerSettings.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexServerService.cs" />
|
<Compile Include="Notifications\Plex\Server\PlexServerService.cs" />
|
||||||
<Compile Include="Notifications\Plex\PlexUser.cs" />
|
|
||||||
<Compile Include="Notifications\Prowl\InvalidApiKeyException.cs" />
|
<Compile Include="Notifications\Prowl\InvalidApiKeyException.cs" />
|
||||||
<Compile Include="Notifications\Prowl\Prowl.cs">
|
<Compile Include="Notifications\Prowl\Prowl.cs">
|
||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
|
||||||
|
@ -16,8 +16,8 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||||
var schema = DownloadClients.Schema().First(v => v.Implementation == "UsenetBlackhole");
|
var schema = DownloadClients.Schema().First(v => v.Implementation == "UsenetBlackhole");
|
||||||
|
|
||||||
schema.Enable = true;
|
schema.Enable = true;
|
||||||
schema.Fields.First(v => v.Name == "WatchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
|
schema.Fields.First(v => v.Name == "watchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
|
||||||
schema.Fields.First(v => v.Name == "NzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
|
schema.Fields.First(v => v.Name == "nzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
|
||||||
|
|
||||||
DownloadClients.InvalidPost(schema);
|
DownloadClients.InvalidPost(schema);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||||
|
|
||||||
schema.Enable = true;
|
schema.Enable = true;
|
||||||
schema.Name = "Test UsenetBlackhole";
|
schema.Name = "Test UsenetBlackhole";
|
||||||
schema.Fields.First(v => v.Name == "WatchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
|
schema.Fields.First(v => v.Name == "watchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
|
||||||
|
|
||||||
DownloadClients.InvalidPost(schema);
|
DownloadClients.InvalidPost(schema);
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||||
|
|
||||||
schema.Enable = true;
|
schema.Enable = true;
|
||||||
schema.Name = "Test UsenetBlackhole";
|
schema.Name = "Test UsenetBlackhole";
|
||||||
schema.Fields.First(v => v.Name == "NzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
|
schema.Fields.First(v => v.Name == "nzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
|
||||||
|
|
||||||
DownloadClients.InvalidPost(schema);
|
DownloadClients.InvalidPost(schema);
|
||||||
}
|
}
|
||||||
|
@ -59,8 +59,8 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||||
|
|
||||||
schema.Enable = true;
|
schema.Enable = true;
|
||||||
schema.Name = "Test UsenetBlackhole";
|
schema.Name = "Test UsenetBlackhole";
|
||||||
schema.Fields.First(v => v.Name == "WatchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
|
schema.Fields.First(v => v.Name == "watchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
|
||||||
schema.Fields.First(v => v.Name == "NzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
|
schema.Fields.First(v => v.Name == "nzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
|
||||||
|
|
||||||
var result = DownloadClients.Post(schema);
|
var result = DownloadClients.Post(schema);
|
||||||
|
|
||||||
|
@ -99,7 +99,7 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||||
EnsureNoDownloadClient();
|
EnsureNoDownloadClient();
|
||||||
var client = EnsureDownloadClient();
|
var client = EnsureDownloadClient();
|
||||||
|
|
||||||
client.Fields.First(v => v.Name == "NzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb2");
|
client.Fields.First(v => v.Name == "nzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb2");
|
||||||
var result = DownloadClients.Put(client);
|
var result = DownloadClients.Put(client);
|
||||||
|
|
||||||
result.Should().NotBeNull();
|
result.Should().NotBeNull();
|
||||||
|
@ -117,4 +117,4 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||||
DownloadClients.All().Should().NotContain(v => v.Id == client.Id);
|
DownloadClients.All().Should().NotContain(v => v.Id == client.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
@ -34,10 +34,10 @@ namespace NzbDrone.Integration.Test.ApiTests
|
||||||
var xbmc = schema.Single(s => s.Implementation.Equals("Xbmc", StringComparison.InvariantCultureIgnoreCase));
|
var xbmc = schema.Single(s => s.Implementation.Equals("Xbmc", StringComparison.InvariantCultureIgnoreCase));
|
||||||
|
|
||||||
xbmc.Name = "Test XBMC";
|
xbmc.Name = "Test XBMC";
|
||||||
xbmc.Fields.Single(f => f.Name.Equals("Host")).Value = "localhost";
|
xbmc.Fields.Single(f => f.Name.Equals("host")).Value = "localhost";
|
||||||
|
|
||||||
var result = Notifications.Post(xbmc);
|
var result = Notifications.Post(xbmc);
|
||||||
Notifications.Delete(result.Id);
|
Notifications.Delete(result.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,8 +329,8 @@ namespace NzbDrone.Integration.Test
|
||||||
|
|
||||||
schema.Enable = enabled;
|
schema.Enable = enabled;
|
||||||
schema.Name = "Test UsenetBlackhole";
|
schema.Name = "Test UsenetBlackhole";
|
||||||
schema.Fields.First(v => v.Name == "WatchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
|
schema.Fields.First(v => v.Name == "watchFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Watch");
|
||||||
schema.Fields.First(v => v.Name == "NzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
|
schema.Fields.First(v => v.Name == "nzbFolder").Value = GetTempDirectory("Download", "UsenetBlackhole", "Nzb");
|
||||||
|
|
||||||
client = DownloadClients.Post(schema);
|
client = DownloadClients.Post(schema);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using FluentValidation.Results;
|
using FluentValidation.Results;
|
||||||
using Nancy;
|
using Nancy;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using NzbDrone.Common.Serializer;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
using Sonarr.Http;
|
using Sonarr.Http;
|
||||||
|
@ -172,7 +173,7 @@ namespace Sonarr.Api.V3
|
||||||
var query = ((IDictionary<string, object>)Request.Query.ToDictionary()).ToDictionary(k => k.Key, k => k.Value.ToString());
|
var query = ((IDictionary<string, object>)Request.Query.ToDictionary()).ToDictionary(k => k.Key, k => k.Value.ToString());
|
||||||
|
|
||||||
var data = _providerFactory.RequestAction(providerDefinition, action, query);
|
var data = _providerFactory.RequestAction(providerDefinition, action, query);
|
||||||
Response resp = JsonConvert.SerializeObject(data);
|
Response resp = data.ToJson();
|
||||||
resp.ContentType = "application/json";
|
resp.ContentType = "application/json";
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
@ -93,7 +93,7 @@ namespace Sonarr.Http.ClientSchema
|
||||||
var fieldAttribute = property.Item2;
|
var fieldAttribute = property.Item2;
|
||||||
var field = new Field
|
var field = new Field
|
||||||
{
|
{
|
||||||
Name = prefix + propertyInfo.Name,
|
Name = prefix + GetCamelCaseName(propertyInfo.Name),
|
||||||
Label = fieldAttribute.Label,
|
Label = fieldAttribute.Label,
|
||||||
Unit = fieldAttribute.Unit,
|
Unit = fieldAttribute.Unit,
|
||||||
HelpText = fieldAttribute.HelpText,
|
HelpText = fieldAttribute.HelpText,
|
||||||
|
@ -121,7 +121,7 @@ namespace Sonarr.Http.ClientSchema
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
result.AddRange(GetFieldMapping(propertyInfo.PropertyType, propertyInfo.Name + ".", t => propertyInfo.GetValue(targetSelector(t), null)));
|
result.AddRange(GetFieldMapping(propertyInfo.PropertyType, GetCamelCaseName(propertyInfo.Name) + ".", t => propertyInfo.GetValue(targetSelector(t), null)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,5 +212,10 @@ namespace Sonarr.Http.ClientSchema
|
||||||
return fieldValue => fieldValue;
|
return fieldValue => fieldValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string GetCamelCaseName(string name)
|
||||||
|
{
|
||||||
|
return Char.ToLowerInvariant(name[0]) + name.Substring(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue