diff --git a/src/NzbDrone.Core/Notifications/Telegram/InvalidResponseException.cs b/src/NzbDrone.Core/Notifications/Telegram/InvalidResponseException.cs new file mode 100644 index 000000000..137e7322b --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Telegram/InvalidResponseException.cs @@ -0,0 +1,15 @@ +using System; + +namespace NzbDrone.Core.Notifications.Telegram +{ + public class InvalidResponseException : Exception + { + public InvalidResponseException() + { + } + + public InvalidResponseException(string message) : base(message) + { + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs b/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs new file mode 100644 index 000000000..837599d76 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using FluentValidation.Results; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Tv; + +namespace NzbDrone.Core.Notifications.Telegram +{ + public class Telegram : NotificationBase + { + private readonly ITelegramProxy _proxy; + + public Telegram(ITelegramProxy proxy) + { + _proxy = proxy; + } + + public override string Link + { + get { return "https://telegram.org/"; } + } + + public override void OnGrab(GrabMessage grabMessage) + { + const string title = "Episode Grabbed"; + + _proxy.SendNotification(title, grabMessage.Message, Settings); + } + + public override void OnDownload(DownloadMessage message) + { + const string title = "Episode Downloaded"; + + _proxy.SendNotification(title, message.Message, Settings); + } + + public override void OnRename(Series series) + { + } + + public override string Name + { + get + { + return "Telegram"; + } + } + + public override bool SupportsOnRename + { + get + { + return false; + } + } + + public override ValidationResult Test() + { + var failures = new List(); + + failures.AddIfNotNull(_proxy.Test(Settings)); + + return new ValidationResult(failures); + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Telegram/TelegramError.cs b/src/NzbDrone.Core/Notifications/Telegram/TelegramError.cs new file mode 100644 index 000000000..313c0ebc1 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Telegram/TelegramError.cs @@ -0,0 +1,14 @@ +using Newtonsoft.Json; + +namespace NzbDrone.Core.Notifications.Telegram +{ + public class TelegramError + { + public bool Ok { get; set; } + + [JsonProperty(PropertyName = "error_code")] + public int ErrorCode { get; set; } + + public string Description { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/Telegram/TelegramService.cs b/src/NzbDrone.Core/Notifications/Telegram/TelegramService.cs new file mode 100644 index 000000000..e7259d753 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Telegram/TelegramService.cs @@ -0,0 +1,72 @@ +using System; +using System.Net; +using FluentValidation.Results; +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Serializer; +using RestSharp; +using NzbDrone.Core.Rest; + +namespace NzbDrone.Core.Notifications.Telegram +{ + public interface ITelegramProxy + { + void SendNotification(string title, string message, TelegramSettings settings); + ValidationFailure Test(TelegramSettings settings); + } + + public class TelegramProxy : ITelegramProxy + { + private readonly Logger _logger; + private const string URL = "https://api.telegram.org"; + + public TelegramProxy(Logger logger) + { + _logger = logger; + } + + public void SendNotification(string title, string message, TelegramSettings settings) + { + //Format text to add the title before and bold using markdown + var text = $"*{title}*\n{message}"; + var client = RestClientFactory.BuildClient(URL); + var request = new RestRequest("bot{token}/sendmessage", Method.POST); + + request.AddUrlSegment("token", settings.BotToken); + request.AddParameter("chat_id", settings.ChatId); + request.AddParameter("parse_mode", "Markdown"); + request.AddParameter("text", text); + + client.ExecuteAndValidate(request); + } + + public ValidationFailure Test(TelegramSettings settings) + { + try + { + const string title = "Test Notification"; + const string body = "This is a test message from Sonarr"; + + SendNotification(title, body, settings); + } + catch (Exception ex) + { + _logger.Error(ex, "Unable to send test message: " + ex.Message); + + var restException = ex as RestException; + + if (restException != null && restException.Response.StatusCode == HttpStatusCode.BadRequest) + { + var error = Json.Deserialize(restException.Response.Content); + var property = error.Description.ContainsIgnoreCase("chat not found") ? "ChatId" : "BotToken"; + + return new ValidationFailure(property, error.Description); + } + + return new ValidationFailure("BotToken", "Unable to send test message"); + } + + return null; + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Telegram/TelegramSettings.cs b/src/NzbDrone.Core/Notifications/Telegram/TelegramSettings.cs new file mode 100644 index 000000000..dcd1559ee --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Telegram/TelegramSettings.cs @@ -0,0 +1,40 @@ +using FluentValidation; +using NzbDrone.Core.Annotations; +using NzbDrone.Core.ThingiProvider; +using NzbDrone.Core.Validation; + +namespace NzbDrone.Core.Notifications.Telegram +{ + public class TelegramSettingsValidator : AbstractValidator + { + public TelegramSettingsValidator() + { + RuleFor(c => c.BotToken).NotEmpty(); + RuleFor(c => c.ChatId).NotEmpty(); + } + } + + public class TelegramSettings : IProviderConfig + { + private static readonly TelegramSettingsValidator Validator = new TelegramSettingsValidator(); + + [FieldDefinition(0, Label = "Bot Token", HelpLink = "https://core.telegram.org/bots")] + public string BotToken { get; set; } + + [FieldDefinition(1, Label = "Chat ID", HelpLink = "http://stackoverflow.com/a/37396871/882971", HelpText = "You must start a conversation with the bot or add it to your group to receive messages")] + public string ChatId { get; set; } + + public bool IsValid + { + get + { + return !string.IsNullOrWhiteSpace(ChatId) && !string.IsNullOrWhiteSpace(BotToken); + } + } + + 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 4564e0df8..e24705b25 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -842,6 +842,10 @@ + + + + @@ -1173,6 +1177,7 @@ libsqlite3.0.dylib PreserveNewest +