diff --git a/src/NzbDrone.Core/Notifications/Apprise/AppriseError.cs b/src/NzbDrone.Core/Notifications/Apprise/AppriseError.cs new file mode 100644 index 000000000..581c223fa --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Apprise/AppriseError.cs @@ -0,0 +1,7 @@ +namespace NzbDrone.Core.Notifications.Apprise +{ + public class AppriseError + { + public string Error { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/Apprise/AppriseException.cs b/src/NzbDrone.Core/Notifications/Apprise/AppriseException.cs new file mode 100644 index 000000000..13ad743e7 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Apprise/AppriseException.cs @@ -0,0 +1,18 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Notifications.Apprise +{ + public class AppriseException : NzbDroneException + { + public AppriseException(string message) + : base(message) + { + } + + public AppriseException(string message, Exception innerException, params object[] args) + : base(message, innerException, args) + { + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Apprise/AppriseNotificationType.cs b/src/NzbDrone.Core/Notifications/Apprise/AppriseNotificationType.cs new file mode 100644 index 000000000..655f4ea32 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Apprise/AppriseNotificationType.cs @@ -0,0 +1,19 @@ +using System.Runtime.Serialization; + +namespace NzbDrone.Core.Notifications.Apprise +{ + public enum AppriseNotificationType + { + [EnumMember(Value = "info")] + Info = 0, + + [EnumMember(Value = "success")] + Success, + + [EnumMember(Value = "warning")] + Warning, + + [EnumMember(Value = "failure")] + Failure, + } +} diff --git a/src/NzbDrone.Core/Notifications/Apprise/ApprisePayload.cs b/src/NzbDrone.Core/Notifications/Apprise/ApprisePayload.cs new file mode 100644 index 000000000..bd38da148 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Apprise/ApprisePayload.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; + +namespace NzbDrone.Core.Notifications.Apprise +{ + public class ApprisePayload + { + public string Urls { get; set; } + + public string Title { get; set; } + + public string Body { get; set; } + + public AppriseNotificationType Type { get; set; } + + public string Tag { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/Apprise/AppriseProxy.cs b/src/NzbDrone.Core/Notifications/Apprise/AppriseProxy.cs index c62d8c68a..f73a8727f 100644 --- a/src/NzbDrone.Core/Notifications/Apprise/AppriseProxy.cs +++ b/src/NzbDrone.Core/Notifications/Apprise/AppriseProxy.cs @@ -3,16 +3,15 @@ using System.Linq; using System.Net; using FluentValidation.Results; using NLog; -using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; +using NzbDrone.Common.Serializer; namespace NzbDrone.Core.Notifications.Apprise { public interface IAppriseProxy { void SendNotification(AppriseSettings settings, string title, string message); - ValidationFailure Test(AppriseSettings settings); } @@ -27,11 +26,18 @@ namespace NzbDrone.Core.Notifications.Apprise _logger = logger; } - public void SendNotification(AppriseSettings settings, string title, string body) + public void SendNotification(AppriseSettings settings, string title, string message) { - var requestBuilder = new HttpRequestBuilder(settings.BaseUrl.TrimEnd('/', ' ')).Post() - .AddFormParameter("title", title) - .AddFormParameter("body", body); + var payload = new ApprisePayload + { + Title = title, + Body = message, + Type = (AppriseNotificationType)settings.NotificationType + }; + + var requestBuilder = new HttpRequestBuilder(settings.BaseUrl.TrimEnd('/', ' ')) + .Post() + .Accept(HttpAccept.Json); if (settings.ConfigurationKey.IsNotNullOrWhiteSpace()) { @@ -41,14 +47,14 @@ namespace NzbDrone.Core.Notifications.Apprise } else if (settings.StatelessUrls.IsNotNullOrWhiteSpace()) { - requestBuilder - .Resource("/notify") - .AddFormParameter("urls", settings.StatelessUrls); + requestBuilder.Resource("/notify"); + + payload.Urls = settings.StatelessUrls; } if (settings.Tags.Any()) { - requestBuilder.AddFormParameter("tag", settings.Tags.Join(",")); + payload.Tag = settings.Tags.Join(","); } if (settings.AuthUsername.IsNotNullOrWhiteSpace() || settings.AuthPassword.IsNotNullOrWhiteSpace()) @@ -56,7 +62,20 @@ namespace NzbDrone.Core.Notifications.Apprise requestBuilder.NetworkCredential = new BasicNetworkCredential(settings.AuthUsername, settings.AuthPassword); } - _httpClient.Execute(requestBuilder.Build()); + var request = requestBuilder.Build(); + + request.Headers.ContentType = "application/json"; + request.SetContent(payload.ToJson()); + + try + { + _httpClient.Execute(request); + } + catch (HttpException ex) + { + _logger.Error(ex, "Unable to send message"); + throw new AppriseException("Unable to send Apprise notifications: {0}", ex, ex.Message); + } } public ValidationFailure Test(AppriseSettings settings) @@ -68,21 +87,29 @@ namespace NzbDrone.Core.Notifications.Apprise { SendNotification(settings, title, body); } - catch (HttpException ex) + catch (AppriseException ex) when (ex.InnerException is HttpException httpException) { - if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) + if (httpException.Response.StatusCode == HttpStatusCode.Unauthorized) { - _logger.Error(ex, $"HTTP Auth credentials are invalid: {ex.Message}"); + _logger.Error(ex, $"HTTP Auth credentials are invalid: {0}", ex.Message); return new ValidationFailure("AuthUsername", $"HTTP Auth credentials are invalid: {ex.Message}"); } - _logger.Error(ex, "Unable to send test message. Server connection failed. Status code: {0}", ex.Message); - return new ValidationFailure("Url", $"Unable to connect to Apprise API. Please try again later. Status code: {ex.Message}"); + if (httpException.Response.Content.IsNotNullOrWhiteSpace()) + { + var error = Json.Deserialize(httpException.Response.Content); + + _logger.Error(ex, $"Unable to send test message. Response from API: {0}", error.Error); + return new ValidationFailure(string.Empty, $"Unable to send test message. Response from API: {error.Error}"); + } + + _logger.Error(ex, "Unable to send test message. Server connection failed: ({0}) {1}", httpException.Response.StatusCode, ex.Message); + return new ValidationFailure("Url", $"Unable to connect to Apprise API. Server connection failed: ({httpException.Response.StatusCode}) {ex.Message}"); } catch (Exception ex) { - _logger.Error(ex, "Unable to send test message. Status code: {0}", ex.Message); - return new ValidationFailure("Url", $"Unable to send test message. Status code: {ex.Message}"); + _logger.Error(ex, "Unable to send test message: {0}", ex.Message); + return new ValidationFailure("Url", $"Unable to send test message: {ex.Message}"); } return null; diff --git a/src/NzbDrone.Core/Notifications/Apprise/AppriseSettings.cs b/src/NzbDrone.Core/Notifications/Apprise/AppriseSettings.cs index d7bfd3a04..2e29d76df 100644 --- a/src/NzbDrone.Core/Notifications/Apprise/AppriseSettings.cs +++ b/src/NzbDrone.Core/Notifications/Apprise/AppriseSettings.cs @@ -41,6 +41,7 @@ namespace NzbDrone.Core.Notifications.Apprise public AppriseSettings() { + NotificationType = (int)AppriseNotificationType.Info; Tags = Array.Empty(); } @@ -53,13 +54,16 @@ namespace NzbDrone.Core.Notifications.Apprise [FieldDefinition(3, Label = "Apprise Stateless Urls", Type = FieldType.Textbox, HelpText = "One or more URLs separated by commas identifying where the notification should be sent to. Leave empty if Persistent Storage is used.", HelpLink = "https://github.com/caronc/apprise#productivity-based-notifications")] public string StatelessUrls { get; set; } - [FieldDefinition(4, Label = "Apprise Tags", Type = FieldType.Tag, HelpText = "Optionally notify only those tagged accordingly.")] + [FieldDefinition(4, Label = "Apprise Notification Type", Type = FieldType.Select, SelectOptions = typeof(AppriseNotificationType))] + public int NotificationType { get; set; } + + [FieldDefinition(5, Label = "Apprise Tags", Type = FieldType.Tag, HelpText = "Optionally notify only those tagged accordingly.")] public IEnumerable Tags { get; set; } - [FieldDefinition(5, Label = "Auth Username", Type = FieldType.Textbox, HelpText = "HTTP Basic Auth Username", Privacy = PrivacyLevel.UserName)] + [FieldDefinition(6, Label = "Auth Username", Type = FieldType.Textbox, HelpText = "HTTP Basic Auth Username", Privacy = PrivacyLevel.UserName)] public string AuthUsername { get; set; } - [FieldDefinition(6, Label = "Auth Password", Type = FieldType.Password, HelpText = "HTTP Basic Auth Password", Privacy = PrivacyLevel.Password)] + [FieldDefinition(7, Label = "Auth Password", Type = FieldType.Password, HelpText = "HTTP Basic Auth Password", Privacy = PrivacyLevel.Password)] public string AuthPassword { get; set; } public NzbDroneValidationResult Validate()