1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-27 17:54:34 -04:00

Compare commits

...

9 Commits

Author SHA1 Message Date
Mark McDowall
c99e92e6af New: Validate PMS version before performing a library update 2016-12-08 10:23:53 -08:00
Mark McDowall
3f64c01d5b Fixed: Partial library updates for Plex Media Server 1.3 2016-12-07 12:58:23 -08:00
Mark McDowall
f022dae1fa Fixed: Partial library updates for Plex Media Server 1.3 2016-12-06 17:05:00 -08:00
Mark McDowall
52ad8cf37f Fixed: Error handling of valid, empty responses from Plex Media Server 2016-12-06 09:11:50 -08:00
Mark McDowall
3d20fd8f96 Fixed: Error handling of valid, empty responses from Plex Media Server 2016-12-05 09:34:17 -08:00
Lloyd Sparkes
cf662291d5 Fixed: Lingering Socks5 Proxy sockets when proxy is using dynamic ips 2016-12-04 16:43:18 +01:00
Mark McDowall
43d85bf59d Fixed: Parsing of some Plex server responses before 1.3 2016-12-02 10:39:12 -08:00
ARTbird309
4a149c356b New: Telegram notifications
Closes #1355
2016-11-30 19:08:29 -08:00
Chris Allen
740fc9154f Shorten 'MPEG-2 Video' to 'MPEG2'. 2016-11-30 17:54:22 -08:00
14 changed files with 288 additions and 25 deletions

View File

@@ -48,11 +48,11 @@
<Private>True</Private>
</Reference>
<Reference Include="Org.Mentalis, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DotNet4.SocksProxy.1.1.0.0\lib\net40\Org.Mentalis.dll</HintPath>
<HintPath>..\packages\DotNet4.SocksProxy.1.3.2.0\lib\net40\Org.Mentalis.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SocksWebProxy, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DotNet4.SocksProxy.1.1.0.0\lib\net40\SocksWebProxy.dll</HintPath>
<Reference Include="SocksWebProxy, Version=1.3.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DotNet4.SocksProxy.1.3.2.0\lib\net40\SocksWebProxy.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DotNet4.SocksProxy" version="1.1.0.0" targetFramework="net40" />
<package id="DotNet4.SocksProxy" version="1.3.2.0" targetFramework="net40" />
<package id="ICSharpCode.SharpZipLib.Patched" version="0.86.5" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
<package id="NLog" version="4.3.4" targetFramework="net40" />

View File

@@ -17,12 +17,13 @@ namespace NzbDrone.Core.Notifications.Plex.Models
public string Type { get; set; }
public string Language { get; set; }
public PlexSectionLocation Location { get; set; }
[JsonProperty("Location")]
public List<PlexSectionLocation> Locations { get; set; }
}
public class PlexSectionsContainer
{
[JsonProperty("Metadata")]
[JsonProperty("Directory")]
public List<PlexSection> Sections { get; set; }
}

View File

@@ -13,6 +13,7 @@ namespace NzbDrone.Core.Notifications.Plex.Models
public class PlexSectionResponse
{
[JsonProperty("Metadata")]
public List<PlexSectionItem> Items { get; set; }
}

View File

