New: Add pushsafer notification service (#8770)

This commit is contained in:
Ricardo Christmann 2023-07-15 04:22:08 +02:00 committed by GitHub
parent 3055ed5336
commit f38077aac7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 271 additions and 0 deletions

View File

@ -0,0 +1,74 @@
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Movies;
namespace NzbDrone.Core.Notifications.Pushsafer
{
public class Pushsafer : NotificationBase<PushsaferSettings>
{
private readonly IPushsaferProxy _proxy;
public Pushsafer(IPushsaferProxy proxy)
{
_proxy = proxy;
}
public override string Name => "Pushsafer";
public override string Link => "https://pushsafer.com/";
public override void OnGrab(GrabMessage grabMessage)
{
_proxy.SendNotification(MOVIE_GRABBED_TITLE, grabMessage.Message, Settings);
}
public override void OnDownload(DownloadMessage message)
{
_proxy.SendNotification(MOVIE_DOWNLOADED_TITLE, message.Message, Settings);
}
public override void OnMovieAdded(Movie movie)
{
_proxy.SendNotification(MOVIE_ADDED_TITLE, $"{movie.Title} added to library", Settings);
}
public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage)
{
_proxy.SendNotification(MOVIE_FILE_DELETED_TITLE, deleteMessage.Message, Settings);
}
public override void OnMovieDelete(MovieDeleteMessage deleteMessage)
{
_proxy.SendNotification(MOVIE_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 OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
{
_proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings);
}
public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message)
{
_proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE, message.Message, Settings);
}
public override ValidationResult Test()
{
var failures = new List<ValidationFailure>();
failures.AddIfNotNull(_proxy.Test(Settings));
return new ValidationResult(failures);
}
}
}

View File

@ -0,0 +1,11 @@
namespace NzbDrone.Core.Notifications.Pushsafer
{
public enum PushsaferPriority
{
Silent = -2,
Quiet = -1,
Normal = 0,
High = 1,
Emergency = 2
}
}

View File

@ -0,0 +1,105 @@
using System;
using System.Net;
using FluentValidation.Results;
using NLog;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http;
namespace NzbDrone.Core.Notifications.Pushsafer
{
public interface IPushsaferProxy
{
void SendNotification(string title, string message, PushsaferSettings settings);
ValidationFailure Test(PushsaferSettings settings);
}
public class PushsaferProxy : IPushsaferProxy
{
private const string URL = "https://pushsafer.com/api";
private readonly IHttpClient _httpClient;
private readonly Logger _logger;
public PushsaferProxy(IHttpClient httpClient, Logger logger)
{
_httpClient = httpClient;
_logger = logger;
}
public void SendNotification(string title, string message, PushsaferSettings settings)
{
var requestBuilder = new HttpRequestBuilder(URL).Post();
requestBuilder.AddFormParameter("k", settings.ApiKey)
.AddFormParameter("d", string.Join("|", settings.DeviceIds))
.AddFormParameter("t", title)
.AddFormParameter("m", message)
.AddFormParameter("pr", settings.Priority);
if ((PushsaferPriority)settings.Priority == PushsaferPriority.Emergency)
{
requestBuilder.AddFormParameter("re", settings.Retry);
requestBuilder.AddFormParameter("ex", settings.Expire);
}
if (!settings.Sound.IsNullOrWhiteSpace())
{
requestBuilder.AddFormParameter("s", settings.Sound);
}
if (!settings.Vibration.IsNullOrWhiteSpace())
{
requestBuilder.AddFormParameter("v", settings.Vibration);
}
if (!settings.Icon.IsNullOrWhiteSpace())
{
requestBuilder.AddFormParameter("i", settings.Icon);
}
if (!settings.IconColor.IsNullOrWhiteSpace())
{
requestBuilder.AddFormParameter("c", settings.IconColor);
}
var request = requestBuilder.Build();
var response = _httpClient.Post(request);
// https://www.pushsafer.com/en/pushapi#api-message
if (response.StatusCode != HttpStatusCode.OK)
{
throw new HttpException(request, response);
}
}
public ValidationFailure Test(PushsaferSettings settings)
{
try
{
const string title = "Test Notification";
const string body = "This is a test message from Radarr";
SendNotification(title, body, settings);
}
catch (HttpException ex)
{
_logger.Error(ex, "Unable to send test message");
return (int)ex.Response.StatusCode switch
{
250 => new ValidationFailure("ApiKey", "API key is invalid"),
270 => new ValidationFailure("DeviceIds", "Device ID is invalid"),
275 => new ValidationFailure("DeviceIds", "Device Group ID is invalid"),
_ => null,
};
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to send test message");
return new ValidationFailure("ApiKey", "Unable to send test message");
}
return null;
}
}
}

