New: Option for Telegram link previews

Closes #7500
v5-develop
Mark McDowall 4 days ago
parent 31e02bdead
commit 094df71301

@ -0,0 +1,218 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Notifications.Telegram;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class telegram_link_previewFixture : MigrationTest<telegram_link_preview>
{
[Test]
public void should_set_link_preview_to_none_when_no_metadata_links()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
OnGrab = true,
OnDownload = true,
OnUpgrade = true,
OnHealthIssue = true,
IncludeHealthWarnings = true,
OnRename = true,
Name = "Telegram Sonarr",
Implementation = "Telegram",
Tags = "[]",
Settings = new TelegramSettings217
{
BotToken = "secret",
ChatId = "12345",
SendSilently = false,
IncludeAppNameInTitle = false,
IncludeInstanceNameInTitle = false,
MetadataLinks = new List<int>()
}.ToJson(),
ConfigContract = "TelegramSettings"
});
});
var items = db.Query<NotificationDefinition218>("SELECT * FROM \"Notifications\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Telegram");
items.First().ConfigContract.Should().Be("TelegramSettings");
items.First().Settings.LinkPreview.Should().Be((int)MetadataLinkPreviewType.None);
}
[Test]
public void should_set_link_preview_to_first_metadata_link_when_not_tvdb()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
OnGrab = true,
OnDownload = true,
OnUpgrade = true,
OnHealthIssue = true,
IncludeHealthWarnings = true,
OnRename = true,
Name = "Telegram Sonarr",
Implementation = "Telegram",
Tags = "[]",
Settings = new TelegramSettings217
{
BotToken = "secret",
ChatId = "12345",
SendSilently = false,
IncludeAppNameInTitle = false,
IncludeInstanceNameInTitle = false,
MetadataLinks = new List<int> { 0, 1 }
}.ToJson(),
ConfigContract = "TelegramSettings"
});
});
var items = db.Query<NotificationDefinition218>("SELECT * FROM \"Notifications\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Telegram");
items.First().ConfigContract.Should().Be("TelegramSettings");
items.First().Settings.LinkPreview.Should().Be((int)MetadataLinkPreviewType.Imdb);
}
[Test]
public void should_set_link_preview_to_first_metadata_link_that_is_not_tvdb()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
OnGrab = true,
OnDownload = true,
OnUpgrade = true,
OnHealthIssue = true,
IncludeHealthWarnings = true,
OnRename = true,
Name = "Telegram Sonarr",
Implementation = "Telegram",
Tags = "[]",
Settings = new TelegramSettings217
{
BotToken = "secret",
ChatId = "12345",
SendSilently = false,
IncludeAppNameInTitle = false,
IncludeInstanceNameInTitle = false,
MetadataLinks = new List<int> { 1, 2 }
}.ToJson(),
ConfigContract = "TelegramSettings"
});
});
var items = db.Query<NotificationDefinition218>("SELECT * FROM \"Notifications\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Telegram");
items.First().ConfigContract.Should().Be("TelegramSettings");
items.First().Settings.LinkPreview.Should().Be((int)MetadataLinkPreviewType.Tvmaze);
}
[Test]
public void should_set_link_preview_to_none_when_only_metadata_link_is_tvdb()
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Notifications").Row(new
{
OnGrab = true,
OnDownload = true,
OnUpgrade = true,
OnHealthIssue = true,
IncludeHealthWarnings = true,
OnRename = true,
Name = "Telegram Sonarr",
Implementation = "Telegram",
Tags = "[]",
Settings = new TelegramSettings217
{
BotToken = "secret",
ChatId = "12345",
SendSilently = false,
IncludeAppNameInTitle = false,
IncludeInstanceNameInTitle = false,
MetadataLinks = new List<int> { 1 }
}.ToJson(),
ConfigContract = "TelegramSettings"
});
});
var items = db.Query<NotificationDefinition218>("SELECT * FROM \"Notifications\"");
items.Should().HaveCount(1);
items.First().Implementation.Should().Be("Telegram");
items.First().ConfigContract.Should().Be("TelegramSettings");
items.First().Settings.LinkPreview.Should().Be((int)MetadataLinkPreviewType.None);
}
}
public class NotificationDefinition218
{
public int Id { get; set; }
public string Implementation { get; set; }
public string ConfigContract { get; set; }
public TelegramSettings218 Settings { get; set; }
public string Name { get; set; }
public bool OnGrab { get; set; }
public bool OnDownload { get; set; }
public bool OnUpgrade { get; set; }
public bool OnRename { get; set; }
public bool OnSeriesDelete { get; set; }
public bool OnEpisodeFileDelete { get; set; }
public bool OnEpisodeFileDeleteForUpgrade { get; set; }
public bool OnHealthIssue { get; set; }
public bool OnApplicationUpdate { get; set; }
public bool OnManualInteractionRequired { get; set; }
public bool OnSeriesAdd { get; set; }
public bool OnHealthRestored { get; set; }
public bool OnImportComplete { get; set; }
public bool SupportsOnGrab { get; set; }
public bool SupportsOnDownload { get; set; }
public bool SupportsOnUpgrade { get; set; }
public bool SupportsOnRename { get; set; }
public bool SupportsOnSeriesDelete { get; set; }
public bool SupportsOnEpisodeFileDelete { get; set; }
public bool SupportsOnEpisodeFileDeleteForUpgrade { get; set; }
public bool SupportsOnHealthIssue { get; set; }
public bool IncludeHealthWarnings { get; set; }
public List<int> Tags { get; set; }
}
public class TelegramSettings217
{
public string BotToken { get; set; }
public string ChatId { get; set; }
public int? TopicId { get; set; }
public bool SendSilently { get; set; }
public bool IncludeAppNameInTitle { get; set; }
public bool IncludeInstanceNameInTitle { get; set; }
public IEnumerable<int> MetadataLinks { get; set; }
}
public class TelegramSettings218
{
public string BotToken { get; set; }
public string ChatId { get; set; }
public int? TopicId { get; set; }
public bool SendSilently { get; set; }
public bool IncludeAppNameInTitle { get; set; }
public bool IncludeInstanceNameInTitle { get; set; }
public IEnumerable<int> MetadataLinks { get; set; }
public int LinkPreview { get; set; }
}
}

@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Dapper;
using FluentMigrator;
using Newtonsoft.Json.Linq;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(218)]
public class telegram_link_preview : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(ChangeEncryption);
}
private void ChangeEncryption(IDbConnection conn, IDbTransaction tran)
{
var updated = new List<object>();
using (var cmd = conn.CreateCommand())
{
cmd.Transaction = tran;
cmd.CommandText = "SELECT \"Id\", \"Settings\" FROM \"Notifications\" WHERE \"Implementation\" = 'Telegram'";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var id = reader.GetInt32(0);
var settings = Json.Deserialize<JObject>(reader.GetString(1));
var metadataLinks = settings["metadataLinks"] as JArray;
if (metadataLinks == null)
{
settings["linkPreview"] = -1;
}
else
{
settings["linkPreview"] = metadataLinks.FirstOrDefault(l => l.Value<int?>() != 1) ?? -1;
}
updated.Add(new
{
Settings = settings.ToJson(),
Id = id
});
}
}
}
var updateSql = "UPDATE \"Notifications\" SET \"Settings\" = @Settings WHERE \"Id\" = @Id";
conn.Execute(updateSql, updated, transaction: tran);
}
}
}

@ -1469,6 +1469,8 @@
"NotificationsTelegramSettingsBotToken": "Bot Token",
"NotificationsTelegramSettingsChatId": "Chat ID",
"NotificationsTelegramSettingsChatIdHelpText": "You must start a conversation with the bot or add it to your group to receive messages",
"NotificationsTelegramSettingsLinkPreview": "Link Preview",
"NotificationsTelegramSettingsLinkPreviewHelpText": "Determines which link will be previewed in the Telegram notification. Choose 'None' to disable",
"NotificationsTelegramSettingsIncludeAppName": "Include {appName} in Title",
"NotificationsTelegramSettingsIncludeAppNameHelpText": "Optionally prefix message title with {appName} to differentiate notifications from different applications",
"NotificationsTelegramSettingsIncludeInstanceName": "Include Instance Name in Title",

@ -0,0 +1,25 @@
using NzbDrone.Core.Annotations;
namespace NzbDrone.Core.Notifications.Telegram
{
// Maintain the same values as MetadataLinkType
public enum MetadataLinkPreviewType
{
[FieldOption(Label = "None")]
None = -1,
[FieldOption(Label = "IMDb")]
Imdb = 0,
// No preview data is supported for TheTVDB at this time
// [FieldOption(Label = "TVDb")]
// Tvdb = 1,
[FieldOption(Label = "TVMaze")]
Tvmaze = 2,
[FieldOption(Label = "Trakt")]
Trakt = 3
}
}

@ -133,22 +133,22 @@ namespace NzbDrone.Core.Notifications.Telegram
if (linkType == MetadataLinkType.Imdb && series.ImdbId.IsNotNullOrWhiteSpace())
{
links.Add(new TelegramLink("IMDb", $"https://www.imdb.com/title/{series.ImdbId}"));
links.Add(new TelegramLink(MetadataLinkType.Imdb, "IMDb", $"https://www.imdb.com/title/{series.ImdbId}"));
}
if (linkType == MetadataLinkType.Tvdb && series.TvdbId > 0)
{
links.Add(new TelegramLink("TVDb", $"http://www.thetvdb.com/?tab=series&id={series.TvdbId}"));
links.Add(new TelegramLink(MetadataLinkType.Tvdb, "TVDb", $"http://www.thetvdb.com/?tab=series&id={series.TvdbId}"));
}
if (linkType == MetadataLinkType.Trakt && series.TvdbId > 0)
{
links.Add(new TelegramLink("Trakt", $"http://trakt.tv/search/tvdb/{series.TvdbId}?id_type=show"));
links.Add(new TelegramLink(MetadataLinkType.Trakt, "Trakt", $"http://trakt.tv/search/tvdb/{series.TvdbId}?id_type=show"));
}
if (linkType == MetadataLinkType.Tvmaze && series.TvMazeId > 0)
{
links.Add(new TelegramLink("TVMaze", $"http://www.tvmaze.com/shows/{series.TvMazeId}/_"));
links.Add(new TelegramLink(MetadataLinkType.Tvmaze, "TVMaze", $"http://www.tvmaze.com/shows/{series.TvMazeId}/_"));
}
}

@ -2,11 +2,13 @@ namespace NzbDrone.Core.Notifications.Telegram
{
public class TelegramLink
{
public MetadataLinkType? Type { get; set; }
public string Label { get; set; }
public string Link { get; set; }
public TelegramLink(string label, string link)
public TelegramLink(MetadataLinkType? type, string label, string link)
{
Type = type;
Label = label;
Link = link;
}

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
namespace NzbDrone.Core.Notifications.Telegram;
public class TelegramLinkPreviewOptions
{
[JsonProperty("is_disabled")]
public bool IsDisabled { get; set; }
[JsonProperty("url")]
public string Url { get; set; }
public TelegramLinkPreviewOptions(List<TelegramLink> links, TelegramSettings settings)
{
IsDisabled = (MetadataLinkPreviewType)settings.LinkPreview == MetadataLinkPreviewType.None;
Url = links.FirstOrDefault(l => l.Type.HasValue && (int)l.Type.Value == settings.LinkPreview)?.Link;
}
}

@ -0,0 +1,22 @@
using Newtonsoft.Json;
namespace NzbDrone.Core.Notifications.Telegram;
public class TelegramPayload
{
[JsonProperty("chat_id")]
public string ChatId { get; set; }
[JsonProperty("parse_mode")]
public string ParseMode = "HTML";
public string Text { get; set; }
[JsonProperty("disable_notification")]
public bool DisableNotification { get; set; }
[JsonProperty("message_thread_id")]
public int? MessageThreadId { get; set; }
[JsonProperty("link_preview_options")]
public TelegramLinkPreviewOptions LinkPreviewOptions { get; set; }
}

@ -50,13 +50,22 @@ namespace NzbDrone.Core.Notifications.Telegram
var requestBuilder = new HttpRequestBuilder(URL).Resource("bot{token}/sendmessage").Post();
var request = requestBuilder.SetSegment("token", settings.BotToken)
.AddFormParameter("chat_id", settings.ChatId)
.AddFormParameter("parse_mode", "HTML")
.AddFormParameter("text", text)
.AddFormParameter("disable_notification", settings.SendSilently)
.AddFormParameter("message_thread_id", settings.TopicId)
.Accept(HttpAccept.Json)
.Build();
request.Headers.ContentType = "application/json";
var payload = new TelegramPayload
{
ChatId = settings.ChatId,
Text = text.ToString(),
DisableNotification = settings.SendSilently,
MessageThreadId = settings.TopicId,
LinkPreviewOptions = new TelegramLinkPreviewOptions(links, settings)
};
request.SetContent(payload.ToJson());
_httpClient.Post(request);
}
@ -70,7 +79,7 @@ namespace NzbDrone.Core.Notifications.Telegram
var links = new List<TelegramLink>
{
new TelegramLink("Sonarr.tv", "https://sonarr.tv")
new TelegramLink(null, "Sonarr.tv", "https://sonarr.tv")
};
var testMessageTitle = settings.IncludeAppNameInTitle ? brandedTitle : title;

@ -24,6 +24,20 @@ namespace NzbDrone.Core.Notifications.Telegram
}
}
});
RuleFor(c => c.LinkPreview).Custom((link, context) =>
{
if (!Enum.IsDefined(typeof(MetadataLinkPreviewType), link))
{
context.AddFailure("LinkPreview", $"Selected value is not valid: {link}");
}
});
// Ensure the select value is one of the selected metadata links
RuleFor(c => c.LinkPreview)
.Must((model, field) => model.MetadataLinks.Any(link => link == field))
.Unless(c => c.LinkPreview == (int)MetadataLinkPreviewType.None)
.WithMessage("Link Preview must be one of the selected Metadata Links");
}
}
@ -34,6 +48,7 @@ namespace NzbDrone.Core.Notifications.Telegram
public TelegramSettings()
{
MetadataLinks = Enumerable.Empty<int>();
LinkPreview = (int)MetadataLinkPreviewType.None;
}
[FieldDefinition(0, Label = "NotificationsTelegramSettingsBotToken", Privacy = PrivacyLevel.ApiKey, HelpLink = "https://core.telegram.org/bots")]
@ -57,6 +72,9 @@ namespace NzbDrone.Core.Notifications.Telegram
[FieldDefinition(6, Label = "NotificationsTelegramSettingsMetadataLinks", Type = FieldType.Select, SelectOptions = typeof(MetadataLinkType), HelpText = "NotificationsTelegramSettingsMetadataLinksHelpText")]
public IEnumerable<int> MetadataLinks { get; set; }
[FieldDefinition(7, Label = "NotificationsTelegramSettingsLinkPreview", Type = FieldType.Select, SelectOptions = typeof(MetadataLinkPreviewType), HelpText = "NotificationsTelegramSettingsLinkPreviewHelpText")]
public int LinkPreview { get; set; }
public override NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

Loading…
Cancel
Save