diff --git a/frontend/src/Settings/Notifications/Notifications/Notification.js b/frontend/src/Settings/Notifications/Notifications/Notification.js index 4ecf33047..a0f8fefa4 100644 --- a/frontend/src/Settings/Notifications/Notifications/Notification.js +++ b/frontend/src/Settings/Notifications/Notifications/Notification.js @@ -59,10 +59,12 @@ class Notification extends Component { onGrab, onHealthIssue, onHealthRestored, + onVipExpiration, onApplicationUpdate, supportsOnGrab, supportsOnHealthIssue, supportsOnHealthRestored, + supportsOnVipExpiration, supportsOnApplicationUpdate, tags, tagList @@ -102,6 +104,14 @@ class Notification extends Component { null } + { + supportsOnVipExpiration && onVipExpiration ? + + {translate('OnVipExpiration')} + : + null + } + { supportsOnApplicationUpdate && onApplicationUpdate ? @@ -111,7 +121,7 @@ class Notification extends Component { } { - !onGrab && !onHealthIssue && !onHealthRestored && !onApplicationUpdate ? + !onGrab && !onHealthIssue && !onHealthRestored && !onVipExpiration && !onApplicationUpdate ? + + + + { (onHealthIssue.value || onHealthRestored.value) && diff --git a/frontend/src/typings/Notification.ts b/frontend/src/typings/Notification.ts index 63ea906c4..05991fbc5 100644 --- a/frontend/src/typings/Notification.ts +++ b/frontend/src/typings/Notification.ts @@ -16,11 +16,13 @@ interface Notification extends ModelBase { onGrab: boolean; onHealthIssue: boolean; onHealthRestored: boolean; + onVipExpiration: boolean; includeHealthWarnings: boolean; onApplicationUpdate: boolean; supportsOnGrab: boolean; supportsOnHealthIssue: boolean; supportsOnHealthRestored: boolean; + supportsOnVipExpiration: boolean; supportsOnApplicationUpdate: boolean; fields: Field[]; implementationName: string; diff --git a/src/NzbDrone.Core/Datastore/Migration/044_vip_expiration_notification.cs b/src/NzbDrone.Core/Datastore/Migration/044_vip_expiration_notification.cs new file mode 100644 index 000000000..1b88eac77 --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/044_vip_expiration_notification.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(044)] + public class vip_expiration_notification : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Notifications").AddColumn("OnVipExpiration").AsBoolean().NotNullable().WithDefaultValue(false); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 637f61b99..c914ccacd 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -70,6 +70,7 @@ public static void Map() .Ignore(i => i.SupportsOnGrab) .Ignore(i => i.SupportsOnHealthIssue) .Ignore(i => i.SupportsOnHealthRestored) + .Ignore(i => i.SupportsOnVipExpiration) .Ignore(i => i.SupportsOnApplicationUpdate); Mapper.Entity("IndexerProxies").RegisterModel() diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json index a789c6046..a552006aa 100644 --- a/src/NzbDrone.Core/Localization/Core/en.json +++ b/src/NzbDrone.Core/Localization/Core/en.json @@ -548,6 +548,8 @@ "OnHealthRestored": "On Health Restored", "OnHealthRestoredHelpText": "On Health Restored", "OnLatestVersion": "The latest version of {appName} is already installed", + "OnVipExpiration": "On VIP Expiration", + "OnVipExpirationHelpText": "On VIP Expiration", "Open": "Open", "OpenBrowserOnStart": "Open browser on start", "OpenThisModal": "Open This Modal", diff --git a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs index 67dcefe11..24f19eed1 100644 --- a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs +++ b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs @@ -6,15 +6,17 @@ public class NotificationDefinition : ProviderDefinition { public bool OnHealthIssue { get; set; } public bool OnHealthRestored { get; set; } + public bool OnVipExpiration { get; set; } public bool OnApplicationUpdate { get; set; } public bool OnGrab { get; set; } public bool SupportsOnGrab { get; set; } public bool IncludeManualGrabs { get; set; } public bool SupportsOnHealthIssue { get; set; } public bool SupportsOnHealthRestored { get; set; } + public bool SupportsOnVipExpiration { get; set; } public bool IncludeHealthWarnings { get; set; } public bool SupportsOnApplicationUpdate { get; set; } - public override bool Enable => OnHealthIssue || OnHealthRestored || OnApplicationUpdate || OnGrab; + public override bool Enable => OnHealthIssue || OnHealthRestored || OnVipExpiration || OnApplicationUpdate || OnGrab; } } diff --git a/src/NzbDrone.Core/Notifications/NotificationFactory.cs b/src/NzbDrone.Core/Notifications/NotificationFactory.cs index 824aae0b5..bc48777bc 100644 --- a/src/NzbDrone.Core/Notifications/NotificationFactory.cs +++ b/src/NzbDrone.Core/Notifications/NotificationFactory.cs @@ -13,6 +13,7 @@ public interface INotificationFactory : IProviderFactory OnGrabEnabled(bool filterBlockedNotifications = true); List OnHealthIssueEnabled(bool filterBlockedNotifications = true); List OnHealthRestoredEnabled(bool filterBlockedNotifications = true); + List OnVipExpirationEnabled(bool filterBlockedNotifications = true); List OnApplicationUpdateEnabled(bool filterBlockedNotifications = true); } @@ -63,6 +64,16 @@ public List OnHealthRestoredEnabled(bool filterBlockedNotificatio return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnHealthRestored).ToList(); } + public List OnVipExpirationEnabled(bool filterBlockedNotifications = true) + { + if (filterBlockedNotifications) + { + return FilterBlockedNotifications(GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnVipExpiration)).ToList(); + } + + return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnVipExpiration).ToList(); + } + public List OnApplicationUpdateEnabled(bool filterBlockedNotifications = true) { if (filterBlockedNotifications) @@ -96,6 +107,7 @@ public override void SetProviderCharacteristics(INotification provider, Notifica definition.SupportsOnGrab = provider.SupportsOnGrab; definition.SupportsOnHealthIssue = provider.SupportsOnHealthIssue; definition.SupportsOnHealthRestored = provider.SupportsOnHealthRestored; + definition.SupportsOnVipExpiration = provider.SupportsOnHealthIssue; definition.SupportsOnApplicationUpdate = provider.SupportsOnApplicationUpdate; } diff --git a/src/NzbDrone.Core/Notifications/NotificationService.cs b/src/NzbDrone.Core/Notifications/NotificationService.cs index ab4473023..94f38e38c 100644 --- a/src/NzbDrone.Core/Notifications/NotificationService.cs +++ b/src/NzbDrone.Core/Notifications/NotificationService.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Core.HealthCheck; +using NzbDrone.Core.HealthCheck.Checks; using NzbDrone.Core.Indexers; using NzbDrone.Core.Indexers.Events; using NzbDrone.Core.Messaging.Events; @@ -40,6 +42,18 @@ private bool ShouldHandleHealthFailure(HealthCheck.HealthCheck healthCheck, bool }; } + private bool ShouldHandleVipExpiration(HealthCheck.HealthCheck healthCheck) + { + return IsVipExpirationHealthCheck(healthCheck) && + ShouldHandleHealthFailure(healthCheck, true); + } + + private bool IsVipExpirationHealthCheck(HealthCheck.HealthCheck healthCheck) + { + return healthCheck.Source == typeof(IndexerVIPCheck) || + healthCheck.Source == typeof(IndexerVIPExpiredCheck); + } + private bool ShouldHandleOnGrab(GrabMessage message, bool includeManual) { return message.GrabTrigger switch @@ -81,15 +95,33 @@ public void Handle(HealthCheckFailedEvent message) return; } - foreach (var notification in _notificationFactory.OnHealthIssueEnabled()) + var notified = new HashSet(); + var notifications = _notificationFactory.OnHealthIssueEnabled() + .Concat(_notificationFactory.OnVipExpirationEnabled()); + + foreach (var notification in notifications) { try { - if (ShouldHandleHealthFailure(message.HealthCheck, ((NotificationDefinition)notification.Definition).IncludeHealthWarnings)) + var definition = (NotificationDefinition)notification.Definition; + + var shouldHandleHealthIssue = definition.OnHealthIssue && + ShouldHandleHealthFailure(message.HealthCheck, definition.IncludeHealthWarnings); + var shouldHandleVipExpiration = definition.OnVipExpiration && + ShouldHandleVipExpiration(message.HealthCheck); + + if (!shouldHandleHealthIssue && !shouldHandleVipExpiration) { - notification.OnHealthIssue(message.HealthCheck); - _notificationStatusService.RecordSuccess(notification.Definition.Id); + continue; } + + if (!notified.Add(definition.Id)) + { + continue; + } + + notification.OnHealthIssue(message.HealthCheck); + _notificationStatusService.RecordSuccess(notification.Definition.Id); } catch (Exception ex) { diff --git a/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs b/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs index dcac3f6d4..39895d5ef 100644 --- a/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs +++ b/src/Prowlarr.Api.V1/Notifications/NotificationResource.cs @@ -8,11 +8,13 @@ public class NotificationResource : ProviderResource public bool OnGrab { get; set; } public bool OnHealthIssue { get; set; } public bool OnHealthRestored { get; set; } + public bool OnVipExpiration { get; set; } public bool OnApplicationUpdate { get; set; } public bool SupportsOnGrab { get; set; } public bool IncludeManualGrabs { get; set; } public bool SupportsOnHealthIssue { get; set; } public bool SupportsOnHealthRestored { get; set; } + public bool SupportsOnVipExpiration { get; set; } public bool IncludeHealthWarnings { get; set; } public bool SupportsOnApplicationUpdate { get; set; } public string TestCommand { get; set; } @@ -34,8 +36,10 @@ public override NotificationResource ToResource(NotificationDefinition definitio resource.IncludeManualGrabs = definition.IncludeManualGrabs; resource.OnHealthIssue = definition.OnHealthIssue; resource.OnHealthRestored = definition.OnHealthRestored; + resource.OnVipExpiration = definition.OnVipExpiration; resource.SupportsOnHealthIssue = definition.SupportsOnHealthIssue; resource.SupportsOnHealthRestored = definition.SupportsOnHealthRestored; + resource.SupportsOnVipExpiration = definition.SupportsOnVipExpiration; resource.IncludeHealthWarnings = definition.IncludeHealthWarnings; resource.OnApplicationUpdate = definition.OnApplicationUpdate; resource.SupportsOnApplicationUpdate = definition.SupportsOnApplicationUpdate; @@ -57,8 +61,10 @@ public override NotificationDefinition ToModel(NotificationResource resource, No definition.IncludeManualGrabs = resource.IncludeManualGrabs; definition.OnHealthIssue = resource.OnHealthIssue; definition.OnHealthRestored = resource.OnHealthRestored; + definition.OnVipExpiration = resource.OnVipExpiration; definition.SupportsOnHealthIssue = resource.SupportsOnHealthIssue; definition.SupportsOnHealthRestored = resource.SupportsOnHealthRestored; + definition.SupportsOnVipExpiration = resource.SupportsOnVipExpiration; definition.IncludeHealthWarnings = resource.IncludeHealthWarnings; definition.OnApplicationUpdate = resource.OnApplicationUpdate; definition.SupportsOnApplicationUpdate = resource.SupportsOnApplicationUpdate; diff --git a/src/Prowlarr.Api.V1/openapi.json b/src/Prowlarr.Api.V1/openapi.json index 134d31d7d..c1ce9f55a 100644 --- a/src/Prowlarr.Api.V1/openapi.json +++ b/src/Prowlarr.Api.V1/openapi.json @@ -5683,6 +5683,9 @@ "onHealthRestored": { "type": "boolean" }, + "onVipExpiration": { + "type": "boolean" + }, "onApplicationUpdate": { "type": "boolean" }, @@ -5698,6 +5701,9 @@ "supportsOnHealthRestored": { "type": "boolean" }, + "supportsOnVipExpiration": { + "type": "boolean" + }, "includeHealthWarnings": { "type": "boolean" }, @@ -6353,4 +6359,4 @@ "apikey": [ ] } ] -} \ No newline at end of file +}