View File

@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using FluentValidation;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
namespace NzbDrone.Core.Notifications.Pushsafer
{
public class PushsaferSettingsValidator : AbstractValidator<PushsaferSettings>
{
public PushsaferSettingsValidator()
{
RuleFor(c => c.ApiKey).NotEmpty();
RuleFor(c => c.Retry).GreaterThanOrEqualTo(60).LessThanOrEqualTo(10800).When(c => (PushsaferPriority)c.Priority == PushsaferPriority.Emergency);
RuleFor(c => c.Expire).GreaterThanOrEqualTo(60).LessThanOrEqualTo(10800).When(c => (PushsaferPriority)c.Priority == PushsaferPriority.Emergency);
RuleFor(c => c.Sound).ValidParsedStringRange(0, 62).When(c => c.Sound.IsNotNullOrWhiteSpace());
RuleFor(c => c.Vibration).ValidParsedStringRange(1, 3).When(c => c.Vibration.IsNotNullOrWhiteSpace());
RuleFor(c => c.Icon).ValidParsedStringRange(1, 181).When(c => c.Icon.IsNotNullOrWhiteSpace());
RuleFor(c => c.IconColor).Matches(new Regex("^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$")).When(c => c.IconColor.IsNotNullOrWhiteSpace());
}
}
public class PushsaferSettings : IProviderConfig
{
private static readonly PushsaferSettingsValidator Validator = new ();
public PushsaferSettings()
{
Priority = (int)PushsaferPriority.Normal;
DeviceIds = Array.Empty<string>();
}
[FieldDefinition(0, Label = "API Key", Privacy = PrivacyLevel.ApiKey, HelpLink = "https://www.pushsafer.com/en/pushapi_ext#API-K")]
public string ApiKey { get; set; }
[FieldDefinition(1, Label = "Device IDs", HelpText = "Device Group ID or list of Device IDs (leave blank to send to all devices)", Type = FieldType.Tag, Placeholder = "123456789|987654321")]
public IEnumerable<string> DeviceIds { get; set; }
[FieldDefinition(2, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(PushsaferPriority))]
public int Priority { get; set; }
[FieldDefinition(3, Label = "Retry", Type = FieldType.Textbox, HelpText = "Interval to retry Emergency alerts, minimum 60 seconds")]
public int Retry { get; set; }
[FieldDefinition(4, Label = "Expire", Type = FieldType.Textbox, HelpText = "Maximum time to retry Emergency alerts, maximum 10800 seconds")]
public int Expire { get; set; }
[FieldDefinition(5, Label = "Sound", Type = FieldType.Textbox, Advanced = true, HelpText = "Notification sound 0-62 (leave blank to use the default)", HelpLink = "https://www.pushsafer.com/en/pushapi_ext#API-S")]
public string Sound { get; set; }
[FieldDefinition(6, Label = "Vibration", Type = FieldType.Textbox, Advanced = true, HelpText = "Vibration pattern 1-3 (leave blank to use the device default)", HelpLink = "https://www.pushsafer.com/en/pushapi_ext#API-V")]
public string Vibration { get; set; }
[FieldDefinition(7, Label = "Icon", Type = FieldType.Textbox, Advanced = true, HelpText = "Icon number 1-181 (leave blank to use the default Pushsafer icon)", HelpLink = "https://www.pushsafer.com/en/pushapi_ext#API-I")]
public string Icon { get; set; }
[FieldDefinition(8, Label = "Icon Color", Type = FieldType.Textbox, Advanced = true, HelpText = "Icon color in hex format (leave blank to use the default Pushsafer icon color)", HelpLink = "https://www.pushsafer.com/en/pushapi_ext#API-C")]
public string IconColor { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));
}
}
}

View File

@ -73,5 +73,18 @@ namespace NzbDrone.Core.Validation
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new RegularExpressionValidator("radarr", RegexOptions.IgnoreCase)).WithMessage("Must contain Radarr");
}
public static IRuleBuilderOptions<T, string> ValidParsedStringRange<T>(this IRuleBuilder<T, string> ruleBuilder, int minValue, int maxValue)
{
return ruleBuilder.Must(x =>
{
if (int.TryParse(x, out var value))
{
return value >= minValue && value <= maxValue;
}
return false;
}).WithMessage($"Must be greater than or equal to '{minValue}' and less than or equal to '{maxValue}'");
}
}
}