From b801aa093595a33a55e9edd5cda141c4c55a9bb5 Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Tue, 30 Jul 2024 21:25:48 -0700 Subject: [PATCH] New: Add metadata links to telegram messages Co-authored-by: Ivar Stangeby Fixed errors sending Telegram notifications when links aren't available (cherry picked from commit 4eab168267db716a9e897a992e3a7f6889571f9f) (cherry picked from commit 4d7a3d0909437268b4ad0a0dbeb59d45b4435118) Closes #10242 Closes #10489 --- src/NzbDrone.Core/Localization/Core/en.json | 2 + .../Notifications/Telegram/Telegram.cs | 55 ++++++++++++++++--- .../Notifications/Telegram/TelegramLink.cs | 14 +++++ .../Notifications/Telegram/TelegramProxy.cs | 23 ++++++-- .../Telegram/TelegramSettings.cs | 21 +++++++ 5 files changed, 101 insertions(+), 14 deletions(-) create mode 100644 src/NzbDrone.Core/Notifications/Telegram/TelegramLink.cs diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index 2f646215e..3a55611ff 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -1236,6 +1236,8 @@ "NotificationsTelegramSettingsIncludeAppNameHelpText": "Optionally prefix message title with {appName} to differentiate notifications from different applications", "NotificationsTelegramSettingsIncludeInstanceName": "Include Instance Name in Title", "NotificationsTelegramSettingsIncludeInstanceNameHelpText": "Optionally include Instance name in notification", + "NotificationsTelegramSettingsMetadataLinks": "Metadata Links", + "NotificationsTelegramSettingsMetadataLinksMovieHelpText": "Add links to movie metadata when sending notifications", "NotificationsTelegramSettingsSendSilently": "Send Silently", "NotificationsTelegramSettingsSendSilentlyHelpText": "Sends the message silently. Users will receive a notification with no sound", "NotificationsTelegramSettingsTopicId": "Topic ID", diff --git a/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs b/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs index 71608aebf..769efd06c 100644 --- a/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs +++ b/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs @@ -27,8 +27,9 @@ public override void OnGrab(GrabMessage grabMessage) { var title = Settings.IncludeAppNameInTitle ? MOVIE_GRABBED_TITLE_BRANDED : MOVIE_GRABBED_TITLE; title = Settings.IncludeInstanceNameInTitle ? $"{title} - {InstanceName}" : title; + var links = GetLinks(grabMessage.Movie); - _proxy.SendNotification(title, grabMessage.Message, Settings); + _proxy.SendNotification(title, grabMessage.Message, links, Settings); } public override void OnDownload(DownloadMessage message) @@ -44,32 +45,36 @@ public override void OnDownload(DownloadMessage message) } title = Settings.IncludeInstanceNameInTitle ? $"{title} - {InstanceName}" : title; + var links = GetLinks(message.Movie); - _proxy.SendNotification(title, message.Message, Settings); + _proxy.SendNotification(title, message.Message, links, Settings); } public override void OnMovieAdded(Movie movie) { var title = Settings.IncludeAppNameInTitle ? MOVIE_ADDED_TITLE_BRANDED : MOVIE_ADDED_TITLE; title = Settings.IncludeInstanceNameInTitle ? $"{title} - {InstanceName}" : title; + var links = GetLinks(movie); - _proxy.SendNotification(title, $"{movie.Title} added to library", Settings); + _proxy.SendNotification(title, $"{movie.Title} added to library", links, Settings); } public override void OnMovieFileDelete(MovieFileDeleteMessage deleteMessage) { var title = Settings.IncludeAppNameInTitle ? MOVIE_FILE_DELETED_TITLE_BRANDED : MOVIE_FILE_DELETED_TITLE; title = Settings.IncludeInstanceNameInTitle ? $"{title} - {InstanceName}" : title; + var links = GetLinks(deleteMessage.Movie); - _proxy.SendNotification(title, deleteMessage.Message, Settings); + _proxy.SendNotification(title, deleteMessage.Message, links, Settings); } public override void OnMovieDelete(MovieDeleteMessage deleteMessage) { var title = Settings.IncludeAppNameInTitle ? MOVIE_DELETED_TITLE_BRANDED : MOVIE_DELETED_TITLE; title = Settings.IncludeInstanceNameInTitle ? $"{title} - {InstanceName}" : title; + var links = GetLinks(deleteMessage.Movie); - _proxy.SendNotification(title, deleteMessage.Message, Settings); + _proxy.SendNotification(title, deleteMessage.Message, links, Settings); } public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) @@ -77,7 +82,7 @@ public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck) var title = Settings.IncludeAppNameInTitle ? HEALTH_ISSUE_TITLE_BRANDED : HEALTH_ISSUE_TITLE; title = Settings.IncludeInstanceNameInTitle ? $"{title} - {InstanceName}" : title; - _proxy.SendNotification(title, healthCheck.Message, Settings); + _proxy.SendNotification(title, healthCheck.Message, new List(), Settings); } public override void OnHealthRestored(HealthCheck.HealthCheck previousCheck) @@ -85,7 +90,7 @@ public override void OnHealthRestored(HealthCheck.HealthCheck previousCheck) var title = Settings.IncludeAppNameInTitle ? HEALTH_RESTORED_TITLE_BRANDED : HEALTH_RESTORED_TITLE; title = Settings.IncludeInstanceNameInTitle ? $"{title} - {InstanceName}" : title; - _proxy.SendNotification(title, $"The following issue is now resolved: {previousCheck.Message}", Settings); + _proxy.SendNotification(title, $"The following issue is now resolved: {previousCheck.Message}", new List(), Settings); } public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage) @@ -93,7 +98,7 @@ public override void OnApplicationUpdate(ApplicationUpdateMessage updateMessage) var title = Settings.IncludeAppNameInTitle ? APPLICATION_UPDATE_TITLE_BRANDED : APPLICATION_UPDATE_TITLE; title = Settings.IncludeInstanceNameInTitle ? $"{title} - {InstanceName}" : title; - _proxy.SendNotification(title, updateMessage.Message, Settings); + _proxy.SendNotification(title, updateMessage.Message, new List(), Settings); } public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) @@ -101,7 +106,7 @@ public override void OnManualInteractionRequired(ManualInteractionRequiredMessag var title = Settings.IncludeAppNameInTitle ? MANUAL_INTERACTION_REQUIRED_TITLE_BRANDED : MANUAL_INTERACTION_REQUIRED_TITLE; title = Settings.IncludeInstanceNameInTitle ? $"{title} - {InstanceName}" : title; - _proxy.SendNotification(title, message.Message, Settings); + _proxy.SendNotification(title, message.Message, new List(), Settings); } public override ValidationResult Test() @@ -112,5 +117,37 @@ public override ValidationResult Test() return new ValidationResult(failures); } + + private List GetLinks(Movie movie) + { + var links = new List(); + + if (movie == null) + { + return links; + } + + foreach (var link in Settings.MetadataLinks) + { + var linkType = (MetadataLinkType)link; + + if (linkType == MetadataLinkType.Tmdb && movie.TmdbId > 0) + { + links.Add(new TelegramLink("TMDb", $"https://www.themoviedb.org/movie/{movie.TmdbId}")); + } + + if (linkType == MetadataLinkType.Imdb && movie.ImdbId.IsNotNullOrWhiteSpace()) + { + links.Add(new TelegramLink("IMDb", $"https://www.imdb.com/title/{movie.ImdbId}")); + } + + if (linkType == MetadataLinkType.Trakt && movie.TmdbId > 0) + { + links.Add(new TelegramLink("Trakt", $"https://trakt.tv/search/tmdb/{movie.TmdbId}?id_type=movie")); + } + } + + return links; + } } } diff --git a/src/NzbDrone.Core/Notifications/Telegram/TelegramLink.cs b/src/NzbDrone.Core/Notifications/Telegram/TelegramLink.cs new file mode 100644 index 000000000..ac131b483 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Telegram/TelegramLink.cs @@ -0,0 +1,14 @@ +namespace NzbDrone.Core.Notifications.Telegram +{ + public class TelegramLink + { + public string Label { get; set; } + public string Link { get; set; } + + public TelegramLink(string label, string link) + { + Label = label; + Link = link; + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Telegram/TelegramProxy.cs b/src/NzbDrone.Core/Notifications/Telegram/TelegramProxy.cs index 2edb9110f..ef5bc5dcf 100644 --- a/src/NzbDrone.Core/Notifications/Telegram/TelegramProxy.cs +++ b/src/NzbDrone.Core/Notifications/Telegram/TelegramProxy.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Text; using System.Web; using FluentValidation.Results; using NLog; @@ -14,7 +15,7 @@ namespace NzbDrone.Core.Notifications.Telegram { public interface ITelegramProxy { - void SendNotification(string title, string message, TelegramSettings settings); + void SendNotification(string title, string message, List links, TelegramSettings settings); ValidationFailure Test(TelegramSettings settings); } @@ -34,10 +35,16 @@ public TelegramProxy(IHttpClient httpClient, IConfigFileProvider configFileProvi _logger = logger; } - public void SendNotification(string title, string message, TelegramSettings settings) + public void SendNotification(string title, string message, List links, TelegramSettings settings) { - // Format text to add the title before and bold using markdown - var text = $"{HttpUtility.HtmlEncode(title)}\n{HttpUtility.HtmlEncode(message)}"; + var text = new StringBuilder($"{HttpUtility.HtmlEncode(title)}\n"); + + text.AppendLine(HttpUtility.HtmlEncode(message)); + + foreach (var link in links) + { + text.AppendLine($"{HttpUtility.HtmlEncode(link.Label)}"); + } var requestBuilder = new HttpRequestBuilder(URL).Resource("bot{token}/sendmessage").Post(); @@ -60,9 +67,15 @@ public ValidationFailure Test(TelegramSettings settings) const string title = "Test Notification"; const string body = "This is a test message from Radarr"; + var links = new List + { + new ("Radarr.video", "https://radarr.video") + }; + var testMessageTitle = settings.IncludeAppNameInTitle ? brandedTitle : title; testMessageTitle = settings.IncludeInstanceNameInTitle ? $"{testMessageTitle} - {_configFileProvider.InstanceName}" : testMessageTitle; - SendNotification(testMessageTitle, body, settings); + + SendNotification(testMessageTitle, body, links, settings); } catch (Exception ex) { diff --git a/src/NzbDrone.Core/Notifications/Telegram/TelegramSettings.cs b/src/NzbDrone.Core/Notifications/Telegram/TelegramSettings.cs index 270163513..b87b5a9d8 100644 --- a/src/NzbDrone.Core/Notifications/Telegram/TelegramSettings.cs +++ b/src/NzbDrone.Core/Notifications/Telegram/TelegramSettings.cs @@ -1,3 +1,6 @@ +using System; +using System.Collections.Generic; +using System.Linq; using FluentValidation; using NzbDrone.Core.Annotations; using NzbDrone.Core.Validation; @@ -12,6 +15,16 @@ public TelegramSettingsValidator() RuleFor(c => c.ChatId).NotEmpty(); RuleFor(c => c.TopicId).Must(topicId => !topicId.HasValue || topicId > 1) .WithMessage("Topic ID must be greater than 1 or empty"); + RuleFor(c => c.MetadataLinks).Custom((links, context) => + { + foreach (var link in links) + { + if (!Enum.IsDefined(typeof(MetadataLinkType), link)) + { + context.AddFailure("MetadataLinks", $"MetadataLink is not valid: {link}"); + } + } + }); } } @@ -19,6 +32,11 @@ public class TelegramSettings : NotificationSettingsBase { private static readonly TelegramSettingsValidator Validator = new (); + public TelegramSettings() + { + MetadataLinks = Enumerable.Empty(); + } + [FieldDefinition(0, Label = "NotificationsTelegramSettingsBotToken", Privacy = PrivacyLevel.ApiKey, HelpLink = "https://core.telegram.org/bots")] public string BotToken { get; set; } @@ -37,6 +55,9 @@ public class TelegramSettings : NotificationSettingsBase [FieldDefinition(5, Label = "NotificationsTelegramSettingsIncludeInstanceName", Type = FieldType.Checkbox, HelpText = "NotificationsTelegramSettingsIncludeInstanceNameHelpText", Advanced = true)] public bool IncludeInstanceNameInTitle { get; set; } + [FieldDefinition(6, Label = "NotificationsTelegramSettingsMetadataLinks", Type = FieldType.Select, SelectOptions = typeof(MetadataLinkType), HelpText = "NotificationsTelegramSettingsMetadataLinksMovieHelpText")] + public IEnumerable MetadataLinks { get; set; } + public override NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this));