From 2bde784d620e409d0b04f096f2dc232cf4001f4d Mon Sep 17 00:00:00 2001 From: Robert Dailey Date: Sat, 2 Nov 2024 11:14:59 -0500 Subject: [PATCH] feat: New Verbosity setting for notifications Fixes #354 --- CHANGELOG.md | 5 +++ schemas/settings/notifications.json | 19 +++++++++++ src/Recyclarr.Core/CoreAutofacModule.cs | 10 ++++++ .../Notifications/NotificationEmitter.cs | 26 +++++++++++---- .../Notifications/NotificationService.cs | 19 ++++++++--- .../Notifications/VerbosityStrategies.cs | 33 +++++++++++++++++++ .../Settings/RecyclarrSettings.cs | 8 +++++ 7 files changed, 108 insertions(+), 12 deletions(-) create mode 100644 src/Recyclarr.Core/Notifications/VerbosityStrategies.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 07abc4f9..04e82436 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Notifications: New `verbosity` setting for Notifications to control the frequency and content of + notifications sent after sync operations. + ## [7.3.0] - 2024-10-28 ### Added diff --git a/schemas/settings/notifications.json b/schemas/settings/notifications.json index d4085fa8..77ca122e 100644 --- a/schemas/settings/notifications.json +++ b/schemas/settings/notifications.json @@ -37,6 +37,25 @@ } } } + }, + "verbosity": { + "description": "The verbosity level for notifications", + "type": "string", + "default": "normal", + "oneOf": [ + { + "const": "normal", + "description": "Includes errors, warnings, and informational (changes). This is the default." + }, + { + "const": "detailed", + "description": "Everything in normal, plus empty messages (no changes)." + }, + { + "const": "minimal", + "description": "Only errors and warnings." + } + ] } } } diff --git a/src/Recyclarr.Core/CoreAutofacModule.cs b/src/Recyclarr.Core/CoreAutofacModule.cs index 9215994e..b5e51c82 100644 --- a/src/Recyclarr.Core/CoreAutofacModule.cs +++ b/src/Recyclarr.Core/CoreAutofacModule.cs @@ -173,6 +173,16 @@ public class CoreAutofacModule : Module .Keyed(AppriseMode.Stateless); builder.RegisterType().As(); + + // Verbosity Strategies + builder.RegisterType().Keyed(NotificationVerbosity.Minimal); + builder.RegisterType().Keyed(NotificationVerbosity.Normal); + builder.RegisterType().Keyed(NotificationVerbosity.Detailed); + builder.Register(c => + { + var settings = c.Resolve>().Value; + return c.ResolveKeyed(settings.Verbosity); + }); } private static void RegisterPlatform(ContainerBuilder builder) diff --git a/src/Recyclarr.Core/Notifications/NotificationEmitter.cs b/src/Recyclarr.Core/Notifications/NotificationEmitter.cs index 019e5040..ab3c3b21 100644 --- a/src/Recyclarr.Core/Notifications/NotificationEmitter.cs +++ b/src/Recyclarr.Core/Notifications/NotificationEmitter.cs @@ -4,7 +4,7 @@ using Recyclarr.Notifications.Events; namespace Recyclarr.Notifications; -public class NotificationEmitter +public class NotificationEmitter(IVerbosityStrategy verbosity) { private readonly Subject _notifications = new(); @@ -12,24 +12,36 @@ public class NotificationEmitter public void SendStatistic(string description) { - _notifications.OnNext(new InformationEvent(description)); + if (verbosity.ShouldSendInformation()) + { + _notifications.OnNext(new InformationEvent(description)); + } } public void SendStatistic(string description, T stat) where T : notnull { - _notifications.OnNext(new InformationEvent(description) + if (verbosity.ShouldSendInformation()) { - Statistic = stat.ToString() ?? "!STAT ERROR!" - }); + _notifications.OnNext(new InformationEvent(description) + { + Statistic = stat.ToString() ?? "!STAT ERROR!" + }); + } } public void SendError(string message) { - _notifications.OnNext(new ErrorEvent(message)); + if (verbosity.ShouldSendError()) + { + _notifications.OnNext(new ErrorEvent(message)); + } } public void SendWarning(string message) { - _notifications.OnNext(new WarningEvent(message)); + if (verbosity.ShouldSendWarning()) + { + _notifications.OnNext(new WarningEvent(message)); + } } } diff --git a/src/Recyclarr.Core/Notifications/NotificationService.cs b/src/Recyclarr.Core/Notifications/NotificationService.cs index 87ec303a..e3d872dc 100644 --- a/src/Recyclarr.Core/Notifications/NotificationService.cs +++ b/src/Recyclarr.Core/Notifications/NotificationService.cs @@ -14,7 +14,8 @@ public sealed class NotificationService( ILogger log, IIndex apiFactory, ISettings settings, - NotificationEmitter notificationEmitter) + NotificationEmitter notificationEmitter, + IVerbosityStrategy verbosity) : IDisposable { private const string NoInstance = "[no instance]"; @@ -65,6 +66,16 @@ public sealed class NotificationService( private async Task SendAppriseNotification(bool succeeded, string body, AppriseMessageType messageType) { + if (string.IsNullOrEmpty(body) && !verbosity.ShouldSendEmpty()) + { + log.Debug("Skipping notification because the body is empty"); + return; + } + + // Apprise doesn't like empty bodies, so the hyphens are there in case there are no notifications to render. + // This also doesn't look too bad because it creates some separation between the title and the content. + body = "---\n" + body.Trim(); + try { var api = apiFactory[_settings!.Mode!.Value]; @@ -72,7 +83,7 @@ public sealed class NotificationService( await api.Notify(_settings!, payload => payload with { Title = $"Recyclarr Sync {(succeeded ? "Completed" : "Failed")}", - Body = body.Trim(), + Body = body, Type = messageType, Format = AppriseMessageFormat.Markdown }); @@ -85,9 +96,7 @@ public sealed class NotificationService( private string BuildNotificationBody() { - // Apprise doesn't like empty bodies, so the hyphens are there in case there are no notifications to render. - // This also doesn't look too bad because it creates some separation between the title and the content. - var body = new StringBuilder("---\n"); + var body = new StringBuilder(); foreach (var (instanceName, notifications) in _events) { diff --git a/src/Recyclarr.Core/Notifications/VerbosityStrategies.cs b/src/Recyclarr.Core/Notifications/VerbosityStrategies.cs new file mode 100644 index 00000000..9e199b60 --- /dev/null +++ b/src/Recyclarr.Core/Notifications/VerbosityStrategies.cs @@ -0,0 +1,33 @@ +namespace Recyclarr.Notifications; + +public interface IVerbosityStrategy +{ + bool ShouldSendInformation(); + bool ShouldSendError(); + bool ShouldSendWarning(); + bool ShouldSendEmpty(); +} + +public class MinimalVerbosityStrategy : IVerbosityStrategy +{ + public bool ShouldSendInformation() => false; + public bool ShouldSendError() => true; + public bool ShouldSendWarning() => true; + public bool ShouldSendEmpty() => false; +} + +public class NormalVerbosityStrategy : IVerbosityStrategy +{ + public bool ShouldSendInformation() => true; + public bool ShouldSendError() => true; + public bool ShouldSendWarning() => true; + public bool ShouldSendEmpty() => false; +} + +public class DetailedVerbosityStrategy : IVerbosityStrategy +{ + public bool ShouldSendInformation() => true; + public bool ShouldSendError() => true; + public bool ShouldSendWarning() => true; + public bool ShouldSendEmpty() => true; +} diff --git a/src/Recyclarr.Core/Settings/RecyclarrSettings.cs b/src/Recyclarr.Core/Settings/RecyclarrSettings.cs index af0158b8..4af15c8c 100644 --- a/src/Recyclarr.Core/Settings/RecyclarrSettings.cs +++ b/src/Recyclarr.Core/Settings/RecyclarrSettings.cs @@ -38,9 +38,17 @@ public record RecyclarrSettings public record NotificationSettings { + public NotificationVerbosity Verbosity { get; [UsedImplicitly] init; } = NotificationVerbosity.Normal; public AppriseNotificationSettings? Apprise { get; [UsedImplicitly] init; } } +public enum NotificationVerbosity +{ + Minimal, + Normal, + Detailed +} + public record AppriseNotificationSettings { public AppriseMode? Mode { get; [UsedImplicitly] init; }