mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-17 21:26:22 -04:00
New: Ability to select Plex Media Server from plex.tv
(cherry picked from commit 4c622fd41289cd293a68a6a9f6b8da2a086edecb) Closes #10110
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace NzbDrone.Core.Annotations
|
||||
@@ -58,13 +59,27 @@ namespace NzbDrone.Core.Annotations
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public class FieldSelectOption
|
||||
public class FieldSelectOption<T>
|
||||
where T : struct
|
||||
{
|
||||
public int Value { get; set; }
|
||||
public T Value { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string Hint { get; set; }
|
||||
public int? ParentValue { get; set; }
|
||||
public T? ParentValue { get; set; }
|
||||
public bool? IsDisabled { get; set; }
|
||||
public Dictionary<string, object> AdditionalProperties { get; set; }
|
||||
}
|
||||
|
||||
public class FieldSelectStringOption
|
||||
{
|
||||
public string Value { get; set; }
|
||||
public string Name { get; set; }
|
||||
public int Order { get; set; }
|
||||
public string Hint { get; set; }
|
||||
public string ParentValue { get; set; }
|
||||
public bool? IsDisabled { get; set; }
|
||||
public Dictionary<string, object> AdditionalProperties { get; set; }
|
||||
}
|
||||
|
||||
public enum FieldType
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
public static class NewznabCategoryFieldOptionsConverter
|
||||
{
|
||||
public static List<FieldSelectOption> GetFieldSelectOptions(List<NewznabCategory> categories)
|
||||
public static List<FieldSelectOption<int>> GetFieldSelectOptions(List<NewznabCategory> categories)
|
||||
{
|
||||
// Categories not relevant for Radarr
|
||||
var ignoreCategories = new[] { 1000, 3000, 4000, 6000, 7000 };
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
// And maybe relevant for specific users
|
||||
var unimportantCategories = new[] { 0, 5000 };
|
||||
|
||||
var result = new List<FieldSelectOption>();
|
||||
var result = new List<FieldSelectOption<int>>();
|
||||
|
||||
if (categories == null)
|
||||
{
|
||||
@@ -39,7 +39,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
|
||||
foreach (var category in categories.Where(cat => !ignoreCategories.Contains(cat.Id)).OrderBy(cat => unimportantCategories.Contains(cat.Id)).ThenBy(cat => cat.Id))
|
||||
{
|
||||
result.Add(new FieldSelectOption
|
||||
result.Add(new FieldSelectOption<int>
|
||||
{
|
||||
Value = category.Id,
|
||||
Name = category.Name,
|
||||
@@ -50,7 +50,7 @@ namespace NzbDrone.Core.Indexers.Newznab
|
||||
{
|
||||
foreach (var subcat in category.Subcategories.OrderBy(cat => cat.Id))
|
||||
{
|
||||
result.Add(new FieldSelectOption
|
||||
result.Add(new FieldSelectOption<int>
|
||||
{
|
||||
Value = subcat.Id,
|
||||
Name = subcat.Name,
|
||||
|
||||
@@ -1118,6 +1118,8 @@
|
||||
"NotificationsNtfyValidationAuthorizationRequired": "Authorization is required",
|
||||
"NotificationsPlexSettingsAuthToken": "Auth Token",
|
||||
"NotificationsPlexSettingsAuthenticateWithPlexTv": "Authenticate with Plex.tv",
|
||||
"NotificationsPlexSettingsServer": "Server",
|
||||
"NotificationsPlexSettingsServerHelpText": "Select server from plex.tv account after authenticating",
|
||||
"NotificationsPlexValidationNoMovieLibraryFound": "At least one Movie library is required",
|
||||
"NotificationsPushBulletSettingSenderId": "Sender ID",
|
||||
"NotificationsPushBulletSettingSenderIdHelpText": "The device ID to send notifications from, use device_iden in the device's URL on pushbullet.com (leave blank to send from yourself)",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using NLog;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
@@ -12,6 +13,7 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
{
|
||||
string GetAuthToken(string clientIdentifier, int pinId);
|
||||
bool Ping(string clientIdentifier, string authToken);
|
||||
List<PlexTvResource> GetResources(string clientIdentifier, string authToken);
|
||||
}
|
||||
|
||||
public class PlexTvProxy : IPlexTvProxy
|
||||
@@ -61,6 +63,33 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<PlexTvResource> GetResources(string clientIdentifier, string authToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Allows us to tell plex.tv that we're still active and tokens should not be expired.
|
||||
|
||||
var request = BuildRequest(clientIdentifier);
|
||||
|
||||
request.ResourceUrl = "/api/v2/resources";
|
||||
request.AddQueryParam("includeHttps", 1);
|
||||
request.AddQueryParam("clientID", clientIdentifier);
|
||||
request.AddQueryParam("X-Plex-Token", authToken);
|
||||
|
||||
if (Json.TryDeserialize<List<PlexTvResource>>(ProcessRequest(request), out var response))
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Catch all exceptions and log at trace, this information could be interesting in debugging, but expired tokens will be handled elsewhere.
|
||||
_logger.Trace(e, "Unable to ping plex.tv");
|
||||
}
|
||||
|
||||
return new List<PlexTvResource>();
|
||||
}
|
||||
|
||||
private HttpRequestBuilder BuildRequest(string clientIdentifier)
|
||||
{
|
||||
var requestBuilder = new HttpRequestBuilder("https://plex.tv")
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
{
|
||||
public class PlexTvResource
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public bool Owned { get; set; }
|
||||
|
||||
public List<PlexTvResourceConnection> Connections { get; set; }
|
||||
|
||||
[JsonProperty("provides")]
|
||||
public string ProvidesRaw { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public List<string> Provides => ProvidesRaw.Split(",").ToList();
|
||||
}
|
||||
|
||||
public class PlexTvResourceConnection
|
||||
{
|
||||
public string Uri { get; set; }
|
||||
public string Protocol { get; set; }
|
||||
public string Address { get; set; }
|
||||
public int Port { get; set; }
|
||||
public bool Local { get; set; }
|
||||
public string Host => Uri.IsNullOrWhiteSpace() ? Address : new Uri(Uri).Host;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using NzbDrone.Common.Cache;
|
||||
@@ -14,6 +15,7 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
PlexTvSignInUrlResponse GetSignInUrl(string callbackUrl, int pinId, string pinCode);
|
||||
string GetAuthToken(int pinId);
|
||||
void Ping(string authToken);
|
||||
List<PlexTvResource> GetServers(string authToken);
|
||||
HttpRequest GetWatchlist(string authToken, int pageSize, int pageOffset);
|
||||
}
|
||||
|
||||
@@ -93,6 +95,16 @@ namespace NzbDrone.Core.Notifications.Plex.PlexTv
|
||||
_cache.Get(authToken, () => _proxy.Ping(_configService.PlexClientIdentifier, authToken), TimeSpan.FromHours(24));
|
||||
}
|
||||
|
||||
public List<PlexTvResource> GetServers(string authToken)
|
||||
{
|
||||
Ping(authToken);
|
||||
|
||||
var clientIdentifier = _configService.PlexClientIdentifier;
|
||||
var resources = _proxy.GetResources(clientIdentifier, authToken);
|
||||
|
||||
return resources.Where(r => r.Owned && r.Provides.Contains("server")).ToList();
|
||||
}
|
||||
|
||||
public HttpRequest GetWatchlist(string authToken, int pageSize, int pageOffset)
|
||||
{
|
||||
Ping(authToken);
|
||||
|
||||
@@ -5,6 +5,7 @@ using FluentValidation.Results;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Movies;
|
||||
@@ -188,6 +189,79 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
};
|
||||
}
|
||||
|
||||
if (action == "servers")
|
||||
{
|
||||
Settings.Validate().Filter("AuthToken").ThrowOnError();
|
||||
|
||||
if (Settings.AuthToken.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new { };
|
||||
}
|
||||
|
||||
var servers = _plexTvService.GetServers(Settings.AuthToken);
|
||||
var options = servers.SelectMany(s =>
|
||||
{
|
||||
var result = new List<FieldSelectStringOption>();
|
||||
|
||||
// result.Add(new FieldSelectStringOption
|
||||
// {
|
||||
// Value = s.Name,
|
||||
// Name = s.Name,
|
||||
// IsDisabled = true
|
||||
// });
|
||||
|
||||
s.Connections.ForEach(c =>
|
||||
{
|
||||
var isSecure = c.Protocol == "https";
|
||||
var additionalProperties = new Dictionary<string, object>();
|
||||
var hints = new List<string>();
|
||||
|
||||
additionalProperties.Add("host", c.Host);
|
||||
additionalProperties.Add("port", c.Port);
|
||||
additionalProperties.Add("useSsl", isSecure);
|
||||
hints.Add(c.Local ? "Local" : "Remote");
|
||||
|
||||
if (isSecure)
|
||||
{
|
||||
hints.Add("Secure");
|
||||
}
|
||||
|
||||
result.Add(new FieldSelectStringOption
|
||||
{
|
||||
Value = c.Uri,
|
||||
Name = $"{s.Name} ({c.Host})",
|
||||
Hint = string.Join(", ", hints),
|
||||
AdditionalProperties = additionalProperties
|
||||
});
|
||||
|
||||
if (isSecure)
|
||||
{
|
||||
var uri = $"http://{c.Address}:{c.Port}";
|
||||
var insecureAdditionalProperties = new Dictionary<string, object>();
|
||||
|
||||
insecureAdditionalProperties.Add("host", c.Address);
|
||||
insecureAdditionalProperties.Add("port", c.Port);
|
||||
insecureAdditionalProperties.Add("useSsl", false);
|
||||
|
||||
result.Add(new FieldSelectStringOption
|
||||
{
|
||||
Value = uri,
|
||||
Name = $"{s.Name} ({c.Address})",
|
||||
Hint = c.Local ? "Local" : "Remote",
|
||||
AdditionalProperties = insecureAdditionalProperties
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
return new
|
||||
{
|
||||
options
|
||||
};
|
||||
}
|
||||
|
||||
return new { };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using FluentValidation;
|
||||
using Newtonsoft.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Annotations;
|
||||
using NzbDrone.Core.Validation;
|
||||
@@ -22,40 +23,45 @@ namespace NzbDrone.Core.Notifications.Plex.Server
|
||||
|
||||
public PlexServerSettings()
|
||||
{
|
||||
Host = "";
|
||||
Port = 32400;
|
||||
UpdateLibrary = true;
|
||||
SignIn = "startOAuth";
|
||||
}
|
||||
|
||||
[FieldDefinition(0, Label = "Host")]
|
||||
[JsonIgnore]
|
||||
[FieldDefinition(0, Label = "NotificationsPlexSettingsServer", Type = FieldType.Select, SelectOptionsProviderAction = "servers", HelpText = "NotificationsPlexSettingsServerHelpText")]
|
||||
public string Server { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Host")]
|
||||
public string Host { get; set; }
|
||||
|
||||
[FieldDefinition(1, Label = "Port")]
|
||||
[FieldDefinition(2, Label = "Port")]
|
||||
public int Port { get; set; }
|
||||
|
||||
[FieldDefinition(2, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "NotificationsSettingsUseSslHelpText")]
|
||||
[FieldDefinition(3, Label = "UseSsl", Type = FieldType.Checkbox, HelpText = "NotificationsSettingsUseSslHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "UseSsl", "serviceName", "Plex")]
|
||||
public bool UseSsl { get; set; }
|
||||
|
||||
[FieldDefinition(3, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "ConnectionSettingsUrlBaseHelpText")]
|
||||
[FieldDefinition(4, Label = "UrlBase", Type = FieldType.Textbox, Advanced = true, HelpText = "ConnectionSettingsUrlBaseHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "UrlBase", "connectionName", "Plex")]
|
||||
[FieldToken(TokenField.HelpText, "UrlBase", "url", "http://[host]:[port]/[urlBase]/plex")]
|
||||
public string UrlBase { get; set; }
|
||||
|
||||
[FieldDefinition(4, Label = "NotificationsPlexSettingsAuthToken", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey, Advanced = true)]
|
||||
[FieldDefinition(5, Label = "NotificationsPlexSettingsAuthToken", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey, Advanced = true)]
|
||||
public string AuthToken { get; set; }
|
||||
|
||||
[FieldDefinition(5, Label = "NotificationsPlexSettingsAuthenticateWithPlexTv", Type = FieldType.OAuth)]
|
||||
[FieldDefinition(6, Label = "NotificationsPlexSettingsAuthenticateWithPlexTv", Type = FieldType.OAuth)]
|
||||
public string SignIn { get; set; }
|
||||
|
||||
[FieldDefinition(6, Label = "NotificationsSettingsUpdateLibrary", Type = FieldType.Checkbox)]
|
||||
[FieldDefinition(7, Label = "NotificationsSettingsUpdateLibrary", Type = FieldType.Checkbox)]
|
||||
public bool UpdateLibrary { get; set; }
|
||||
|
||||
[FieldDefinition(7, Label = "NotificationsSettingsUpdateMapPathsFrom", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsFromHelpText")]
|
||||
[FieldDefinition(8, Label = "NotificationsSettingsUpdateMapPathsFrom", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsFromHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "NotificationsSettingsUpdateMapPathsFrom", "serviceName", "Plex")]
|
||||
public string MapFrom { get; set; }
|
||||
|
||||
[FieldDefinition(8, Label = "NotificationsSettingsUpdateMapPathsTo", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsToHelpText")]
|
||||
[FieldDefinition(9, Label = "NotificationsSettingsUpdateMapPathsTo", Type = FieldType.Textbox, Advanced = true, HelpText = "NotificationsSettingsUpdateMapPathsToHelpText")]
|
||||
[FieldToken(TokenField.HelpText, "NotificationsSettingsUpdateMapPathsTo", "serviceName", "Plex")]
|
||||
public string MapTo { get; set; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user