From e0ffb7d3590a33a5f776d345f0fc8a09458b80d7 Mon Sep 17 00:00:00 2001 From: Stickie Date: Mon, 17 Oct 2022 18:19:54 +0200 Subject: [PATCH] New: Notifications when Manual Interaction is required for importing (cherry picked from commit 33c7bd54b222c54ed7e505653793ddf962617d52) --- .../Notifications/Notification.js | 30 +++-- .../Notifications/NotificationEventItems.js | 15 ++- .../Store/Actions/Settings/notifications.js | 1 + .../Notifications/NotificationResource.cs | 6 + .../ProcessFixture.cs | 18 +-- .../MatchesGrabSpecificationFixture.cs | 125 ++++++++++++++++++ .../NotificationBaseFixture.cs | 7 + ...l_interaction_required_to_notifications.cs | 14 ++ src/NzbDrone.Core/Datastore/TableMapping.cs | 3 +- .../Download/CompletedDownloadService.cs | 3 +- .../ManualInteractionRequiredEvent.cs | 20 +++ .../DownloadMonitoringService.cs | 6 + .../TrackedDownloads/TrackedDownload.cs | 4 +- .../TrackedDownloadService.cs | 3 +- .../Aggregators/AggregateReleaseInfo.cs | 39 ++++++ .../Notifications/Apprise/Apprise.cs | 5 + .../Notifications/Boxcar/Boxcar.cs | 5 + .../CustomScript/CustomScript.cs | 21 +++ .../Notifications/Discord/Discord.cs | 91 +++++++++++++ .../Notifications/Discord/DiscordFieldType.cs | 14 ++ .../Notifications/Discord/DiscordSettings.cs | 8 +- .../Notifications/Email/Email.cs | 7 + .../Notifications/Gotify/Gotify.cs | 5 + .../Notifications/INotification.cs | 2 + src/NzbDrone.Core/Notifications/Join/Join.cs | 5 + .../Notifications/Mailgun/Mailgun.cs | 5 + .../ManualInteractionRequiredMessage.cs | 25 ++++ .../Notifications/Notifiarr/Notifiarr.cs | 5 + .../Notifications/NotificationBase.cs | 7 + .../Notifications/NotificationDefinition.cs | 4 +- .../Notifications/NotificationFactory.cs | 7 + .../Notifications/NotificationService.cs | 35 +++++ src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs | 5 + .../Notifications/Prowl/Prowl.cs | 5 + .../Notifications/PushBullet/PushBullet.cs | 5 + .../Notifications/Pushover/Pushover.cs | 5 + .../Notifications/SendGrid/SendGrid.cs | 5 + .../Notifications/Simplepush/Simplepush.cs | 5 + .../Notifications/Slack/Slack.cs | 17 +++ .../Notifications/Telegram/Telegram.cs | 5 + .../Notifications/Twitter/Twitter.cs | 5 + .../Notifications/Webhook/Webhook.cs | 5 + .../Notifications/Webhook/WebhookBase.cs | 22 +++ .../Webhook/WebhookDownloadClientItem.cs | 26 ++++ .../Notifications/Webhook/WebhookEventType.cs | 3 +- .../WebhookManualInteractionPayload.cs | 17 +++ src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs | 5 + .../Parser/Model/GrabbedReleaseInfo.cs | 30 +++++ 48 files changed, 683 insertions(+), 27 deletions(-) create mode 100644 src/NzbDrone.Core.Test/MediaFiles/TrackImport/Specifications/MatchesGrabSpecificationFixture.cs create mode 100644 src/NzbDrone.Core/Datastore/Migration/067_add_on_manual_interaction_required_to_notifications.cs create mode 100644 src/NzbDrone.Core/Download/ManualInteractionRequiredEvent.cs create mode 100644 src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateReleaseInfo.cs create mode 100644 src/NzbDrone.Core/Notifications/ManualInteractionRequiredMessage.cs create mode 100644 src/NzbDrone.Core/Notifications/Webhook/WebhookDownloadClientItem.cs create mode 100644 src/NzbDrone.Core/Notifications/Webhook/WebhookManualInteractionPayload.cs create mode 100644 src/NzbDrone.Core/Parser/Model/GrabbedReleaseInfo.cs diff --git a/frontend/src/Settings/Notifications/Notifications/Notification.js b/frontend/src/Settings/Notifications/Notifications/Notification.js index 9e2e881c4..14ab5f300 100644 --- a/frontend/src/Settings/Notifications/Notifications/Notification.js +++ b/frontend/src/Settings/Notifications/Notifications/Notification.js @@ -67,6 +67,7 @@ class Notification extends Component { onImportFailure, onTrackRetag, onApplicationUpdate, + onManualInteractionRequired, supportsOnGrab, supportsOnReleaseImport, supportsOnUpgrade, @@ -78,7 +79,8 @@ class Notification extends Component { supportsOnDownloadFailure, supportsOnImportFailure, supportsOnTrackRetag, - supportsOnApplicationUpdate + supportsOnApplicationUpdate, + supportsOnManualInteractionRequired } = this.props; return ( @@ -177,14 +179,22 @@ class Notification extends Component { } { - !onGrab && !onReleaseImport && !onRename && !onTrackRetag && !onAlbumDelete && !onArtistDelete && - !onHealthIssue && !onHealthRestored && !onDownloadFailure && !onImportFailure && !onApplicationUpdate && - + supportsOnManualInteractionRequired && onManualInteractionRequired ? + : + null + } + + { + !onGrab && !onReleaseImport && !onRename && !onTrackRetag && !onAlbumDelete && !onArtistDelete && !onHealthIssue && !onHealthRestored && !onDownloadFailure && !onImportFailure && !onApplicationUpdate && !onManualInteractionRequired ? + : + null } } + +
+ +
diff --git a/frontend/src/Store/Actions/Settings/notifications.js b/frontend/src/Store/Actions/Settings/notifications.js index b6a38022d..16fa5ceb1 100644 --- a/frontend/src/Store/Actions/Settings/notifications.js +++ b/frontend/src/Store/Actions/Settings/notifications.js @@ -111,6 +111,7 @@ export default { selectedSchema.onImportFailure = selectedSchema.supportsOnImportFailure; selectedSchema.onTrackRetag = selectedSchema.supportsOnTrackRetag; selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate; + selectedSchema.onManualInteractionRequired = selectedSchema.supportsOnManualInteractionRequired; return selectedSchema; }); diff --git a/src/Lidarr.Api.V1/Notifications/NotificationResource.cs b/src/Lidarr.Api.V1/Notifications/NotificationResource.cs index df705d42d..c3605c2c4 100644 --- a/src/Lidarr.Api.V1/Notifications/NotificationResource.cs +++ b/src/Lidarr.Api.V1/Notifications/NotificationResource.cs @@ -17,6 +17,7 @@ namespace Lidarr.Api.V1.Notifications public bool OnImportFailure { get; set; } public bool OnTrackRetag { get; set; } public bool OnApplicationUpdate { get; set; } + public bool OnManualInteractionRequired { get; set; } public bool SupportsOnGrab { get; set; } public bool SupportsOnReleaseImport { get; set; } public bool SupportsOnUpgrade { get; set; } @@ -30,6 +31,7 @@ namespace Lidarr.Api.V1.Notifications public bool SupportsOnImportFailure { get; set; } public bool SupportsOnTrackRetag { get; set; } public bool SupportsOnApplicationUpdate { get; set; } + public bool SupportsOnManualInteractionRequired { get; set; } public string TestCommand { get; set; } } @@ -56,6 +58,7 @@ namespace Lidarr.Api.V1.Notifications resource.OnImportFailure = definition.OnImportFailure; resource.OnTrackRetag = definition.OnTrackRetag; resource.OnApplicationUpdate = definition.OnApplicationUpdate; + resource.OnManualInteractionRequired = definition.OnManualInteractionRequired; resource.SupportsOnGrab = definition.SupportsOnGrab; resource.SupportsOnReleaseImport = definition.SupportsOnReleaseImport; resource.SupportsOnUpgrade = definition.SupportsOnUpgrade; @@ -69,6 +72,7 @@ namespace Lidarr.Api.V1.Notifications resource.SupportsOnImportFailure = definition.SupportsOnImportFailure; resource.SupportsOnTrackRetag = definition.SupportsOnTrackRetag; resource.SupportsOnApplicationUpdate = definition.SupportsOnApplicationUpdate; + resource.SupportsOnManualInteractionRequired = definition.SupportsOnManualInteractionRequired; return resource; } @@ -94,6 +98,7 @@ namespace Lidarr.Api.V1.Notifications definition.OnImportFailure = resource.OnImportFailure; definition.OnTrackRetag = resource.OnTrackRetag; definition.OnApplicationUpdate = resource.OnApplicationUpdate; + definition.OnManualInteractionRequired = resource.OnManualInteractionRequired; definition.SupportsOnGrab = resource.SupportsOnGrab; definition.SupportsOnReleaseImport = resource.SupportsOnReleaseImport; definition.SupportsOnUpgrade = resource.SupportsOnUpgrade; @@ -107,6 +112,7 @@ namespace Lidarr.Api.V1.Notifications definition.SupportsOnImportFailure = resource.SupportsOnImportFailure; definition.SupportsOnTrackRetag = resource.SupportsOnTrackRetag; definition.SupportsOnApplicationUpdate = resource.SupportsOnApplicationUpdate; + definition.SupportsOnManualInteractionRequired = resource.SupportsOnManualInteractionRequired; return definition; } diff --git a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs index 593fd7dd0..5c87612c3 100644 --- a/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs +++ b/src/NzbDrone.Core.Test/Download/CompletedDownloadServiceTests/ProcessFixture.cs @@ -50,8 +50,8 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests .Returns((DownloadClientItem item, DownloadClientItem previous) => item); Mocker.GetMock() - .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId)) - .Returns(new EntityHistory()); + .Setup(s => s.FindByDownloadId(_trackedDownload.DownloadItem.DownloadId)) + .Returns(new List()); Mocker.GetMock() .Setup(s => s.GetArtist("Drone.S01E01.HDTV")) @@ -70,8 +70,8 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests private void GivenNoGrabbedHistory() { Mocker.GetMock() - .Setup(s => s.MostRecentForDownloadId(_trackedDownload.DownloadItem.DownloadId)) - .Returns((EntityHistory)null); + .Setup(s => s.FindByDownloadId(_trackedDownload.DownloadItem.DownloadId)) + .Returns(new List()); } private void GivenArtistMatch() @@ -86,8 +86,11 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests _trackedDownload.DownloadItem.DownloadId = "1234"; _trackedDownload.DownloadItem.Title = "Droned Pilot"; // Set a badly named download Mocker.GetMock() - .Setup(s => s.MostRecentForDownloadId(It.Is(i => i == "1234"))) - .Returns(new EntityHistory() { SourceTitle = "Droned S01E01" }); + .Setup(s => s.FindByDownloadId(It.Is(i => i == "1234"))) + .Returns(new List + { + new EntityHistory() { SourceTitle = "Droned S01E01", EventType = EntityHistoryEventType.Grabbed } + }); Mocker.GetMock() .Setup(s => s.GetArtist(It.IsAny())) @@ -151,9 +154,6 @@ namespace NzbDrone.Core.Test.Download.CompletedDownloadServiceTests GivenABadlyNamedDownload(); _trackedDownload.RemoteAlbum.Artist = null; - Mocker.GetMock() - .Setup(s => s.MostRecentForDownloadId(It.Is(i => i == "1234"))); - Subject.Check(_trackedDownload); AssertNotReadyToImport(); diff --git a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Specifications/MatchesGrabSpecificationFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Specifications/MatchesGrabSpecificationFixture.cs new file mode 100644 index 000000000..f2929dea8 --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Specifications/MatchesGrabSpecificationFixture.cs @@ -0,0 +1,125 @@ +// using System.Collections.Generic; +// using System.Linq; +// using FizzWare.NBuilder; +// using FluentAssertions; +// using Moq; +// using NUnit.Framework; +// using NzbDrone.Common.Extensions; +// using NzbDrone.Core.Download; +// using NzbDrone.Core.History; +// using NzbDrone.Core.MediaFiles.EpisodeImport.Specifications; +// using NzbDrone.Core.Music; +// using NzbDrone.Core.Parser.Model; +// using NzbDrone.Core.Test.Framework; +// using NzbDrone.Test.Common; +// +// namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport.Specifications +// { +// [TestFixture] +// public class MatchesGrabSpecificationFixture : CoreTest +// { +// private Episode _episode1; +// private Episode _episode2; +// private Episode _episode3; +// private LocalEpisode _localEpisode; +// private DownloadClientItem _downloadClientItem; +// +// [SetUp] +// public void Setup() +// { +// _episode1 = Builder.CreateNew() +// .With(e => e.Id = 1) +// .Build(); +// +// _episode2 = Builder.CreateNew() +// .With(e => e.Id = 2) +// .Build(); +// +// _episode3 = Builder.CreateNew() +// .With(e => e.Id = 3) +// .Build(); +// +// _localEpisode = Builder.CreateNew() +// .With(l => l.Path = @"C:\Test\Unsorted\Series.Title.S01E01.720p.HDTV-Sonarr\S01E05.mkv".AsOsAgnostic()) +// .With(l => l.Episodes = new List { _episode1 }) +// .With(l => l.Release = null) +// .Build(); +// +// _downloadClientItem = Builder.CreateNew().Build(); +// } +// +// private void GivenHistoryForEpisodes(params Episode[] episodes) +// { +// if (episodes.Empty()) +// { +// return; +// } +// +// var grabbedHistories = Builder.CreateListOfSize(episodes.Length) +// .All() +// .With(h => h.EventType == EpisodeHistoryEventType.Grabbed) +// .BuildList(); +// +// for (var i = 0; i < grabbedHistories.Count; i++) +// { +// grabbedHistories[i].EpisodeId = episodes[i].Id; +// } +// +// _localEpisode.Release = new GrabbedReleaseInfo(grabbedHistories); +// } +// +// [Test] +// public void should_be_accepted_for_existing_file() +// { +// _localEpisode.ExistingFile = true; +// +// Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue(); +// } +// +// [Test] +// public void should_be_accepted_if_no_download_client_item() +// { +// Subject.IsSatisfiedBy(_localEpisode, null).Accepted.Should().BeTrue(); +// } +// +// [Test] +// public void should_be_accepted_if_no_grabbed_release_info() +// { +// GivenHistoryForEpisodes(); +// +// Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue(); +// } +// +// [Test] +// public void should_be_accepted_if_file_episode_matches_single_grabbed_release_info() +// { +// GivenHistoryForEpisodes(_episode1); +// +// Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue(); +// } +// +// [Test] +// public void should_be_accepted_if_file_episode_is_in_multi_episode_grabbed_release_info() +// { +// GivenHistoryForEpisodes(_episode1, _episode2); +// +// Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeTrue(); +// } +// +// [Test] +// public void should_be_rejected_if_file_episode_does_not_match_single_grabbed_release_info() +// { +// GivenHistoryForEpisodes(_episode2); +// +// Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeFalse(); +// } +// +// [Test] +// public void should_be_rejected_if_file_episode_is_not_in_multi_episode_grabbed_release_info() +// { +// GivenHistoryForEpisodes(_episode2, _episode3); +// +// Subject.IsSatisfiedBy(_localEpisode, _downloadClientItem).Accepted.Should().BeFalse(); +// } +// } +// } diff --git a/src/NzbDrone.Core.Test/NotificationTests/NotificationBaseFixture.cs b/src/NzbDrone.Core.Test/NotificationTests/NotificationBaseFixture.cs index 7e7f0647c..518d8c00f 100644 --- a/src/NzbDrone.Core.Test/NotificationTests/NotificationBaseFixture.cs +++ b/src/NzbDrone.Core.Test/NotificationTests/NotificationBaseFixture.cs @@ -103,6 +103,11 @@ namespace NzbDrone.Core.Test.NotificationTests { TestLogger.Info("OnApplicationUpdate was called"); } + + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + TestLogger.Info("OnManualInteractionRequired was called"); + } } private class TestNotificationWithNoEvents : NotificationBase @@ -143,6 +148,7 @@ namespace NzbDrone.Core.Test.NotificationTests notification.SupportsOnImportFailure.Should().BeTrue(); notification.SupportsOnTrackRetag.Should().BeTrue(); notification.SupportsOnApplicationUpdate.Should().BeTrue(); + notification.SupportsOnManualInteractionRequired.Should().BeTrue(); notification.SupportsOnAlbumDelete.Should().BeTrue(); notification.SupportsOnArtistDelete.Should().BeTrue(); } @@ -162,6 +168,7 @@ namespace NzbDrone.Core.Test.NotificationTests notification.SupportsOnImportFailure.Should().BeFalse(); notification.SupportsOnTrackRetag.Should().BeFalse(); notification.SupportsOnApplicationUpdate.Should().BeFalse(); + notification.SupportsOnManualInteractionRequired.Should().BeFalse(); notification.SupportsOnAlbumDelete.Should().BeFalse(); notification.SupportsOnArtistDelete.Should().BeFalse(); } diff --git a/src/NzbDrone.Core/Datastore/Migration/067_add_on_manual_interaction_required_to_notifications.cs b/src/NzbDrone.Core/Datastore/Migration/067_add_on_manual_interaction_required_to_notifications.cs new file mode 100644 index 000000000..783adf47b --- /dev/null +++ b/src/NzbDrone.Core/Datastore/Migration/067_add_on_manual_interaction_required_to_notifications.cs @@ -0,0 +1,14 @@ +using FluentMigrator; +using NzbDrone.Core.Datastore.Migration.Framework; + +namespace NzbDrone.Core.Datastore.Migration +{ + [Migration(067)] + public class add_on_manual_interaction_required_to_notifications : NzbDroneMigrationBase + { + protected override void MainDbUpgrade() + { + Alter.Table("Notifications").AddColumn("OnManualInteractionRequired").AsBoolean().WithDefaultValue(false); + } + } +} diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index ca9052f58..cb8ee6183 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -91,7 +91,8 @@ namespace NzbDrone.Core.Datastore .Ignore(i => i.SupportsOnDownloadFailure) .Ignore(i => i.SupportsOnImportFailure) .Ignore(i => i.SupportsOnTrackRetag) - .Ignore(i => i.SupportsOnApplicationUpdate); + .Ignore(i => i.SupportsOnApplicationUpdate) + .Ignore(i => i.SupportsOnManualInteractionRequired); Mapper.Entity("Metadata").RegisterModel() .Ignore(x => x.ImplementationName) diff --git a/src/NzbDrone.Core/Download/CompletedDownloadService.cs b/src/NzbDrone.Core/Download/CompletedDownloadService.cs index 0f27bda99..59b96a262 100644 --- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs +++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs @@ -69,7 +69,8 @@ namespace NzbDrone.Core.Download return; } - var historyItem = _historyService.MostRecentForDownloadId(trackedDownload.DownloadItem.DownloadId); + var grabbedHistories = _historyService.FindByDownloadId(trackedDownload.DownloadItem.DownloadId).Where(h => h.EventType == EntityHistoryEventType.Grabbed).ToList(); + var historyItem = grabbedHistories.MaxBy(h => h.Date); if (historyItem == null && trackedDownload.DownloadItem.Category.IsNullOrWhiteSpace()) { diff --git a/src/NzbDrone.Core/Download/ManualInteractionRequiredEvent.cs b/src/NzbDrone.Core/Download/ManualInteractionRequiredEvent.cs new file mode 100644 index 000000000..fc0b2395f --- /dev/null +++ b/src/NzbDrone.Core/Download/ManualInteractionRequiredEvent.cs @@ -0,0 +1,20 @@ +using NzbDrone.Common.Messaging; +using NzbDrone.Core.Download.TrackedDownloads; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.Download +{ + public class ManualInteractionRequiredEvent : IEvent + { + public RemoteAlbum Album { get; private set; } + public TrackedDownload TrackedDownload { get; private set; } + public GrabbedReleaseInfo Release { get; private set; } + + public ManualInteractionRequiredEvent(TrackedDownload trackedDownload, GrabbedReleaseInfo release) + { + TrackedDownload = trackedDownload; + Album = trackedDownload.RemoteAlbum; + Release = release; + } + } +} diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs index 79ec696b8..fc80a7b43 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs @@ -15,6 +15,7 @@ namespace NzbDrone.Core.Download.TrackedDownloads IExecute, IHandle, IHandle, + IHandle, IHandle, IHandle { @@ -166,6 +167,11 @@ namespace NzbDrone.Core.Download.TrackedDownloads _refreshDebounce.Execute(); } + public void Handle(ManualInteractionRequiredEvent message) + { + _refreshDebounce.Execute(); + } + public void Handle(TrackImportedEvent message) { _refreshDebounce.Execute(); diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs index e12949bd1..7acc81d3c 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs @@ -1,3 +1,4 @@ +using System; using NzbDrone.Core.Indexers; using NzbDrone.Core.Parser.Model; @@ -15,10 +16,11 @@ namespace NzbDrone.Core.Download.TrackedDownloads public DownloadProtocol Protocol { get; set; } public string Indexer { get; set; } public bool IsTrackable { get; set; } + public bool HasNotifiedManualInteractionRequired { get; set; } public TrackedDownload() { - StatusMessages = System.Array.Empty(); + StatusMessages = Array.Empty(); } public void Warn(string message, params object[] args) diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs index 6de46407c..9a8caa6b6 100644 --- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs +++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs @@ -108,7 +108,8 @@ namespace NzbDrone.Core.Download.TrackedDownloads DownloadClient = downloadClient.Id, DownloadItem = downloadItem, Protocol = downloadClient.Protocol, - IsTrackable = true + IsTrackable = true, + HasNotifiedManualInteractionRequired = existingItem?.HasNotifiedManualInteractionRequired ?? false }; try diff --git a/src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateReleaseInfo.cs b/src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateReleaseInfo.cs new file mode 100644 index 000000000..b43ab28ea --- /dev/null +++ b/src/NzbDrone.Core/MediaFiles/TrackImport/Aggregation/Aggregators/AggregateReleaseInfo.cs @@ -0,0 +1,39 @@ +using System.Linq; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Download; +using NzbDrone.Core.History; +using NzbDrone.Core.Parser.Model; + +namespace NzbDrone.Core.MediaFiles.TrackImport.Aggregation.Aggregators +{ + // public class AggregateReleaseInfo : IAggregateLocalEpisode + // { + // private readonly IHistoryService _historyService; + // + // public AggregateReleaseInfo(IHistoryService historyService) + // { + // _historyService = historyService; + // } + // + // public LocalTrack Aggregate(LocalTrack localTrack, DownloadClientItem downloadClientItem) + // { + // if (downloadClientItem == null) + // { + // return localTrack; + // } + // + // var grabbedHistories = _historyService.FindByDownloadId(downloadClientItem.DownloadId) + // .Where(h => h.EventType == TrackHistoryEventType.Grabbed) + // .ToList(); + // + // if (grabbedHistories.Empty()) + // { + // return localTrack; + // } + // + // localTrack.Release = new GrabbedReleaseInfo(grabbedHistories); + // + // return localTrack; + // } + // } +} diff --git a/src/NzbDrone.Core/Notifications/Apprise/Apprise.cs b/src/NzbDrone.Core/Notifications/Apprise/Apprise.cs index 59dc96f4f..b830a8935 100644 --- a/src/NzbDrone.Core/Notifications/Apprise/Apprise.cs +++ b/src/NzbDrone.Core/Notifications/Apprise/Apprise.cs @@ -62,6 +62,11 @@ namespace NzbDrone.Core.Notifications.Apprise _proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE, message.Message, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs b/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs index 9607ce326..ed9d5feaf 100644 --- a/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs +++ b/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs @@ -61,6 +61,11 @@ namespace NzbDrone.Core.Notifications.Boxcar _proxy.SendNotification(APPLICATION_UPDATE_TITLE, message.Message, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE, message.Message, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs index 83aac352c..e1c14ff41 100644 --- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs +++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs @@ -258,6 +258,27 @@ namespace NzbDrone.Core.Notifications.CustomScript ExecuteScript(environmentVariables); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + var artist = message.Artist; + var environmentVariables = new StringDictionary(); + + environmentVariables.Add("Lidarr_EventType", "ManualInteractionRequired"); + environmentVariables.Add("Lidarr_InstanceName", _configFileProvider.InstanceName); + environmentVariables.Add("Lidarr_ApplicationUrl", _configService.ApplicationUrl); + environmentVariables.Add("Lidarr_Artist_Id", artist.Id.ToString()); + environmentVariables.Add("Lidarr_Artist_Name", artist.Metadata.Value.Name); + environmentVariables.Add("Lidarr_Artist_MBId", artist.Metadata.Value.ForeignArtistId); + environmentVariables.Add("Lidarr_Artist_Type", artist.Metadata.Value.Type); + environmentVariables.Add("Lidarr_Download_Client", message.DownloadClientName ?? string.Empty); + environmentVariables.Add("Lidarr_Download_Client_Type", message.DownloadClientType ?? string.Empty); + environmentVariables.Add("Lidarr_Download_Id", message.DownloadId ?? string.Empty); + environmentVariables.Add("Lidarr_Download_Size", message.TrackedDownload.DownloadItem.TotalSize.ToString()); + environmentVariables.Add("Lidarr_Download_Title", message.TrackedDownload.DownloadItem.Title); + + ExecuteScript(environmentVariables); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/Discord/Discord.cs b/src/NzbDrone.Core/Notifications/Discord/Discord.cs index 865951f6b..e7b12c93e 100644 --- a/src/NzbDrone.Core/Notifications/Discord/Discord.cs +++ b/src/NzbDrone.Core/Notifications/Discord/Discord.cs @@ -408,6 +408,97 @@ namespace NzbDrone.Core.Notifications.Discord _proxy.SendPayload(payload, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + var artist = message.Artist; + var albums = message.RemoteAlbum.Albums; + var artistMetadata = message.Artist.Metadata.Value; + + var embed = new Embed + { + Author = new DiscordAuthor + { + Name = Settings.Author.IsNullOrWhiteSpace() ? Environment.MachineName : Settings.Author, + IconUrl = "https://raw.githubusercontent.com/Lidarr/Lidarr/develop/Logo/256.png" + }, + Url = $"https://musicbrainz.org/artist/{artist.ForeignArtistId}", + Description = "Manual interaction needed", + Title = GetTitle(artist, albums), + Color = (int)DiscordColors.Standard, + Fields = new List(), + Timestamp = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ") + }; + + if (Settings.ManualInteractionFields.Contains((int)DiscordGrabFieldType.Poster)) + { + embed.Thumbnail = new DiscordImage + { + Url = albums.First().Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Poster)?.Url + }; + } + + if (Settings.ManualInteractionFields.Contains((int)DiscordGrabFieldType.Fanart)) + { + embed.Image = new DiscordImage + { + Url = artistMetadata.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Fanart)?.Url + }; + } + + foreach (var field in Settings.ManualInteractionFields) + { + var discordField = new DiscordField(); + + switch ((DiscordManualInteractionFieldType)field) + { + case DiscordManualInteractionFieldType.Overview: + var overview = albums.First().Overview ?? ""; + discordField.Name = "Overview"; + discordField.Value = overview.Length <= 300 ? overview : $"{overview.AsSpan(0, 300)}..."; + break; + case DiscordManualInteractionFieldType.Rating: + discordField.Name = "Rating"; + discordField.Value = albums.First().Ratings.Value.ToString(); + break; + case DiscordManualInteractionFieldType.Genres: + discordField.Name = "Genres"; + discordField.Value = albums.First().Genres.Take(5).Join(", "); + break; + case DiscordManualInteractionFieldType.Quality: + discordField.Name = "Quality"; + discordField.Inline = true; + discordField.Value = message.Quality.Quality.Name; + break; + case DiscordManualInteractionFieldType.Group: + discordField.Name = "Group"; + discordField.Value = message.RemoteAlbum.ParsedAlbumInfo.ReleaseGroup; + break; + case DiscordManualInteractionFieldType.Size: + discordField.Name = "Size"; + discordField.Value = BytesToString(message.RemoteAlbum.Release.Size); + discordField.Inline = true; + break; + case DiscordManualInteractionFieldType.DownloadTitle: + discordField.Name = "Download"; + discordField.Value = string.Format("```{0}```", message.TrackedDownload.DownloadItem.Title); + break; + case DiscordManualInteractionFieldType.Links: + discordField.Name = "Links"; + discordField.Value = GetLinksString(artist); + break; + } + + if (discordField.Name.IsNotNullOrWhiteSpace() && discordField.Value.IsNotNullOrWhiteSpace()) + { + embed.Fields.Add(discordField); + } + } + + var payload = CreatePayload(null, new List { embed }); + + _proxy.SendPayload(payload, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/Discord/DiscordFieldType.cs b/src/NzbDrone.Core/Notifications/Discord/DiscordFieldType.cs index 43a9a56e3..dad09aba9 100644 --- a/src/NzbDrone.Core/Notifications/Discord/DiscordFieldType.cs +++ b/src/NzbDrone.Core/Notifications/Discord/DiscordFieldType.cs @@ -28,4 +28,18 @@ namespace NzbDrone.Core.Notifications.Discord Poster, Fanart } + + public enum DiscordManualInteractionFieldType + { + Overview, + Rating, + Genres, + Quality, + Group, + Size, + Links, + DownloadTitle, + Poster, + Fanart + } } diff --git a/src/NzbDrone.Core/Notifications/Discord/DiscordSettings.cs b/src/NzbDrone.Core/Notifications/Discord/DiscordSettings.cs index 8bc7878d4..eb9b18df9 100644 --- a/src/NzbDrone.Core/Notifications/Discord/DiscordSettings.cs +++ b/src/NzbDrone.Core/Notifications/Discord/DiscordSettings.cs @@ -19,8 +19,9 @@ namespace NzbDrone.Core.Notifications.Discord public DiscordSettings() { // Set Default Fields - GrabFields = new int[] { 0, 1, 2, 3, 5, 6, 7, 8, 9 }; - ImportFields = new int[] { 0, 1, 2, 3, 5, 6, 7, 8, 9 }; + GrabFields = new[] { 0, 1, 2, 3, 5, 6, 7, 8, 9 }; + ImportFields = new[] { 0, 1, 2, 3, 5, 6, 7, 8, 9 }; + ManualInteractionFields = new[] { 0, 1, 2, 3, 5, 6, 7, 8, 9 }; } private static readonly DiscordSettingsValidator Validator = new DiscordSettingsValidator(); @@ -43,6 +44,9 @@ namespace NzbDrone.Core.Notifications.Discord [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 ImportFields { get; set; } + [FieldDefinition(6, Label = "On Manual Interaction Fields", Advanced = true, SelectOptions = typeof(DiscordManualInteractionFieldType), HelpText = "Change the fields that are passed for this 'on manual interaction' notification", Type = FieldType.TagSelect)] + public IEnumerable ManualInteractionFields { get; set; } + public NzbDroneValidationResult Validate() { return new NzbDroneValidationResult(Validator.Validate(this)); diff --git a/src/NzbDrone.Core/Notifications/Email/Email.cs b/src/NzbDrone.Core/Notifications/Email/Email.cs index b013c71bb..b61a43dca 100644 --- a/src/NzbDrone.Core/Notifications/Email/Email.cs +++ b/src/NzbDrone.Core/Notifications/Email/Email.cs @@ -82,6 +82,13 @@ namespace NzbDrone.Core.Notifications.Email SendEmail(Settings, APPLICATION_UPDATE_TITLE_BRANDED, body); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + var body = $"{message.Message} requires manual interaction."; + + SendEmail(Settings, MANUAL_INTERACTION_REQUIRED_TITLE_BRANDED, body); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/Gotify/Gotify.cs b/src/NzbDrone.Core/Notifications/Gotify/Gotify.cs index 058bd6c4a..18a0b334b 100644 --- a/src/NzbDrone.Core/Notifications/Gotify/Gotify.cs +++ b/src/NzbDrone.Core/Notifications/Gotify/Gotify.cs @@ -68,6 +68,11 @@ namespace NzbDrone.Core.Notifications.Gotify SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, null); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE, message.Message, message.Artist); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/INotification.cs b/src/NzbDrone.Core/Notifications/INotification.cs index 4ede325c2..37d01e3e2 100644 --- a/src/NzbDrone.Core/Notifications/INotification.cs +++ b/src/NzbDrone.Core/Notifications/INotification.cs @@ -17,6 +17,7 @@ namespace NzbDrone.Core.Notifications void OnHealthIssue(HealthCheck.HealthCheck healthCheck); void OnHealthRestored(HealthCheck.HealthCheck previousCheck); void OnApplicationUpdate(ApplicationUpdateMessage updateMessage); + void OnManualInteractionRequired(ManualInteractionRequiredMessage message); void OnDownloadFailure(DownloadFailedMessage message); void OnImportFailure(AlbumDownloadMessage message); void OnTrackRetag(TrackRetagMessage message); @@ -30,6 +31,7 @@ namespace NzbDrone.Core.Notifications bool SupportsOnHealthIssue { get; } bool SupportsOnHealthRestored { get; } bool SupportsOnApplicationUpdate { get; } + bool SupportsOnManualInteractionRequired { get; } bool SupportsOnDownloadFailure { get; } bool SupportsOnImportFailure { get; } bool SupportsOnTrackRetag { get; } diff --git a/src/NzbDrone.Core/Notifications/Join/Join.cs b/src/NzbDrone.Core/Notifications/Join/Join.cs index ff1102d4b..b766ada5a 100644 --- a/src/NzbDrone.Core/Notifications/Join/Join.cs +++ b/src/NzbDrone.Core/Notifications/Join/Join.cs @@ -52,6 +52,11 @@ namespace NzbDrone.Core.Notifications.Join _proxy.SendNotification(APPLICATION_UPDATE_TITLE_BRANDED, updateMessage.Message, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE_BRANDED, message.Message, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/Mailgun/Mailgun.cs b/src/NzbDrone.Core/Notifications/Mailgun/Mailgun.cs index f68fef06f..3a6f02f1b 100644 --- a/src/NzbDrone.Core/Notifications/Mailgun/Mailgun.cs +++ b/src/NzbDrone.Core/Notifications/Mailgun/Mailgun.cs @@ -54,6 +54,11 @@ namespace NzbDrone.Core.Notifications.Mailgun _proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE, message.Message, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/ManualInteractionRequiredMessage.cs b/src/NzbDrone.Core/Notifications/ManualInteractionRequiredMessage.cs new file mode 100644 index 000000000..0cd33e6aa --- /dev/null +++ b/src/NzbDrone.Core/Notifications/ManualInteractionRequiredMessage.cs @@ -0,0 +1,25 @@ +using NzbDrone.Core.Download.TrackedDownloads; +using NzbDrone.Core.Music; +using NzbDrone.Core.Parser.Model; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Core.Notifications +{ + public class ManualInteractionRequiredMessage + { + public string Message { get; set; } + public Artist Artist { get; set; } + public RemoteAlbum RemoteAlbum { get; set; } + public TrackedDownload TrackedDownload { get; set; } + public QualityModel Quality { get; set; } + public string DownloadClientType { get; set; } + public string DownloadClientName { get; set; } + public string DownloadId { get; set; } + public GrabbedReleaseInfo Release { get; set; } + + public override string ToString() + { + return Message; + } + } +} diff --git a/src/NzbDrone.Core/Notifications/Notifiarr/Notifiarr.cs b/src/NzbDrone.Core/Notifications/Notifiarr/Notifiarr.cs index 041d8fdc9..538665b16 100644 --- a/src/NzbDrone.Core/Notifications/Notifiarr/Notifiarr.cs +++ b/src/NzbDrone.Core/Notifications/Notifiarr/Notifiarr.cs @@ -77,6 +77,11 @@ namespace NzbDrone.Core.Notifications.Notifiarr _proxy.SendNotification(BuildApplicationUpdatePayload(updateMessage), Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendNotification(BuildManualInteractionRequiredPayload(message), Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/NotificationBase.cs b/src/NzbDrone.Core/Notifications/NotificationBase.cs index 7b5da7f1d..16870c596 100644 --- a/src/NzbDrone.Core/Notifications/NotificationBase.cs +++ b/src/NzbDrone.Core/Notifications/NotificationBase.cs @@ -20,6 +20,7 @@ namespace NzbDrone.Core.Notifications protected const string IMPORT_FAILURE_TITLE = "Import Failed"; protected const string TRACK_RETAGGED_TITLE = "Track File Tags Updated"; protected const string APPLICATION_UPDATE_TITLE = "Application Updated"; + protected const string MANUAL_INTERACTION_REQUIRED_TITLE = "Manual Interaction"; protected const string ALBUM_GRABBED_TITLE_BRANDED = "Lidarr - " + ALBUM_GRABBED_TITLE; protected const string ALBUM_DOWNLOADED_TITLE_BRANDED = "Lidarr - " + ALBUM_DOWNLOADED_TITLE; @@ -31,6 +32,7 @@ namespace NzbDrone.Core.Notifications protected const string IMPORT_FAILURE_TITLE_BRANDED = "Lidarr - " + IMPORT_FAILURE_TITLE; protected const string TRACK_RETAGGED_TITLE_BRANDED = "Lidarr - " + TRACK_RETAGGED_TITLE; protected const string APPLICATION_UPDATE_TITLE_BRANDED = "Lidarr - " + APPLICATION_UPDATE_TITLE; + protected const string MANUAL_INTERACTION_REQUIRED_TITLE_BRANDED = "Lidarr - " + MANUAL_INTERACTION_REQUIRED_TITLE; public abstract string Name { get; } @@ -89,6 +91,10 @@ namespace NzbDrone.Core.Notifications { } + public virtual void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + } + public virtual void ProcessQueue() { } @@ -105,6 +111,7 @@ namespace NzbDrone.Core.Notifications public bool SupportsOnImportFailure => HasConcreteImplementation("OnImportFailure"); public bool SupportsOnTrackRetag => HasConcreteImplementation("OnTrackRetag"); public bool SupportsOnApplicationUpdate => HasConcreteImplementation("OnApplicationUpdate"); + public bool SupportsOnManualInteractionRequired => HasConcreteImplementation("OnManualInteractionRequired"); protected TSettings Settings => (TSettings)Definition.Settings; diff --git a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs index 1dc9d8026..aca60a965 100644 --- a/src/NzbDrone.Core/Notifications/NotificationDefinition.cs +++ b/src/NzbDrone.Core/Notifications/NotificationDefinition.cs @@ -16,6 +16,7 @@ namespace NzbDrone.Core.Notifications public bool OnImportFailure { get; set; } public bool OnTrackRetag { get; set; } public bool OnApplicationUpdate { get; set; } + public bool OnManualInteractionRequired { get; set; } public bool SupportsOnGrab { get; set; } public bool SupportsOnReleaseImport { get; set; } public bool SupportsOnUpgrade { get; set; } @@ -29,7 +30,8 @@ namespace NzbDrone.Core.Notifications public bool SupportsOnImportFailure { get; set; } public bool SupportsOnTrackRetag { get; set; } public bool SupportsOnApplicationUpdate { get; set; } + public bool SupportsOnManualInteractionRequired { get; set; } - public override bool Enable => OnGrab || OnReleaseImport || (OnReleaseImport && OnUpgrade) || OnAlbumDelete || OnArtistDelete || OnHealthIssue || OnHealthRestored || OnDownloadFailure || OnImportFailure || OnTrackRetag || OnApplicationUpdate; + public override bool Enable => OnGrab || OnReleaseImport || (OnReleaseImport && OnUpgrade) || OnAlbumDelete || OnArtistDelete || OnHealthIssue || OnHealthRestored || OnDownloadFailure || OnImportFailure || OnTrackRetag || OnApplicationUpdate || OnManualInteractionRequired; } } diff --git a/src/NzbDrone.Core/Notifications/NotificationFactory.cs b/src/NzbDrone.Core/Notifications/NotificationFactory.cs index c9df71ca5..539460629 100644 --- a/src/NzbDrone.Core/Notifications/NotificationFactory.cs +++ b/src/NzbDrone.Core/Notifications/NotificationFactory.cs @@ -22,6 +22,7 @@ namespace NzbDrone.Core.Notifications List OnImportFailureEnabled(); List OnTrackRetagEnabled(); List OnApplicationUpdateEnabled(); + List OnManualInteractionEnabled(); } public class NotificationFactory : ProviderFactory, INotificationFactory @@ -91,6 +92,11 @@ namespace NzbDrone.Core.Notifications return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnApplicationUpdate).ToList(); } + public List OnManualInteractionEnabled() + { + return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnManualInteractionRequired).ToList(); + } + public override void SetProviderCharacteristics(INotification provider, NotificationDefinition definition) { base.SetProviderCharacteristics(provider, definition); @@ -107,6 +113,7 @@ namespace NzbDrone.Core.Notifications definition.SupportsOnImportFailure = provider.SupportsOnImportFailure; definition.SupportsOnTrackRetag = provider.SupportsOnTrackRetag; definition.SupportsOnApplicationUpdate = provider.SupportsOnApplicationUpdate; + definition.SupportsOnManualInteractionRequired = provider.SupportsOnManualInteractionRequired; } } } diff --git a/src/NzbDrone.Core/Notifications/NotificationService.cs b/src/NzbDrone.Core/Notifications/NotificationService.cs index 18b1e0b75..49f2e7f62 100644 --- a/src/NzbDrone.Core/Notifications/NotificationService.cs +++ b/src/NzbDrone.Core/Notifications/NotificationService.cs @@ -4,6 +4,7 @@ using System.Linq; using NLog; using NzbDrone.Common.Extensions; using NzbDrone.Core.Download; +using NzbDrone.Core.Download.TrackedDownloads; using NzbDrone.Core.HealthCheck; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles.Events; @@ -28,6 +29,7 @@ namespace NzbDrone.Core.Notifications IHandle, IHandle, IHandle, + IHandle, IHandleAsync, IHandleAsync, IHandleAsync @@ -376,6 +378,39 @@ namespace NzbDrone.Core.Notifications } } + public void Handle(ManualInteractionRequiredEvent message) + { + var manualInteractionMessage = new ManualInteractionRequiredMessage + { + Message = GetMessage(message.Album.Artist, message.Album.Albums, message.Album.ParsedAlbumInfo.Quality), + Artist = message.Album.Artist, + Quality = message.Album.ParsedAlbumInfo.Quality, + RemoteAlbum = message.Album, + TrackedDownload = message.TrackedDownload, + DownloadClientType = message.TrackedDownload.DownloadItem.DownloadClientInfo.Type, + DownloadClientName = message.TrackedDownload.DownloadItem.DownloadClientInfo.Name, + DownloadId = message.TrackedDownload.DownloadItem.DownloadId, + Release = message.Release + }; + + foreach (var notification in _notificationFactory.OnManualInteractionEnabled()) + { + try + { + if (!ShouldHandleArtist(notification.Definition, message.Album.Artist)) + { + continue; + } + + notification.OnManualInteractionRequired(manualInteractionMessage); + } + catch (Exception ex) + { + _logger.Error(ex, "Unable to send OnManualInteractionRequired notification to {0}", notification.Definition.Name); + } + } + } + public void HandleAsync(RenameCompletedEvent message) { ProcessQueue(); diff --git a/src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs b/src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs index 680338b49..71837c327 100644 --- a/src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs +++ b/src/NzbDrone.Core/Notifications/Ntfy/Ntfy.cs @@ -63,6 +63,11 @@ namespace NzbDrone.Core.Notifications.Ntfy _proxy.SendNotification(APPLICATION_UPDATE_TITLE_BRANDED, updateMessage.Message, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE_BRANDED, message.Message, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs index 5560fa27a..ce5dd600f 100644 --- a/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs +++ b/src/NzbDrone.Core/Notifications/Prowl/Prowl.cs @@ -51,6 +51,11 @@ namespace NzbDrone.Core.Notifications.Prowl _prowlProxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _prowlProxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE, message.Message, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs b/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs index b12cf6a95..5504074d9 100644 --- a/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs +++ b/src/NzbDrone.Core/Notifications/PushBullet/PushBullet.cs @@ -64,6 +64,11 @@ namespace NzbDrone.Core.Notifications.PushBullet _proxy.SendNotification(APPLICATION_UPDATE_TITLE_BRANDED, updateMessage.Message, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE_BRANDED, message.Message, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs b/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs index a2e296b23..dcd1743e8 100644 --- a/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs +++ b/src/NzbDrone.Core/Notifications/Pushover/Pushover.cs @@ -61,6 +61,11 @@ namespace NzbDrone.Core.Notifications.Pushover _proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE, message.Message, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs b/src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs index c1149b411..e317d61e1 100644 --- a/src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs +++ b/src/NzbDrone.Core/Notifications/SendGrid/SendGrid.cs @@ -64,6 +64,11 @@ namespace NzbDrone.Core.Notifications.SendGrid _proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE, message.Message, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/Simplepush/Simplepush.cs b/src/NzbDrone.Core/Notifications/Simplepush/Simplepush.cs index 06ca177d1..a5dd12db8 100644 --- a/src/NzbDrone.Core/Notifications/Simplepush/Simplepush.cs +++ b/src/NzbDrone.Core/Notifications/Simplepush/Simplepush.cs @@ -61,6 +61,11 @@ namespace NzbDrone.Core.Notifications.Simplepush _proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE, message.Message, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/Slack/Slack.cs b/src/NzbDrone.Core/Notifications/Slack/Slack.cs index 019195710..f0716e2ac 100644 --- a/src/NzbDrone.Core/Notifications/Slack/Slack.cs +++ b/src/NzbDrone.Core/Notifications/Slack/Slack.cs @@ -202,6 +202,23 @@ namespace NzbDrone.Core.Notifications.Slack _proxy.SendPayload(payload, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + var attachments = new List + { + new Attachment + { + Title = Environment.MachineName, + Text = message.Message, + Color = "warning" + } + }; + + var payload = CreatePayload("Manual Interaction Required", attachments); + + _proxy.SendPayload(payload, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs b/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs index f95de3338..85ee879d8 100644 --- a/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs +++ b/src/NzbDrone.Core/Notifications/Telegram/Telegram.cs @@ -61,6 +61,11 @@ namespace NzbDrone.Core.Notifications.Telegram _proxy.SendNotification(APPLICATION_UPDATE_TITLE, updateMessage.Message, Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendNotification(MANUAL_INTERACTION_REQUIRED_TITLE, message.Message, Settings); + } + public override ValidationResult Test() { var failures = new List(); diff --git a/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs b/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs index ef423b316..d126cb07c 100644 --- a/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs +++ b/src/NzbDrone.Core/Notifications/Twitter/Twitter.cs @@ -63,6 +63,11 @@ namespace NzbDrone.Core.Notifications.Twitter _twitterService.SendNotification($"Application Updated: {updateMessage.Message}", Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _twitterService.SendNotification($"Manual Interaction Required: {message.Message}", Settings); + } + public override object RequestAction(string action, IDictionary query) { if (action == "startOAuth") diff --git a/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs b/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs index 3bce52990..a3c706490 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/Webhook.cs @@ -75,6 +75,11 @@ namespace NzbDrone.Core.Notifications.Webhook _proxy.SendWebhook(BuildApplicationUpdatePayload(updateMessage), Settings); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + _proxy.SendWebhook(BuildManualInteractionRequiredPayload(message), Settings); + } + public override string Name => "Webhook"; public override ValidationResult Test() diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookBase.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookBase.cs index f4450612f..507c8794e 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/WebhookBase.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookBase.cs @@ -194,6 +194,28 @@ namespace NzbDrone.Core.Notifications.Webhook }; } + protected WebhookManualInteractionPayload BuildManualInteractionRequiredPayload(ManualInteractionRequiredMessage message) + { + var remoteAlbum = message.RemoteAlbum; + var quality = message.Quality; + + return new WebhookManualInteractionPayload + { + EventType = WebhookEventType.ManualInteractionRequired, + InstanceName = _configFileProvider.InstanceName, + ApplicationUrl = _configService.ApplicationUrl, + Artist = new WebhookArtist(message.Artist), + Albums = remoteAlbum.Albums.Select(x => new WebhookAlbum(x)).ToList(), + DownloadInfo = new WebhookDownloadClientItem(quality, message.TrackedDownload.DownloadItem), + DownloadClient = message.DownloadClientName, + DownloadClientType = message.DownloadClientType, + DownloadId = message.DownloadId, + + // CustomFormatInfo = new WebhookCustomFormatInfo(remoteAlbum.CustomFormats, remoteAlbum.CustomFormatScore), + // Release = new WebhookGrabbedRelease(message.Release) + }; + } + protected WebhookPayload BuildTestPayload() { return new WebhookGrabPayload diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookDownloadClientItem.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookDownloadClientItem.cs new file mode 100644 index 000000000..7d5be81c1 --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookDownloadClientItem.cs @@ -0,0 +1,26 @@ +using NzbDrone.Core.Download; +using NzbDrone.Core.Qualities; + +namespace NzbDrone.Core.Notifications.Webhook +{ + public class WebhookDownloadClientItem + { + public WebhookDownloadClientItem() + { + } + + public WebhookDownloadClientItem(QualityModel quality, DownloadClientItem downloadClientItem) + { + Quality = quality.Quality.Name; + QualityVersion = quality.Revision.Version; + Title = downloadClientItem.Title; + Size = downloadClientItem.TotalSize; + } + + public string Quality { get; set; } + public int QualityVersion { get; set; } + public string Title { get; set; } + public string Indexer { get; set; } + public long Size { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookEventType.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookEventType.cs index f598d554c..e680edfbd 100644 --- a/src/NzbDrone.Core/Notifications/Webhook/WebhookEventType.cs +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookEventType.cs @@ -19,6 +19,7 @@ namespace NzbDrone.Core.Notifications.Webhook Health, Retag, ApplicationUpdate, - HealthRestored + HealthRestored, + ManualInteractionRequired } } diff --git a/src/NzbDrone.Core/Notifications/Webhook/WebhookManualInteractionPayload.cs b/src/NzbDrone.Core/Notifications/Webhook/WebhookManualInteractionPayload.cs new file mode 100644 index 000000000..bc14e5ded --- /dev/null +++ b/src/NzbDrone.Core/Notifications/Webhook/WebhookManualInteractionPayload.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace NzbDrone.Core.Notifications.Webhook +{ + public class WebhookManualInteractionPayload : WebhookPayload + { + public WebhookArtist Artist { get; set; } + public List Albums { get; set; } + public WebhookDownloadClientItem DownloadInfo { get; set; } + public string DownloadClient { get; set; } + public string DownloadClientType { get; set; } + public string DownloadId { get; set; } + + // public WebhookCustomFormatInfo CustomFormatInfo { get; set; } + // public WebhookGrabbedRelease Release { get; set; } + } +} diff --git a/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs b/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs index e08ec1b28..c20daea0e 100644 --- a/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs +++ b/src/NzbDrone.Core/Notifications/Xbmc/Xbmc.cs @@ -62,6 +62,11 @@ namespace NzbDrone.Core.Notifications.Xbmc Notify(Settings, APPLICATION_UPDATE_TITLE_BRANDED, updateMessage.Message); } + public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message) + { + Notify(Settings, MANUAL_INTERACTION_REQUIRED_TITLE, message.Message); + } + public override string Name => "Kodi"; public override ValidationResult Test() diff --git a/src/NzbDrone.Core/Parser/Model/GrabbedReleaseInfo.cs b/src/NzbDrone.Core/Parser/Model/GrabbedReleaseInfo.cs new file mode 100644 index 000000000..858bf34a0 --- /dev/null +++ b/src/NzbDrone.Core/Parser/Model/GrabbedReleaseInfo.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.History; + +namespace NzbDrone.Core.Parser.Model +{ + public class GrabbedReleaseInfo + { + public string Title { get; set; } + public string Indexer { get; set; } + public long Size { get; set; } + + public List AlbumsIds { get; set; } + + public GrabbedReleaseInfo(List grabbedHistories) + { + var grabbedHistory = grabbedHistories.MaxBy(h => h.Date); + var albumsIds = grabbedHistories.Select(h => h.AlbumId).Distinct().ToList(); + + grabbedHistory.Data.TryGetValue("indexer", out var indexer); + grabbedHistory.Data.TryGetValue("size", out var sizeString); + long.TryParse(sizeString, out var size); + + Title = grabbedHistory.SourceTitle; + Indexer = indexer; + Size = size; + AlbumsIds = albumsIds; + } + } +}