diff --git a/src/NzbDrone.Core/Notifications/Slack/Payloads/Attachment.cs b/src/NzbDrone.Core/Notifications/Slack/Payloads/Attachment.cs new file mode 100644 index 000000000..412f60b56 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Slack/Payloads/Attachment.cs @@ -0,0 +1,15 @@ +using Newtonsoft.Json; + +namespace NzbDrone.Core.Notifications.Slack.Payloads +{ + public class Attachment + { + public string Fallback { get; set; } + + public string Title { get; set; } + + public string Text { get; set; } + + public string Color { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/Slack/Payloads/SlackPayload.cs b/src/NzbDrone.Core/Notifications/Slack/Payloads/SlackPayload.cs new file mode 100644 index 000000000..a2c64b737 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Slack/Payloads/SlackPayload.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace NzbDrone.Core.Notifications.Slack.Payloads +{ + public class SlackPayload + { + public string Text { get; set; } + + public string Username { get; set; } + + [JsonProperty("icon_emoji")] + public string IconEmoji { get; set; } + + public List Attachments { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/Slack/Slack.cs b/src/NzbDrone.Core/Notifications/Slack/Slack.cs new file mode 100644 index 000000000..cddc98c83 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Slack/Slack.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Notifications.Slack.Payloads; +using NzbDrone.Core.Rest; +using NzbDrone.Core.Tv; +using NzbDrone.Core.Validation; +using RestSharp; + + +namespace NzbDrone.Core.Notifications.Slack +{ + public class Slack : NotificationBase + { + private readonly Logger _logger; + + public Slack(Logger logger) + { + _logger = logger; + } + + public override string Name + { + get + { + return "Slack"; + } + } + + public override string Link + { + get { return "https://my.slack.com/services/new/incoming-webhook/"; } + } + + public override void OnGrab(GrabMessage message) + { + var payload = new SlackPayload + { + IconEmoji = Settings.Icon, + Username = Settings.Username, + Text = "Grabbed", + Attachments = new List + { + new Attachment + { + Fallback = message.Message, + Title = message.Series.Title, + Text = message.Message, + Color = "warning" + } + } + }; + + NotifySlack(payload); + } + + public override void OnDownload(DownloadMessage message) + { + var payload = new SlackPayload + { + IconEmoji = Settings.Icon, + Username = Settings.Username, + Text = "Downloaded", + Attachments = new List + { + new Attachment + { + Fallback = message.Message, + Title = message.Series.Title, + Text = message.Message, + Color = "good" + } + } + }; + + NotifySlack(payload); + } + + public override void OnRename(Series series) + { + var payload = new SlackPayload + { + IconEmoji = Settings.Icon, + Username = Settings.Username, + Text = "Renamed", + Attachments = new List + { + new Attachment + { + Title = series.Title, + } + } + }; + + NotifySlack(payload); + } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(TestMessage()); + + return new ValidationResult(failures); + } + + public ValidationFailure TestMessage() + { + try + { + var message = $"Test message from Sonarr posted at {DateTime.Now}"; + var payload = new SlackPayload + { + IconEmoji = Settings.Icon, + Username = Settings.Username, + Text = message + }; + + NotifySlack(payload); + + } + catch (SlackExeption ex) + { + return new NzbDroneValidationFailure("Unable to post", ex.Message); + } + + return null; + } + + private void NotifySlack(SlackPayload payload) + { + try + { + var client = RestClientFactory.BuildClient(Settings.WebHookUrl); + var request = new RestRequest(Method.POST) + { + RequestFormat = DataFormat.Json, + JsonSerializer = new JsonNetSerializer() + }; + request.AddBody(payload); + client.ExecuteAndValidate(request); + } + catch (RestException ex) + { + _logger.Error(ex, "Unable to post payload {0}", payload); + throw new SlackExeption("Unable to post payload", ex); + } + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Slack/SlackExeption.cs b/src/NzbDrone.Core/Notifications/Slack/SlackExeption.cs new file mode 100644 index 000000000..1d7fd9b85 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Slack/SlackExeption.cs @@ -0,0 +1,16 @@ +using System; +using NzbDrone.Common.Exceptions; + +namespace NzbDrone.Core.Notifications.Slack +{ + class SlackExeption : NzbDroneException + { + public SlackExeption(string message) : base(message) + { + } + + public SlackExeption(string message, Exception innerException, params object[] args) : base(message, innerException, args) + { + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Slack/SlackSettings.cs b/src/NzbDrone.Core/Notifications/Slack/SlackSettings.cs new file mode 100644 index 000000000..e92d37504 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Slack/SlackSettings.cs @@ -0,0 +1,36 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Notifications.Slack +{ + public class SlackSettingsValidator : AbstractValidator + { + public SlackSettingsValidator() + { + RuleFor(c => c.WebHookUrl).IsValidUrl(); + RuleFor(c => c.Username).NotEmpty(); + RuleFor(c => c.Icon).NotEmpty(); + } + } + + public class SlackSettings : IProviderConfig + { + private static readonly SlackSettingsValidator Validator = new SlackSettingsValidator(); + + [FieldDefinition(0, Label = "Webhook URL", HelpText = "Slack channel webhook url", Type = FieldType.Url, HelpLink = "https://my.slack.com/services/new/incoming-webhook/")] + public string WebHookUrl { get; set; } + + [FieldDefinition(1, Label = "Username", HelpText = "Choose the username that this integration will post as", Type = FieldType.Textbox)] + public string Username { get; set; } + + [FieldDefinition(2, Label = "Icon", HelpText = "Change the icon that is used for messages from this integration", Type = FieldType.Textbox, HelpLink = "http://www.emoji-cheat-sheet.com/")] + public string Icon { get; set; } + + public NzbDroneValidationResult Validate() + { + return new NzbDroneValidationResult(Validator.Validate(this)); + } + } +} diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 672a17623..9d5c545ac 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -793,6 +793,11 @@ + + + + +