From 998a2df7f04770861b03928b1d9d03cfaa6cfb58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Skyler=20M=C3=A4ntysaari?= Date: Wed, 20 May 2020 21:22:05 +0300 Subject: [PATCH] New: SendGrid Notifications --- .../Notifications/SendGrid/SendGrid.cs | 67 +++++++++++++++ .../SendGrid/SendGridException.cs | 18 ++++ .../Notifications/SendGrid/SendGridPayload.cs | 50 +++++++++++ .../Notifications/SendGrid/SendGridProxy.cs | 83 +++++++++++++++++++ .../SendGrid/SendGridSettings.cs | 46 ++++++++++ 5 files changed, 264 insertions(+) create mode 100644 src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs create mode 100644 src/NzbDrone.Core/Notifications/SendGrid/SendGridException.cs create mode 100644 src/NzbDrone.Core/Notifications/SendGrid/SendGridPayload.cs create mode 100644 src/NzbDrone.Core/Notifications/SendGrid/SendGridProxy.cs create mode 100644 src/NzbDrone.Core/Notifications/SendGrid/SendGridSettings.cs diff --git a/src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs b/src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs new file mode 100644 index 000000000..c438df60c --- /dev/null +++ b/src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using FluentValidation.Results; +using NLog; + +namespace NzbDrone.Core.Notifications.SendGrid +{ + public class SendGrid : NotificationBase + { + private readonly ISendGridProxy _proxy; + private readonly Logger _logger; + + public SendGrid(ISendGridProxy proxy, Logger logger) + { + _proxy = proxy; + _logger = logger; + } + + public override string Name => "SendGrid"; + public override string Link => "https://sendgrid.com/"; + + 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 OnHealthIssue(HealthCheck.HealthCheck healthCheck) + { + _proxy.SendNotification(HEALTH_ISSUE_TITLE, healthCheck.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 ValidationResult Test() + { + var failures = new List(); + + try + { + const string title = "Test Notification"; + const string body = "This is a test message from Lidarr"; + + _proxy.SendNotification(title, body, Settings); + } + catch (Exception ex) + { + _logger.Error(ex, "Unable to send test message"); + failures.Add(new ValidationFailure("", "Unable to send test message")); + } + + return new ValidationResult(failures); + } + } +} diff --git a/src/NzbDrone.Core/Notifications/SendGrid/SendGridException.cs b/src/NzbDrone.Core/Notifications/SendGrid/SendGridException.cs new file mode 100644 index 000000000..bf230a736 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/SendGrid/SendGridException.cs @@ -0,0 +1,18 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Notifications.SendGrid +{ + public class SendGridException : NzbDroneException + { + public SendGridException(string message) + : base(message) + { + } + + public SendGridException(string message, Exception innerException, params object[] args) + : base(message, innerException, args) + { + } + } +} diff --git a/src/NzbDrone.Core/Notifications/SendGrid/SendGridPayload.cs b/src/NzbDrone.Core/Notifications/SendGrid/SendGridPayload.cs new file mode 100644 index 000000000..3a0d9ed13 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/SendGrid/SendGridPayload.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.Notifications.SendGrid +{ + public class SendGridPayload + { + public SendGridPayload() + { + Personalizations = new List(); + Content = new List(); + } + + public List Content { get; set; } + public SendGridEmail From { get; set; } + public List Personalizations { get; set; } + } + + public class SendGridContent + { + public string Type { get; set; } + public string Value { get; set; } + } + + public class SendGridEmail + { + public string Email { get; set; } + } + + public class SendGridPersonalization + { + public SendGridPersonalization() + { + To = new List(); + } + + public List To { get; set; } + public string Subject { get; set; } + } + + public class SendGridSenderResponse + { + public List Result { get; set; } + } + + public class SendGridSender + { + public SendGridEmail From { get; set; } + public string Nickname { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/SendGrid/SendGridProxy.cs b/src/NzbDrone.Core/Notifications/SendGrid/SendGridProxy.cs new file mode 100644 index 000000000..0db56b4c9 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/SendGrid/SendGridProxy.cs @@ -0,0 +1,83 @@ +using System.Net; +using NzbDrone.Common.Http; +using NzbDrone.Common.Serializer; + +namespace NzbDrone.Core.Notifications.SendGrid +{ + public interface ISendGridProxy + { + void SendNotification(string title, string message, SendGridSettings settings); + } + + public class SendGridProxy : ISendGridProxy + { + private readonly IHttpClient _httpClient; + + public SendGridProxy(IHttpClient httpClient) + { + _httpClient = httpClient; + } + + public void SendNotification(string title, string message, SendGridSettings settings) + { + try + { + var request = BuildRequest(settings, "mail/send", HttpMethod.POST); + + var payload = new SendGridPayload + { + From = new SendGridEmail + { + Email = settings.From + } + }; + + payload.Content.Add(new SendGridContent + { + Type = "text/plain", + Value = message + }); + + var personalization = new SendGridPersonalization + { + Subject = title, + }; + + foreach (var recipient in settings.Recipients) + { + personalization.To.Add(new SendGridEmail + { + Email = recipient + }); + } + + payload.Personalizations.Add(personalization); + + request.SetContent(payload.ToJson()); + + _httpClient.Execute(request); + } + catch (HttpException ex) + { + if (ex.Response.StatusCode == HttpStatusCode.Unauthorized) + { + throw new SendGridException("Unauthorized - AuthToken is invalid"); + } + + throw new SendGridException("Unable to connect to SendGrid. Status Code: {0}", ex); + } + } + + private HttpRequest BuildRequest(SendGridSettings settings, string resource, HttpMethod method) + { + var request = new HttpRequestBuilder(settings.BaseUrl).Resource(resource) + .SetHeader("Authorization", $"Bearer {settings.ApiKey}") + .Build(); + + request.Headers.ContentType = "application/json"; + request.Method = method; + + return request; + } + } +} diff --git a/src/NzbDrone.Core/Notifications/SendGrid/SendGridSettings.cs b/src/NzbDrone.Core/Notifications/SendGrid/SendGridSettings.cs new file mode 100644 index 000000000..25b78b9c4 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/SendGrid/SendGridSettings.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Notifications.SendGrid +{ + public class SendGridSettingsValidator : AbstractValidator + { + public SendGridSettingsValidator() + { + RuleFor(c => c.ApiKey).NotEmpty(); + RuleFor(c => c.From).NotEmpty().EmailAddress(); + RuleFor(c => c.Recipients).NotEmpty(); + RuleForEach(c => c.Recipients).NotEmpty().EmailAddress(); + } + } + + public class SendGridSettings : IProviderConfig + { + private static readonly SendGridSettingsValidator Validator = new SendGridSettingsValidator(); + + public SendGridSettings() + { + BaseUrl = "https://api.sendgrid.com/v3/"; + Recipients = new string[] { }; + } + + public string BaseUrl { get; set; } + + [FieldDefinition(1, Label = "API Key", HelpText = "The API Key generated by SendGrid", HelpLink = "https://sendgrid.com/docs/ui/account-and-settings/api-keys/#creating-an-api-key")] + public string ApiKey { get; set; } + + [FieldDefinition(2, Label = "From Address")] + public string From { get; set; } + + [FieldDefinition(3, Label = "Recipient Address(es)", Type = FieldType.Tag)] + public IEnumerable Recipients { get; set; } + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +}