New: Customizable Discord Notifications

05323bd498

include #564
include #570
include #577
include #583
pull/2950/head
MxMarx 8 months ago
parent bd56643eaa
commit 6e87183d5f

@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation.Results;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Books;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Notifications.Discord.Payloads;
using NzbDrone.Core.Validation;
@ -23,34 +25,195 @@ namespace NzbDrone.Core.Notifications.Discord
public override void OnGrab(GrabMessage message)
{
var embeds = new List<Embed>
{
new Embed
{
Description = message.Message,
Title = message.Author.Name,
Text = message.Message,
Color = (int)DiscordColors.Warning
}
};
var payload = CreatePayload($"Grabbed: {message.Message}", embeds);
var author = message.Author;
var authorMetadata = message.Author.Metadata.Value;
var edition = message.RemoteBook.Books.First().Editions.Value.Single(x => x.Monitored);
var embed = new Embed
{
Author = new DiscordAuthor
{
Name = Settings.Author.IsNullOrWhiteSpace() ? Environment.MachineName : Settings.Author,
IconUrl = "https://raw.githubusercontent.com/readarr/Readarr/develop/Logo/256.png"
},
Url = $"https://www.goodreads.com/author/show/{author.ForeignAuthorId}",
Description = "Book Grabbed",
Title = GetTitle(message.Author, message.RemoteBook.Books),
Color = (int)DiscordColors.Standard,
Fields = new List<DiscordField>(),
Timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
};
if (Settings.GrabFields.Contains((int)DiscordGrabFieldType.Cover))
{
embed.Image = new DiscordImage
{
Url = edition.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Cover)?.Url
};
}
if (Settings.GrabFields.Contains((int)DiscordGrabFieldType.Poster))
{
embed.Thumbnail = new DiscordImage
{
Url = authorMetadata.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Poster)?.Url
};
}
foreach (var field in Settings.GrabFields)
{
var discordField = new DiscordField();
switch ((DiscordGrabFieldType)field)
{
case DiscordGrabFieldType.Overview:
var overview = edition.Overview ?? "";
discordField.Name = "Overview";
discordField.Value = overview.Length <= 300 ? overview : $"{overview.AsSpan(0, 300)}...";
break;
case DiscordGrabFieldType.Rating:
discordField.Name = "Rating";
discordField.Value = message.RemoteBook.Books.First().Ratings.Value.ToString();
break;
case DiscordGrabFieldType.Genres:
discordField.Name = "Genres";
discordField.Value = message.RemoteBook.Books.First().Genres.Take(5).Join(", ");
break;
case DiscordGrabFieldType.Quality:
discordField.Name = "Quality";
discordField.Inline = true;
discordField.Value = message.Quality.Quality.Name;
break;
case DiscordGrabFieldType.Group:
discordField.Name = "Group";
discordField.Value = message.RemoteBook.ParsedBookInfo.ReleaseGroup;
break;
case DiscordGrabFieldType.Size:
discordField.Name = "Size";
discordField.Value = BytesToString(message.RemoteBook.Release.Size);
discordField.Inline = true;
break;
case DiscordGrabFieldType.Release:
discordField.Name = "Release";
discordField.Value = string.Format("```{0}```", message.RemoteBook.Release.Title);
break;
case DiscordGrabFieldType.Links:
discordField.Name = "Links";
discordField.Value = GetLinksString(edition);
break;
case DiscordGrabFieldType.CustomFormats:
discordField.Name = "Custom Formats";
discordField.Value = string.Join("|", message.RemoteBook.CustomFormats);
break;
case DiscordGrabFieldType.CustomFormatScore:
discordField.Name = "Custom Format Score";
discordField.Value = message.RemoteBook.CustomFormatScore.ToString();
break;
case DiscordGrabFieldType.Indexer:
discordField.Name = "Indexer";
discordField.Value = message.RemoteBook.Release.Indexer;
break;
}
if (discordField.Name.IsNotNullOrWhiteSpace() && discordField.Value.IsNotNullOrWhiteSpace())
{
embed.Fields.Add(discordField);
}
}
var payload = CreatePayload(null, new List<Embed> { embed });
_proxy.SendPayload(payload, Settings);
}
public override void OnReleaseImport(BookDownloadMessage message)
{
var attachments = new List<Embed>
var author = message.Author;
var authorMetadata = message.Author.Metadata.Value;
var edition = message.BookFiles.First().Edition.Value;
var isUpgrade = message.OldFiles.Count > 0;
var embed = new Embed
{
new Embed
Author = new DiscordAuthor
{
Description = message.Message,
Title = message.Author.Name,
Text = message.Message,
Color = (int)DiscordColors.Success
}
Name = Settings.Author.IsNullOrWhiteSpace() ? Environment.MachineName : Settings.Author,
IconUrl = "https://raw.githubusercontent.com/readarr/Readarr/develop/Logo/256.png"
},
Url = $"https://www.goodreads.com/author/show/{author.ForeignAuthorId}",
Description = isUpgrade ? "Book Upgraded" : "Book Imported",
Title = GetTitle(message.Author, new List<Book> { message.Book }),
Color = isUpgrade ? (int)DiscordColors.Upgrade : (int)DiscordColors.Success,
Fields = new List<DiscordField>(),
Timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
};
var payload = CreatePayload($"Imported: {message.Message}", attachments);
if (Settings.GrabFields.Contains((int)DiscordGrabFieldType.Cover))
{
embed.Image = new DiscordImage
{
Url = edition.Images.FirstOrDefault(c => c.CoverType == MediaCoverTypes.Cover)?.Url
};
}
if (Settings.GrabFields.Contains((int)DiscordGrabFieldType.Poster))
{
embed.Thumbnail = new DiscordImage
{
Url = authorMetadata.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Poster)?.Url
};
}
foreach (var field in Settings.ImportFields)
{
var discordField = new DiscordField();
switch ((DiscordImportFieldType)field)
{
case DiscordImportFieldType.Overview:
var overview = edition.Overview ?? "";
discordField.Name = "Overview";
discordField.Value = overview.Length <= 300 ? overview : overview.Substring(0, 300) + "...";
break;
case DiscordImportFieldType.Rating:
discordField.Name = "Rating";
discordField.Value = message.Book.Ratings.Value.ToString();
break;
case DiscordImportFieldType.Genres:
discordField.Name = "Genres";
discordField.Value = message.Book.Genres.Take(5).Join(", ");
break;
case DiscordImportFieldType.Quality:
discordField.Name = "Quality";
discordField.Inline = true;
discordField.Value = message.BookFiles.First().Quality.Quality.Name;
break;
case DiscordImportFieldType.Group:
discordField.Name = "Group";
discordField.Value = message.BookFiles.First().ReleaseGroup;
break;
case DiscordImportFieldType.Size:
discordField.Name = "Size";
discordField.Value = BytesToString(message.BookFiles.Sum(x => x.Size));
discordField.Inline = true;
break;
case DiscordImportFieldType.Release:
discordField.Name = "Release";
discordField.Value = message.BookFiles.First().SceneName;
break;
case DiscordImportFieldType.Links:
discordField.Name = "Links";
discordField.Value = GetLinksString(edition);
break;
}
if (discordField.Name.IsNotNullOrWhiteSpace() && discordField.Value.IsNotNullOrWhiteSpace())
{
embed.Fields.Add(discordField);
}
}
var payload = CreatePayload(null, new List<Embed> { embed });
_proxy.SendPayload(payload, Settings);
}
@ -124,13 +287,19 @@ namespace NzbDrone.Core.Notifications.Discord
{
new Embed
{
Author = new DiscordAuthor
{
Name = Settings.Author.IsNullOrWhiteSpace() ? Environment.MachineName : Settings.Author,
IconUrl = "https://raw.githubusercontent.com/readarr/Readarr/develop/Logo/256.png"
},
Title = healthCheck.Source.Name,
Text = healthCheck.Message,
Description = healthCheck.Message,
Timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"),
Color = healthCheck.Type == HealthCheck.HealthCheckResult.Warning ? (int)DiscordColors.Warning : (int)DiscordColors.Danger
}
};
var payload = CreatePayload("Health Issue", attachments);
var payload = CreatePayload(null, attachments);
_proxy.SendPayload(payload, Settings);
}
@ -269,5 +438,31 @@ namespace NzbDrone.Core.Notifications.Discord
return payload;
}
private string BytesToString(long byteCount)
{
string[] suf = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; //Longs run out around EB
if (byteCount == 0)
{
return "0 " + suf[0];
}
var bytes = Math.Abs(byteCount);
var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
var num = Math.Round(bytes / Math.Pow(1024, place), 1);
return string.Format("{0} {1}", (Math.Sign(byteCount) * num).ToString(), suf[place]);
}
private string GetLinksString(Edition edition)
{
var links = edition.Links.Select(link => $"[{link.Name}]({link.Url})");
return string.Join(" / ", links);
}
private string GetTitle(Author author, List<Book> books)
{
var bookTitles = string.Join(" + ", books.Select(e => e.Title));
return $"{author.Name} - {bookTitles}";
}
}
}

