mirror of
https://github.com/Radarr/Radarr.git
synced 2024-11-09 12:32:31 +01:00
New: Customizable Discord Notifications (Thanks @hotio)
This commit is contained in:
parent
2ad1cfec42
commit
ad5a90f034
@ -1,7 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using FluentValidation.Results;
|
using FluentValidation.Results;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Core.MediaCover;
|
||||||
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
|
using NzbDrone.Core.Movies;
|
||||||
using NzbDrone.Core.Notifications.Discord.Payloads;
|
using NzbDrone.Core.Notifications.Discord.Payloads;
|
||||||
using NzbDrone.Core.Validation;
|
using NzbDrone.Core.Validation;
|
||||||
|
|
||||||
@ -21,34 +25,189 @@ public Discord(IDiscordProxy proxy)
|
|||||||
|
|
||||||
public override void OnGrab(GrabMessage message)
|
public override void OnGrab(GrabMessage message)
|
||||||
{
|
{
|
||||||
var embeds = new List<Embed>
|
var embed = new Embed
|
||||||
{
|
{
|
||||||
new Embed
|
Author = new DiscordAuthor
|
||||||
{
|
{
|
||||||
Description = message.Message,
|
Name = Settings.Author.IsNullOrWhiteSpace() ? Environment.MachineName : Settings.Author,
|
||||||
Title = message.Movie.Title,
|
IconUrl = "https://raw.githubusercontent.com/Radarr/Radarr/aphrodite/Logo/256.png"
|
||||||
Text = message.Message,
|
},
|
||||||
Color = (int)DiscordColors.Warning
|
Url = $"https://www.themoviedb.org/movie/{message.Movie.TmdbId}",
|
||||||
}
|
Description = "Movie Grabbed",
|
||||||
|
Title = message.Movie.Year > 0 ? $"{message.Movie.Title} ({message.Movie.Year})" : message.Movie.Title,
|
||||||
|
Color = (int)DiscordColors.Standard,
|
||||||
|
Fields = new List<DiscordField>(),
|
||||||
|
Timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
|
||||||
};
|
};
|
||||||
var payload = CreatePayload($"Grabbed: {message.Message}", embeds);
|
|
||||||
|
if (Settings.GrabFields.Contains((int)DiscordGrabFieldType.Poster))
|
||||||
|
{
|
||||||
|
embed.Thumbnail = new DiscordImage
|
||||||
|
{
|
||||||
|
Url = message.Movie.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Poster).Url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.GrabFields.Contains((int)DiscordGrabFieldType.Fanart))
|
||||||
|
{
|
||||||
|
embed.Image = new DiscordImage
|
||||||
|
{
|
||||||
|
Url = message.Movie.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Fanart).Url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var field in Settings.GrabFields)
|
||||||
|
{
|
||||||
|
var discordField = new DiscordField();
|
||||||
|
|
||||||
|
switch ((DiscordGrabFieldType)field)
|
||||||
|
{
|
||||||
|
case DiscordGrabFieldType.Overview:
|
||||||
|
discordField.Name = "Overview";
|
||||||
|
discordField.Value = message.Movie.Overview.Length <= 300 ? message.Movie.Overview : message.Movie.Overview.Substring(0, 300) + "...";
|
||||||
|
break;
|
||||||
|
case DiscordGrabFieldType.Rating:
|
||||||
|
discordField.Name = "Rating";
|
||||||
|
discordField.Value = message.Movie.Ratings.Value.ToString();
|
||||||
|
break;
|
||||||
|
case DiscordGrabFieldType.Genres:
|
||||||
|
discordField.Name = "Genres";
|
||||||
|
discordField.Value = message.Movie.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.RemoteMovie.ParsedMovieInfo.ReleaseGroup;
|
||||||
|
break;
|
||||||
|
case DiscordGrabFieldType.Size:
|
||||||
|
discordField.Name = "Size";
|
||||||
|
discordField.Value = BytesToString(message.RemoteMovie.Release.Size);
|
||||||
|
discordField.Inline = true;
|
||||||
|
break;
|
||||||
|
case DiscordGrabFieldType.Release:
|
||||||
|
discordField.Name = "Release";
|
||||||
|
discordField.Value = string.Format("```{0}```", message.RemoteMovie.Release.Title);
|
||||||
|
break;
|
||||||
|
case DiscordGrabFieldType.Links:
|
||||||
|
discordField.Name = "Links";
|
||||||
|
discordField.Value = GetLinksString(message.Movie);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discordField.Name.IsNotNullOrWhiteSpace() && discordField.Value.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
embed.Fields.Add(discordField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = CreatePayload(null, new List<Embed> { embed });
|
||||||
|
|
||||||
_proxy.SendPayload(payload, Settings);
|
_proxy.SendPayload(payload, Settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnDownload(DownloadMessage message)
|
public override void OnDownload(DownloadMessage message)
|
||||||
{
|
{
|
||||||
var embeds = new List<Embed>
|
var isUpgrade = message.OldMovieFiles.Count > 0;
|
||||||
|
var embed = new Embed
|
||||||
{
|
{
|
||||||
new Embed
|
Author = new DiscordAuthor
|
||||||
{
|
{
|
||||||
Description = message.Message,
|
Name = Settings.Author.IsNullOrWhiteSpace() ? Environment.MachineName : Settings.Author,
|
||||||
Title = message.Movie.Title,
|
IconUrl = "https://raw.githubusercontent.com/Radarr/Radarr/aphrodite/Logo/256.png"
|
||||||
Text = message.Message,
|
},
|
||||||
Color = (int)DiscordColors.Success
|
Url = $"https://www.themoviedb.org/movie/{message.Movie.TmdbId}",
|
||||||
}
|
Description = isUpgrade ? "Movie Upgraded" : "Movie Imported",
|
||||||
|
Title = message.Movie.Year > 0 ? $"{message.Movie.Title} ({message.Movie.Year})" : message.Movie.Title,
|
||||||
|
Color = isUpgrade ? (int)DiscordColors.Upgrade : (int)DiscordColors.Standard,
|
||||||
|
Fields = new List<DiscordField>(),
|
||||||
|
Timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
|
||||||
};
|
};
|
||||||
var payload = CreatePayload($"Imported: {message.Message}", embeds);
|
|
||||||
|
if (Settings.ImportFields.Contains((int)DiscordImportFieldType.Poster))
|
||||||
|
{
|
||||||
|
embed.Thumbnail = new DiscordImage
|
||||||
|
{
|
||||||
|
Url = message.Movie.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Poster).Url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Settings.ImportFields.Contains((int)DiscordImportFieldType.Fanart))
|
||||||
|
{
|
||||||
|
embed.Image = new DiscordImage
|
||||||
|
{
|
||||||
|
Url = message.Movie.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Fanart).Url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var field in Settings.ImportFields)
|
||||||
|
{
|
||||||
|
var discordField = new DiscordField();
|
||||||
|
|
||||||
|
switch ((DiscordImportFieldType)field)
|
||||||
|
{
|
||||||
|
case DiscordImportFieldType.Overview:
|
||||||
|
discordField.Name = "Overview";
|
||||||
|
discordField.Value = message.Movie.Overview.Length <= 300 ? message.Movie.Overview : message.Movie.Overview.Substring(0, 300) + "...";
|
||||||
|
break;
|
||||||
|
case DiscordImportFieldType.Rating:
|
||||||
|
discordField.Name = "Rating";
|
||||||
|
discordField.Value = message.Movie.Ratings.Value.ToString();
|
||||||
|
break;
|
||||||
|
case DiscordImportFieldType.Genres:
|
||||||
|
discordField.Name = "Genres";
|
||||||
|
discordField.Value = message.Movie.Genres.Take(5).Join(", ");
|
||||||
|
break;
|
||||||
|
case DiscordImportFieldType.Quality:
|
||||||
|
discordField.Name = "Quality";
|
||||||
|
discordField.Inline = true;
|
||||||
|
discordField.Value = message.MovieFile.Quality.Quality.Name;
|
||||||
|
break;
|
||||||
|
case DiscordImportFieldType.Codecs:
|
||||||
|
discordField.Name = "Codecs";
|
||||||
|
discordField.Inline = true;
|
||||||
|
discordField.Value = string.Format("{0} / {1} {2}",
|
||||||
|
MediaInfoFormatter.FormatVideoCodec(message.MovieFile.MediaInfo, null),
|
||||||
|
MediaInfoFormatter.FormatAudioCodec(message.MovieFile.MediaInfo, null),
|
||||||
|
MediaInfoFormatter.FormatAudioChannels(message.MovieFile.MediaInfo));
|
||||||
|
break;
|
||||||
|
case DiscordImportFieldType.Group:
|
||||||
|
discordField.Name = "Group";
|
||||||
|
discordField.Value = message.MovieFile.ReleaseGroup;
|
||||||
|
break;
|
||||||
|
case DiscordImportFieldType.Size:
|
||||||
|
discordField.Name = "Size";
|
||||||
|
discordField.Value = BytesToString(message.MovieFile.Size);
|
||||||
|
discordField.Inline = true;
|
||||||
|
break;
|
||||||
|
case DiscordImportFieldType.Languages:
|
||||||
|
discordField.Name = "Languages";
|
||||||
|
discordField.Value = message.MovieFile.MediaInfo.AudioLanguages;
|
||||||
|
break;
|
||||||
|
case DiscordImportFieldType.Subtitles:
|
||||||
|
discordField.Name = "Subtitles";
|
||||||
|
discordField.Value = message.MovieFile.MediaInfo.Subtitles;
|
||||||
|
break;
|
||||||
|
case DiscordImportFieldType.Release:
|
||||||
|
discordField.Name = "Release";
|
||||||
|
discordField.Value = message.MovieFile.SceneName;
|
||||||
|
break;
|
||||||
|
case DiscordImportFieldType.Links:
|
||||||
|
discordField.Name = "Links";
|
||||||
|
discordField.Value = GetLinksString(message.Movie);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discordField.Name.IsNotNullOrWhiteSpace() && discordField.Value.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
embed.Fields.Add(discordField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var payload = CreatePayload(null, new List<Embed> { embed });
|
||||||
|
|
||||||
_proxy.SendPayload(payload, Settings);
|
_proxy.SendPayload(payload, Settings);
|
||||||
}
|
}
|
||||||
@ -59,13 +218,19 @@ public override void OnHealthIssue(HealthCheck.HealthCheck healthCheck)
|
|||||||
{
|
{
|
||||||
new Embed
|
new Embed
|
||||||
{
|
{
|
||||||
|
Author = new DiscordAuthor
|
||||||
|
{
|
||||||
|
Name = Settings.Author.IsNullOrWhiteSpace() ? Environment.MachineName : Settings.Author,
|
||||||
|
IconUrl = "https://raw.githubusercontent.com/Radarr/Radarr/aphrodite/Logo/256.png"
|
||||||
|
},
|
||||||
Title = healthCheck.Source.Name,
|
Title = healthCheck.Source.Name,
|
||||||
Text = healthCheck.Message,
|
Description = healthCheck.Message,
|
||||||
Color = healthCheck.Type == HealthCheck.HealthCheckResult.Warning ? (int)DiscordColors.Warning : (int)DiscordColors.Success
|
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);
|
_proxy.SendPayload(payload, Settings);
|
||||||
}
|
}
|
||||||
@ -119,5 +284,41 @@ private DiscordPayload CreatePayload(string message, List<Embed> embeds = null)
|
|||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static 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 static string GetLinksString(Movie movie)
|
||||||
|
{
|
||||||
|
var links = string.Format("[{0}]({1})", "TMDb", $"https://themoviedb.com/movie/{movie.TmdbId}");
|
||||||
|
links += string.Format(" / [{0}]({1})", "Trakt", $"https://trakt.tv/search/tmdb/{movie.TmdbId}?id_type=movie");
|
||||||
|
if (movie.ImdbId.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
links += string.Format(" / [{0}]({1})", "IMDb", $"https://imdb.com/title/{movie.ImdbId}/");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movie.YouTubeTrailerId.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
links += string.Format(" / [{0}]({1})", "YouTube", $"https://www.youtube.com/watch?v={movie.YouTubeTrailerId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movie.Website.IsNotNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
links += string.Format(" / [{0}]({1})", "Website", movie.Website);
|
||||||
|
}
|
||||||
|
|
||||||
|
return links;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
namespace NzbDrone.Core.Notifications.Discord
|
namespace NzbDrone.Core.Notifications.Discord
|
||||||
{
|
{
|
||||||
public enum DiscordColors
|
public enum DiscordColors
|
||||||
{
|
{
|
||||||
Danger = 15749200,
|
Danger = 15749200,
|
||||||
Success = 2605644,
|
Success = 2605644,
|
||||||
Warning = 16753920
|
Warning = 16753920,
|
||||||
|
Standard = 16761392,
|
||||||
|
Upgrade = 7105644
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
src/NzbDrone.Core/Notifications/Discord/DiscordFieldType.cs
Normal file
33
src/NzbDrone.Core/Notifications/Discord/DiscordFieldType.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
namespace NzbDrone.Core.Notifications.Discord
|
||||||
|
{
|
||||||
|
public enum DiscordGrabFieldType
|
||||||
|
{
|
||||||
|
Overview,
|
||||||
|
Rating,
|
||||||
|
Genres,
|
||||||
|
Quality,
|
||||||
|
Group,
|
||||||
|
Size,
|
||||||
|
Links,
|
||||||
|
Release,
|
||||||
|
Poster,
|
||||||
|
Fanart
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum DiscordImportFieldType
|
||||||
|
{
|
||||||
|
Overview,
|
||||||
|
Rating,
|
||||||
|
Genres,
|
||||||
|
Quality,
|
||||||
|
Codecs,
|
||||||
|
Group,
|
||||||
|
Size,
|
||||||
|
Languages,
|
||||||
|
Subtitles,
|
||||||
|
Links,
|
||||||
|
Release,
|
||||||
|
Poster,
|
||||||
|
Fanart
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using FluentValidation;
|
using FluentValidation;
|
||||||
using NzbDrone.Core.Annotations;
|
using NzbDrone.Core.Annotations;
|
||||||
using NzbDrone.Core.ThingiProvider;
|
using NzbDrone.Core.ThingiProvider;
|
||||||
@ -15,6 +16,13 @@ public DiscordSettingsValidator()
|
|||||||
|
|
||||||
public class DiscordSettings : IProviderConfig
|
public class DiscordSettings : IProviderConfig
|
||||||
{
|
{
|
||||||
|
public DiscordSettings()
|
||||||
|
{
|
||||||
|
//Set Default Fields
|
||||||
|
GrabFields = new List<int> { 0, 1, 2, 3, 5, 6, 7, 8, 9 };
|
||||||
|
ImportFields = new List<int> { 0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12 };
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly DiscordSettingsValidator Validator = new DiscordSettingsValidator();
|
private static readonly DiscordSettingsValidator Validator = new DiscordSettingsValidator();
|
||||||
|
|
||||||
[FieldDefinition(0, Label = "Webhook URL", HelpText = "Discord channel webhook url")]
|
[FieldDefinition(0, Label = "Webhook URL", HelpText = "Discord channel webhook url")]
|
||||||
@ -26,6 +34,15 @@ public class DiscordSettings : IProviderConfig
|
|||||||
[FieldDefinition(2, Label = "Avatar", HelpText = "Change the avatar that is used for messages from this integration", Type = FieldType.Textbox)]
|
[FieldDefinition(2, Label = "Avatar", HelpText = "Change the avatar that is used for messages from this integration", Type = FieldType.Textbox)]
|
||||||
public string Avatar { get; set; }
|
public string Avatar { get; set; }
|
||||||
|
|
||||||
|
[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()
|
public NzbDroneValidationResult Validate()
|
||||||
{
|
{
|
||||||
return new NzbDroneValidationResult(Validator.Validate(this));
|
return new NzbDroneValidationResult(Validator.Validate(this));
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Notifications.Discord.Payloads
|
||||||
|
{
|
||||||
|
public class DiscordAuthor
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("icon_url")]
|
||||||
|
public string IconUrl { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace NzbDrone.Core.Notifications.Discord.Payloads
|
||||||
|
{
|
||||||
|
public class DiscordField
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Value { get; set; }
|
||||||
|
public bool Inline { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
namespace NzbDrone.Core.Notifications.Discord.Payloads
|
||||||
|
{
|
||||||
|
public class DiscordImage
|
||||||
|
{
|
||||||
|
public string Url { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Notifications.Discord.Payloads
|
namespace NzbDrone.Core.Notifications.Discord.Payloads
|
||||||
{
|
{
|
||||||
public class Embed
|
public class Embed
|
||||||
@ -6,5 +8,11 @@ public class Embed
|
|||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
public string Text { get; set; }
|
public string Text { get; set; }
|
||||||
public int Color { get; set; }
|
public int Color { get; set; }
|
||||||
|
public string Url { get; set; }
|
||||||
|
public DiscordAuthor Author { get; set; }
|
||||||
|
public DiscordImage Thumbnail { get; set; }
|
||||||
|
public DiscordImage Image { get; set; }
|
||||||
|
public string Timestamp { get; set; }
|
||||||
|
public List<DiscordField> Fields { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user