@@ -53,7 +53,7 @@ namespace NzbDrone.Core.Notifications.Plex
{
Id = s.Id,
Language = s.Language,
Location = s.Locations.FirstOrDefault(),
Locations = s.Locations,
Type = s.Type
})
.ToList();
@@ -97,12 +97,15 @@ namespace NzbDrone.Core.Notifications.Plex
_logger.Trace("Version response: {0}", response.Content);
CheckForError(response, settings);
if (response.Content.Contains("MediaContainer"))
if (response.Content.Contains("_children"))
{
return Json.Deserialize<PlexResponse<PlexIdentity>>(response.Content).MediaContainer.Version;
return Json.Deserialize<PlexIdentity>(response.Content)
.Version;
}
return Json.Deserialize<PlexIdentity>(response.Content).Version;
return Json.Deserialize<PlexResponse<PlexIdentity>>(response.Content)
.MediaContainer
.Version;
}
public List<PlexPreference> Preferences(PlexServerSettings settings)
@@ -114,12 +117,15 @@ namespace NzbDrone.Core.Notifications.Plex
_logger.Trace("Preferences response: {0}", response.Content);
CheckForError(response, settings);
if (response.Content.Contains("MediaContainer"))
if (response.Content.Contains("_children"))
{
return Json.Deserialize<PlexResponse<PlexPreferences>>(response.Content).MediaContainer.Preferences;
return Json.Deserialize<PlexPreferencesLegacy>(response.Content)
.Preferences;
}
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)
@@ -143,7 +149,8 @@ namespace NzbDrone.Core.Notifications.Plex
else
{
items = Json.Deserialize<PlexSectionResponse>(response.Content)
items = Json.Deserialize<PlexResponse<PlexSectionResponse>>(response.Content)
.MediaContainer
.Items;
}
@@ -240,9 +247,15 @@ namespace NzbDrone.Core.Notifications.Plex
throw new PlexAuthenticationException("Unauthorized - Username or password is incorrect");
}
var error = response.Content.Contains("MediaContainer") ?
Json.Deserialize<PlexResponse<PlexError>>(response.Content).MediaContainer :
Json.Deserialize<PlexError>(response.Content);
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())
{

View File

@@ -19,12 +19,14 @@ namespace NzbDrone.Core.Notifications.Plex
public class PlexServerService : IPlexServerService
{
private readonly ICached<Version> _versionCache;
private readonly ICached<bool> _partialUpdateCache;
private readonly IPlexServerProxy _plexServerProxy;
private readonly Logger _logger;
public PlexServerService(ICacheManager cacheManager, IPlexServerProxy plexServerProxy, Logger logger)
{
_versionCache = cacheManager.GetCache<Version>(GetType(), "versionCache");
_partialUpdateCache = cacheManager.GetCache<bool>(GetType(), "partialUpdateCache");
_plexServerProxy = plexServerProxy;
_logger = logger;
@@ -35,9 +37,12 @@ namespace NzbDrone.Core.Notifications.Plex
try
{
_logger.Debug("Sending Update Request to Plex Server");
var version = _versionCache.Get(settings.Host, () => GetVersion(settings), TimeSpan.FromHours(2));
ValidateVersion(version);
var sections = GetSections(settings);
var partialUpdates = _partialUpdateCache.Get(settings.Host, () => PartialUpdatesAllowed(settings), TimeSpan.FromHours(2));
var partialUpdates = _partialUpdateCache.Get(settings.Host, () => PartialUpdatesAllowed(settings, version), TimeSpan.FromHours(2));
if (partialUpdates)
{
@@ -64,13 +69,10 @@ namespace NzbDrone.Core.Notifications.Plex
return _plexServerProxy.GetTvSections(settings).ToList();
}
private bool PartialUpdatesAllowed(PlexServerSettings settings)
private bool PartialUpdatesAllowed(PlexServerSettings settings, Version version)
{
try
{
var rawVersion = GetVersion(settings);
var version = new Version(Regex.Match(rawVersion, @"^(\d+[.-]){4}").Value.Trim('.', '-'));
if (version >= new Version(0, 9, 12, 0))
{
var preferences = GetPreferences(settings);
@@ -92,13 +94,28 @@ namespace NzbDrone.Core.Notifications.Plex
return false;
}
private string GetVersion(PlexServerSettings settings)
private void ValidateVersion(Version version)
{
if (version >= new Version(1, 3, 0) && version < new Version(1, 3, 1))
{
throw new PlexVersionException("Found version {0}, upgrade to PMS 1.3.1 to fix library updating and then restart Sonarr", version);
}
}
private Version GetVersion(PlexServerSettings settings)
{
_logger.Debug("Getting version from Plex host: {0}", settings.Host);
return _plexServerProxy.Version(settings);
var rawVersion = _plexServerProxy.Version(settings);
var version = new Version(Regex.Match(rawVersion, @"^(\d+[.-]){4}").Value.Trim('.', '-'));
return version;
}
private List<PlexPreference> GetPreferences(PlexServerSettings settings)
{
_logger.Debug("Getting preferences from Plex host: {0}", settings.Host);

View File

@@ -0,0 +1,15 @@
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Notifications.Plex
{
public class PlexVersionException : NzbDroneException
{
public PlexVersionException(string message) : base(message)
{
}
public PlexVersionException(string message, params object[] args) : base(message, args)
{
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace NzbDrone.Core.Notifications.Telegram
{
public class InvalidResponseException : Exception
{
public InvalidResponseException()
{
}
public InvalidResponseException(string message) : base(message)
{
}
}
}

View File

@@ -0,0 +1,65 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Notifications.Telegram
{
public class Telegram : NotificationBase<TelegramSettings>
{
private readonly ITelegramProxy _proxy;
public Telegram(ITelegramProxy proxy)
{
_proxy = proxy;
}
public override string Link
{
get { return "https://telegram.org/"; }
}
public override void OnGrab(GrabMessage grabMessage)
{
const string title = "Episode Grabbed";
_proxy.SendNotification(title, grabMessage.Message, Settings);
}
public override void OnDownload(DownloadMessage message)
{
const string title = "Episode Downloaded";
_proxy.SendNotification(title, message.Message, Settings);
}
public override void OnRename(Series series)
{
}
public override string Name
{
get
{
return "Telegram";
}
}
public override bool SupportsOnRename
{
get
{
return false;
}
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_proxy.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@@ -0,0 +1,14 @@
using Newtonsoft.Json;
namespace NzbDrone.Core.Notifications.Telegram
{
public class TelegramError
{
public bool Ok { get; set; }
[JsonProperty(PropertyName = "error_code")]
public int ErrorCode { get; set; }
public string Description { get; set; }
}
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
using RestSharp;
using NzbDrone.Core.Rest;
namespace NzbDrone.Core.Notifications.Telegram
{
public interface ITelegramProxy
{
void SendNotification(string title, string message, TelegramSettings settings);
ValidationFailure Test(TelegramSettings settings);
}
public class TelegramProxy : ITelegramProxy
{
private readonly Logger _logger;
private const string URL = "https://api.telegram.org";
public TelegramProxy(Logger logger)
{
_logger = logger;
}
public void SendNotification(string title, string message, TelegramSettings settings)
{
//Format text to add the title before and bold using markdown
var text = $"*{title}*\n{message}";
var client = RestClientFactory.BuildClient(URL);
var request = new RestRequest("bot{token}/sendmessage", Method.POST);
request.AddUrlSegment("token", settings.BotToken);
request.AddParameter("chat_id", settings.ChatId);
request.AddParameter("parse_mode", "Markdown");
request.AddParameter("text", text);
client.ExecuteAndValidate(request);
}
public ValidationFailure Test(TelegramSettings settings)
{
try
{
const string title = "Test Notification";
const string body = "This is a test message from Sonarr";
SendNotification(title, body, settings);
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message: " + ex.Message);
var restException = ex as RestException;
if (restException != null && restException.Response.StatusCode == HttpStatusCode.BadRequest)
{
var error = Json.Deserialize<TelegramError>(restException.Response.Content);
var property = error.Description.ContainsIgnoreCase("chat not found") ? "ChatId" : "BotToken";
return new ValidationFailure(property, error.Description);
}
return new ValidationFailure("BotToken", "Unable to send test message");
}
return null;
}
}
}

View File

@@ -0,0 +1,40 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.Telegram
{
public class TelegramSettingsValidator : AbstractValidator<TelegramSettings>
{
public TelegramSettingsValidator()
{
RuleFor(c => c.BotToken).NotEmpty();
RuleFor(c => c.ChatId).NotEmpty();
}
}
public class TelegramSettings : IProviderConfig
{
private static readonly TelegramSettingsValidator Validator = new TelegramSettingsValidator();
[FieldDefinition(0, Label = "Bot Token", HelpLink = "https://core.telegram.org/bots")]
public string BotToken { get; set; }
[FieldDefinition(1, Label = "Chat ID", HelpLink = "http://stackoverflow.com/a/37396871/882971", HelpText = "You must start a conversation with the bot or add it to your group to receive messages")]
public string ChatId { get; set; }
public bool IsValid
{
get
{
return !string.IsNullOrWhiteSpace(ChatId) && !string.IsNullOrWhiteSpace(BotToken);
}
}
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@@ -829,6 +829,7 @@
<Compile Include="Notifications\Plex\PlexAuthenticationException.cs" />
<Compile Include="Notifications\CustomScript\CustomScript.cs" />
<Compile Include="Notifications\CustomScript\CustomScriptSettings.cs" />
<Compile Include="Notifications\Plex\PlexVersionException.cs" />
<Compile Include="Notifications\Plex\PlexHomeTheater.cs" />
<Compile Include="Notifications\Plex\PlexHomeTheaterSettings.cs" />
<Compile Include="Notifications\Plex\PlexClientService.cs" />
@@ -842,6 +843,10 @@
<Compile Include="Notifications\Synology\SynologyIndexer.cs" />
<Compile Include="Notifications\Synology\SynologyIndexerProxy.cs" />
<Compile Include="Notifications\Synology\SynologyIndexerSettings.cs" />
<Compile Include="Notifications\Telegram\InvalidResponseException.cs" />
<Compile Include="Notifications\Telegram\Telegram.cs" />
<Compile Include="Notifications\Telegram\TelegramService.cs" />
<Compile Include="Notifications\Telegram\TelegramSettings.cs" />
<Compile Include="Notifications\Twitter\OAuthToken.cs" />
<Compile Include="Notifications\Twitter\TwitterException.cs" />
<Compile Include="Notifications\Webhook\WebhookEpisode.cs" />
@@ -1173,6 +1178,7 @@
<Link>libsqlite3.0.dylib</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Compile Include="Notifications\Telegram\TelegramError.cs" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@@ -470,6 +470,10 @@ namespace NzbDrone.Core.Organizer
}
break;
case "MPEG-2 Video":
videoCodec = "MPEG2";
break;
default:
videoCodec = episodeFile.MediaInfo.VideoCodec;
break;