Added: Ntfy provider for notifications
This commit is contained in:
parent
51eeb4ee0f
commit
f6e6bc98f7
|
@ -0,0 +1,60 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Ntfy
|
||||||
|
{
|
||||||
|
public class Ntfy : NotificationBase<NtfySettings>
|
||||||
|
{
|
||||||
|
private readonly INtfyProxy _proxy;
|
||||||
|
|
||||||
|
public Ntfy(INtfyProxy proxy)
|
||||||
|
{
|
||||||
|
_proxy = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name => "ntfy.sh";
|
||||||
|
|
||||||
|
public override string Link => "https://ntfy.sh/";
|
||||||
|
|
||||||
|
public override void OnGrab(GrabMessage grabMessage)
|
||||||
|
{
|
||||||
|
_proxy.SendNotification(EPISODE_GRABBED_TITLE_BRANDED, grabMessage.Message, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnDownload(DownloadMessage message)
|
||||||
|
{
|
||||||
|
_proxy.SendNotification(EPISODE_DOWNLOADED_TITLE_BRANDED, message.Message, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEpisodeFileDelete(EpisodeDeleteMessage deleteMessage)
|
||||||
|
{
|
||||||
|
_proxy.SendNotification(EPISODE_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnSeriesDelete(SeriesDeleteMessage deleteMessage)
|
||||||
|
{
|
||||||
|
_proxy.SendNotification(SERIES_DELETED_TITLE, deleteMessage.Message, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
|
||||||
|
{
|
||||||
|
_proxy.SendNotification(HEALTH_ISSUE_TITLE_BRANDED, healthCheck.Message, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage)
|
||||||
|
{
|
||||||
|
_proxy.SendNotification(APPLICATION_UPDATE_TITLE_BRANDED, updateMessage.Message, Settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ValidationResult Test()
|
||||||
|
{
|
||||||
|
var failures = new List<ValidationFailure>();
|
||||||
|
|
||||||
|
failures.AddIfNotNull(_proxy.Test(Settings));
|
||||||
|
|
||||||
|
return new ValidationResult(failures);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using NzbDrone.Common.Exceptions;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Ntfy
|
||||||
|
{
|
||||||
|
public class NtfyException : NzbDroneException
|
||||||
|
{
|
||||||
|
public NtfyException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public NtfyException(string message, Exception innerException, params object[] args)
|
||||||
|
: base(message, innerException, args)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
namespace NzbDrone.Core.Notifications.Ntfy
|
||||||
|
{
|
||||||
|
public enum NtfyPriority
|
||||||
|
{
|
||||||
|
Min = 1,
|
||||||
|
Low = 2,
|
||||||
|
Default = 3,
|
||||||
|
High = 4,
|
||||||
|
Max = 5
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,138 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
|
using FluentValidation.Results;
|
||||||
|
using NLog;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Common.Http;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Ntfy
|
||||||
|
{
|
||||||
|
public interface INtfyProxy
|
||||||
|
{
|
||||||
|
void SendNotification(string title, string message, NtfySettings settings);
|
||||||
|
|
||||||
|
ValidationFailure Test(NtfySettings settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NtfyProxy : INtfyProxy
|
||||||
|
{
|
||||||
|
private const string DEFAULT_PUSH_URL = "https://ntfy.sh";
|
||||||
|
|
||||||
|
private readonly IHttpClient _httpClient;
|
||||||
|
|
||||||
|
private readonly Logger _logger;
|
||||||
|
|
||||||
|
public NtfyProxy(IHttpClient httpClient, Logger logger)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendNotification(string title, string message, NtfySettings settings)
|
||||||
|
{
|
||||||
|
var error = false;
|
||||||
|
|
||||||
|
var serverUrl = settings.ServerUrl.IsNullOrWhiteSpace() ? NtfyProxy.DEFAULT_PUSH_URL : settings.ServerUrl;
|
||||||
|
|
||||||
|
foreach (var topic in settings.Topics)
|
||||||
|
{
|
||||||
|
var request = BuildTopicRequest(serverUrl, topic);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SendNotification(title, message, request, settings);
|
||||||
|
}
|
||||||
|
catch (NtfyException ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to send test message to {0}", topic);
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
throw new NtfyException("Unable to send Ntfy notifications to all topics");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequestBuilder BuildTopicRequest(string serverUrl, string topic)
|
||||||
|
{
|
||||||
|
var trimServerUrl = serverUrl.TrimEnd('/');
|
||||||
|
|
||||||
|
var requestBuilder = new HttpRequestBuilder($"{trimServerUrl}/{topic}").Post();
|
||||||
|
|
||||||
|
return requestBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidationFailure Test(NtfySettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const string title = "Radarr - Test Notification";
|
||||||
|
|
||||||
|
const string body = "This is a test message from Radarr";
|
||||||
|
|
||||||
|
SendNotification(title, body, settings);
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Authorization is required");
|
||||||
|
return new ValidationFailure("UserName", "Authorization is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Error(ex, "Unable to send test message");
|
||||||
|
return new ValidationFailure("ServerUrl", "Unable to send test message");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Unable to send test message");
|
||||||
|
return new ValidationFailure("", "Unable to send test message");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendNotification(string title, string message, HttpRequestBuilder requestBuilder, NtfySettings settings)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
requestBuilder.Headers.Add("X-Title", title);
|
||||||
|
requestBuilder.Headers.Add("X-Message", message);
|
||||||
|
requestBuilder.Headers.Add("X-Priority", settings.Priority.ToString());
|
||||||
|
|
||||||
|
if (settings.Tags.Any())
|
||||||
|
{
|
||||||
|
requestBuilder.Headers.Add("X-Tags", settings.Tags.Join(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!settings.ClickUrl.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
requestBuilder.Headers.Add("X-Click", settings.ClickUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = requestBuilder.Build();
|
||||||
|
|
||||||
|
if (!settings.UserName.IsNullOrWhiteSpace() && !settings.Password.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
request.Credentials = new BasicNetworkCredential(settings.UserName, settings.Password);
|
||||||
|
}
|
||||||
|
|
||||||
|
_httpClient.Execute(request);
|
||||||
|
}
|
||||||
|
catch (HttpException ex)
|
||||||
|
{
|
||||||
|
if (ex.Response.StatusCode == HttpStatusCode.Unauthorized || ex.Response.StatusCode == HttpStatusCode.Forbidden)
|
||||||
|
{
|
||||||
|
_logger.Error(ex, "Authorization is required");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NtfyException("Unable to send text message: {0}", ex, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FluentValidation;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.Annotations;
|
||||||
|
using NzbDrone.Core.ThingiProvider;
|
||||||
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Ntfy
|
||||||
|
{
|
||||||
|
public class NtfySettingsValidator : AbstractValidator<NtfySettings>
|
||||||
|
{
|
||||||
|
public NtfySettingsValidator()
|
||||||
|
{
|
||||||
|
RuleFor(c => c.Topics).NotEmpty();
|
||||||
|
RuleFor(c => c.Priority).InclusiveBetween(1, 5);
|
||||||
|
RuleFor(c => c.ServerUrl).IsValidUrl().When(c => !c.ServerUrl.IsNullOrWhiteSpace());
|
||||||
|
RuleFor(c => c.ClickUrl).IsValidUrl().When(c => !c.ClickUrl.IsNullOrWhiteSpace());
|
||||||
|
RuleFor(c => c.UserName).NotEmpty().When(c => !c.Password.IsNullOrWhiteSpace());
|
||||||
|
RuleFor(c => c.Password).NotEmpty().When(c => !c.UserName.IsNullOrWhiteSpace());
|
||||||
|
RuleForEach(c => c.Topics).NotEmpty().Matches("[a-zA-Z0-9_-]+").Must(c => !InvalidTopics.Contains(c)).WithMessage("Invalid topic");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> InvalidTopics => new List<string> { "announcements", "app", "docs", "settings", "stats", "mytopic-rw", "mytopic-ro", "mytopic-wo" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NtfySettings : IProviderConfig
|
||||||
|
{
|
||||||
|
private static readonly NtfySettingsValidator Validator = new NtfySettingsValidator();
|
||||||
|
|
||||||
|
public NtfySettings()
|
||||||
|
{
|
||||||
|
Topics = Array.Empty<string>();
|
||||||
|
Priority = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
[FieldDefinition(0, Label = "Server Url", Type = FieldType.Url, HelpLink = "https://ntfy.sh/docs/install/", HelpText = "Leave blank to use public server (https://ntfy.sh)")]
|
||||||
|
public string ServerUrl { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(1, Label = "User Name", HelpText = "Optional Authorization", Privacy = PrivacyLevel.UserName)]
|
||||||
|
public string UserName { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(2, Label = "Password", Type = FieldType.Password, HelpText = "Optional Password", Privacy = PrivacyLevel.Password)]
|
||||||
|
public string Password { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(3, Label = "Priority", Type = FieldType.Select, SelectOptions = typeof(NtfyPriority))]
|
||||||
|
public int Priority { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(4, Label = "Topics", HelpText = "List of Topics to send notifications to", Type = FieldType.Tag)]
|
||||||
|
public IEnumerable<string> Topics { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(5, Label = "Ntfy Tags and Emojis", Type = FieldType.Tag, HelpText = "Optional list of tags or emojis to use", HelpLink = "https://ntfy.sh/docs/emojis/")]
|
||||||
|
public IEnumerable<string> Tags { get; set; }
|
||||||
|
|
||||||
|
[FieldDefinition(6, Label = "Click Url", Type = FieldType.Url, HelpText = "Optional link when user clicks notification")]
|
||||||
|
public string ClickUrl { get; set; }
|
||||||
|
|
||||||
|
public NzbDroneValidationResult Validate()
|
||||||
|
{
|
||||||
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue