New: Pushcut notifications

(cherry picked from commit 5f09f2b25f9144666b8e5182e20e263e74d5f5ca)

Closes #4113
This commit is contained in:
Denis Gheorghescu 2023-09-10 23:09:15 +03:00 committed by Bogdan
parent 404c888cca
commit b94df9f714
6 changed files with 241 additions and 0 deletions

View File

@ -0,0 +1,74 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Notifications.Pushcut
{
public class Pushcut : NotificationBase<PushcutSettings>
{
private readonly IPushcutProxy _proxy;
public Pushcut(IPushcutProxy proxy)
{
_proxy = proxy;
}
public override string Name => "Pushcut";
public override string Link => "https://www.pushcut.io";
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_proxy.Test(Settings));
return new ValidationResult(failures);
}
public override void OnGrab(GrabMessage grabMessage)
{
_proxy.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings);
}
public override void OnReleaseImport(AlbumDownloadMessage message)
{
_proxy.SendNotification(ALBUM_DOWNLOADED_TITLE, message.Message, Settings);
}
public override void OnAlbumDelete(AlbumDeleteMessage deleteMessage)
{
_proxy.SendNotification(ALBUM_DELETED_TITLE, deleteMessage.Message, Settings);
}
public override void OnArtistDelete(ArtistDeleteMessage deleteMessage)
{
_proxy.SendNotification(ARTIST_DELETED_TITLE, deleteMessage.Message, Settings);
}
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
{
_proxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheck.Message, Settings);
}
public override void OnHealthRestored(HealthCheck.HealthCheck previousCheck)
{
_proxy.SendNotification(HEALTH_RESTORED_TITLE, $"The following issue is now resolved: {previousCheck.Message}", Settings);
}
public override void OnDownloadFailure(DownloadFailedMessage message)
{
_proxy.SendNotification(DOWNLOAD_FAILURE_TITLE, message.Message, Settings);
}
public override void OnImportFailure(AlbumDownloadMessage message)
{
_proxy.SendNotification(IMPORT_FAILURE_TITLE, message.Message, Settings);
}
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
_proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings);
}
}
}

View File

@ -0,0 +1,28 @@
using System;
using NzbDrone.Common.Exceptions;
namespace NzbDrone.Core.Notifications.Pushcut
{
public class PushcutException : NzbDroneException
{
public PushcutException(string message, params object[] args)
: base(message, args)
{
}
public PushcutException(string message)
: base(message)
{
}
public PushcutException(string message, Exception innerException, params object[] args)
: base(message, innerException, args)
{
}
public PushcutException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@ -0,0 +1,9 @@
namespace NzbDrone.Core.Notifications.Pushcut
{
public class PushcutPayload
{
public string Title { get; set; }
public string Text { get; set; }
public bool? IsTimeSensitive { get; set; }
}
}

View File

@ -0,0 +1,88 @@
using System.Net;
using System.Net.Http;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Core.Notifications.Pushcut
{
public interface IPushcutProxy
{
void SendNotification(string title, string message, PushcutSettings settings);
ValidationFailure Test(PushcutSettings settings);
}
public class PushcutProxy : IPushcutProxy
{
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public PushcutProxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public void SendNotification(string title, string message, PushcutSettings settings)
{
var request = new HttpRequestBuilder("https://api.pushcut.io/v1/notifications/{notificationName}")
.SetSegment("notificationName", settings?.NotificationName)
.SetHeader("API-Key", settings?.ApiKey)
.Accept(HttpAccept.Json)
.Build();
var payload = new PushcutPayload
{
Title = title,
Text = message,
IsTimeSensitive = settings?.TimeSensitive
};
request.Method = HttpMethod.Post;
request.Headers.ContentType = "application/json";
request.SetContent(payload.ToJson());
try
{
_httpClient.Execute(request);
}
catch (HttpException exception)
{
_logger.Error(exception, "Unable to send Pushcut notification: {0}", exception.Message);
throw new PushcutException("Unable to send Pushcut notification: {0}", exception.Message, exception);
}
}
public ValidationFailure Test(PushcutSettings settings)
{
try
{
const string title = "Sonarr Test Title";
const string message = "Success! You have properly configured your Pushcut notification settings.";
SendNotification(title, message, settings);
}
catch (PushcutException pushcutException) when (pushcutException.InnerException is HttpException httpException)
{
if (httpException.Response.StatusCode == HttpStatusCode.Forbidden)
{
_logger.Error(pushcutException, "API Key is invalid: {0}", pushcutException.Message);
return new ValidationFailure("API Key", $"API Key is invalid: {pushcutException.Message}");
}
if (httpException.Response.Content.IsNotNullOrWhiteSpace())
{
var response = Json.Deserialize<PushcutResponse>(httpException.Response.Content);
_logger.Error(pushcutException, "Unable to send test notification. Response from Pushcut: {0}", response.Error);
return new ValidationFailure("Url", $"Unable to send test notification. Response from Pushcut: {response.Error}");
}
_logger.Error(pushcutException, "Unable to connect to Pushcut API. Server connection failed: ({0}) {1}", httpException.Response.StatusCode, pushcutException.Message);
return new ValidationFailure("Host", $"Unable to connect to Pushcut API. Server connection failed: ({httpException.Response.StatusCode}) {pushcutException.Message}");
}
return null;
}
}
}

View File

@ -0,0 +1,7 @@
namespace NzbDrone.Core.Notifications.Pushcut
{
public class PushcutResponse
{
public string Error { get; set; }
}
}

View File

@ -0,0 +1,35 @@
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.Pushcut
{
public class PushcutSettingsValidator : AbstractValidator<PushcutSettings>
{
public PushcutSettingsValidator()
{
RuleFor(settings => settings.ApiKey).NotEmpty();
RuleFor(settings => settings.NotificationName).NotEmpty();
}
}
public class PushcutSettings : IProviderConfig
{
private static readonly PushcutSettingsValidator Validator = new ();
[FieldDefinition(0, Label = "Notification name", Type = FieldType.Textbox, HelpText = "Notification name from Notifications tab of the Pushcut app.")]
public string NotificationName { get; set; }
[FieldDefinition(1, Label = "API Key", Type = FieldType.Textbox, Privacy = PrivacyLevel.ApiKey, HelpText = "API Keys can be managed in the Account view of the Pushcut app.")]
public string ApiKey { get; set; }
[FieldDefinition(2, Label = "Time sensitive", Type = FieldType.Checkbox, HelpText = "Check to mark the notification as \"Time-Sensitive\"")]
public bool TimeSensitive { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}