@ -0,0 +1,33 @@
namespace NzbDrone.Core.Notifications.Discord
{
public enum DiscordGrabFieldType
{
Overview,
Rating,
Genres,
Quality,
Group,
Size,
Links,
Release,
Poster,
Cover,
Indexer,
CustomFormats,
CustomFormatScore
}
public enum DiscordImportFieldType
{
Overview,
Rating,
Genres,
Quality,
Group,
Size,
Links,
Release,
Poster,
Cover
}
}

@ -1,3 +1,4 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.Annotations;
using NzbDrone.Core.ThingiProvider;
@ -15,6 +16,13 @@ namespace NzbDrone.Core.Notifications.Discord
public class DiscordSettings : IProviderConfig
{
public DiscordSettings()
{
// Set Default Fields
GrabFields = new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
ImportFields = new[] { 0, 1, 2, 3, 5, 6, 7, 8, 9 };
}
private static readonly DiscordSettingsValidator Validator = new DiscordSettingsValidator();
[FieldDefinition(0, Label = "Webhook URL", HelpText = "Discord channel webhook url")]
@ -28,6 +36,11 @@ namespace NzbDrone.Core.Notifications.Discord
[FieldDefinition(3, Label = "Host", Advanced = true, HelpText = "Override the Host that shows for this notification, Blank is machine name", Type = FieldType.Textbox)]
public string Author { get; set; }
[FieldDefinition(4, Label = "On Grab Fields", Advanced = true, SelectOptions = typeof(DiscordGrabFieldType), HelpText = "Change the fields that are passed in for this 'on grab' notification", Type = FieldType.TagSelect)]
public IEnumerable<int> GrabFields { get; set; }
[FieldDefinition(5, Label = "On Import Fields", Advanced = true, SelectOptions = typeof(DiscordImportFieldType), HelpText = "Change the fields that are passed for this 'on import' notification", Type = FieldType.TagSelect)]
public IEnumerable<int> ImportFields { get; set; }
public NzbDroneValidationResult Validate()
{
return new NzbDroneValidationResult(Validator.Validate(this));

Loading…
Cancel
Save