diff --git a/frontend/src/Settings/Notifications/Notifications/Notification.js b/frontend/src/Settings/Notifications/Notifications/Notification.js
index baf029184..a525a4e5f 100644
--- a/frontend/src/Settings/Notifications/Notifications/Notification.js
+++ b/frontend/src/Settings/Notifications/Notifications/Notification.js
@@ -65,6 +65,7 @@ class Notification extends Component {
onHealthIssue,
onHealthRestored,
onApplicationUpdate,
+ onManualInteractionRequired,
supportsOnGrab,
supportsOnDownload,
supportsOnUpgrade,
@@ -75,7 +76,8 @@ class Notification extends Component {
supportsOnEpisodeFileDeleteForUpgrade,
supportsOnHealthIssue,
supportsOnHealthRestored,
- supportsOnApplicationUpdate
+ supportsOnApplicationUpdate,
+ supportsOnManualInteractionRequired
} = this.props;
return (
@@ -177,7 +179,15 @@ class Notification extends Component {
}
{
- !onGrab && !onDownload && !onRename && !onHealthIssue && !onHealthRestored && !onApplicationUpdate && !onSeriesDelete && !onEpisodeFileDelete ?
+ supportsOnManualInteractionRequired && onManualInteractionRequired ?
+ :
+ null
+ }
+
+ {
+ !onGrab && !onDownload && !onRename && !onHealthIssue && !onHealthRestored && !onApplicationUpdate && !onSeriesDelete && !onEpisodeFileDelete && !onManualInteractionRequired ?
+
+
+
+
diff --git a/frontend/src/Store/Actions/Settings/notifications.js b/frontend/src/Store/Actions/Settings/notifications.js
index be61173fd..f41e031c3 100644
--- a/frontend/src/Store/Actions/Settings/notifications.js
+++ b/frontend/src/Store/Actions/Settings/notifications.js
@@ -111,6 +111,7 @@ export default {
selectedSchema.onEpisodeFileDelete = selectedSchema.supportsOnEpisodeFileDelete;
selectedSchema.onEpisodeFileDeleteForUpgrade = selectedSchema.supportsOnEpisodeFileDeleteForUpgrade;
selectedSchema.onApplicationUpdate = selectedSchema.supportsOnApplicationUpdate;
+ selectedSchema.onManualInteractionRequired = selectedSchema.supportsOnManualInteractionRequired;
return selectedSchema;
});
diff --git a/src/NzbDrone.Core.Test/NotificationTests/NotificationBaseFixture.cs b/src/NzbDrone.Core.Test/NotificationTests/NotificationBaseFixture.cs
index 964ec4e00..b773ada75 100644
--- a/src/NzbDrone.Core.Test/NotificationTests/NotificationBaseFixture.cs
+++ b/src/NzbDrone.Core.Test/NotificationTests/NotificationBaseFixture.cs
@@ -88,6 +88,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
@@ -128,6 +133,7 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnHealthIssue.Should().BeTrue();
notification.SupportsOnHealthRestored.Should().BeTrue();
notification.SupportsOnApplicationUpdate.Should().BeTrue();
+ notification.SupportsOnManualInteractionRequired.Should().BeTrue();
}
[Test]
@@ -145,6 +151,7 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnHealthIssue.Should().BeFalse();
notification.SupportsOnHealthRestored.Should().BeFalse();
notification.SupportsOnApplicationUpdate.Should().BeFalse();
+ notification.SupportsOnManualInteractionRequired.Should().BeFalse();
}
}
}
diff --git a/src/NzbDrone.Core/Datastore/Migration/177_add_on_manual_interaction_required_to_notifications.cs b/src/NzbDrone.Core/Datastore/Migration/177_add_on_manual_interaction_required_to_notifications.cs
new file mode 100644
index 000000000..7e0636893
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/177_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(177)]
+ 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 a97b90406..7768157d0 100644
--- a/src/NzbDrone.Core/Datastore/TableMapping.cs
+++ b/src/NzbDrone.Core/Datastore/TableMapping.cs
@@ -94,7 +94,8 @@ namespace NzbDrone.Core.Datastore
.Ignore(i => i.SupportsOnEpisodeFileDeleteForUpgrade)
.Ignore(i => i.SupportsOnHealthIssue)
.Ignore(i => i.SupportsOnHealthRestored)
- .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 e56f6e639..ff5a71fd3 100644
--- a/src/NzbDrone.Core/Download/CompletedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/CompletedDownloadService.cs
@@ -105,6 +105,15 @@ namespace NzbDrone.Core.Download
if (seriesMatchType == SeriesMatchType.Id && releaseSource != ReleaseSourceType.InteractiveSearch)
{
trackedDownload.Warn("Found matching series via grab history, but release was matched to series by ID. Automatic import is not possible. See the FAQ for details.");
+
+ if (!trackedDownload.HasNotifiedManualInteractionRequired)
+ {
+ trackedDownload.HasNotifiedManualInteractionRequired = true;
+
+ var manualInteractionEvent = new ManualInteractionRequiredEvent(trackedDownload);
+ _eventAggregator.PublishEvent(manualInteractionEvent);
+ }
+
return;
}
}
diff --git a/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs b/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs
index 0b8561744..5e0d10fc8 100644
--- a/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs
+++ b/src/NzbDrone.Core/Download/EpisodeGrabbedEvent.cs
@@ -1,4 +1,4 @@
-using NzbDrone.Common.Messaging;
+using NzbDrone.Common.Messaging;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Download
diff --git a/src/NzbDrone.Core/Download/ManualInteractionRequiredEvent.cs b/src/NzbDrone.Core/Download/ManualInteractionRequiredEvent.cs
new file mode 100644
index 000000000..c7fb39b8e
--- /dev/null
+++ b/src/NzbDrone.Core/Download/ManualInteractionRequiredEvent.cs
@@ -0,0 +1,18 @@
+using NzbDrone.Common.Messaging;
+using NzbDrone.Core.Download.TrackedDownloads;
+using NzbDrone.Core.Parser.Model;
+
+namespace NzbDrone.Core.Download
+{
+ public class ManualInteractionRequiredEvent : IEvent
+ {
+ public RemoteEpisode Episode { get; private set; }
+ public TrackedDownload TrackedDownload { get; private set; }
+
+ public ManualInteractionRequiredEvent(TrackedDownload trackedDownload)
+ {
+ TrackedDownload = trackedDownload;
+ Episode = trackedDownload.RemoteEpisode;
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/DownloadMonitoringService.cs
index 02119e7f6..69523ab36 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
{
@@ -170,6 +171,11 @@ namespace NzbDrone.Core.Download.TrackedDownloads
_refreshDebounce.Execute();
}
+ public void Handle(ManualInteractionRequiredEvent message)
+ {
+ _refreshDebounce.Execute();
+ }
+
public void Handle(EpisodeImportedEvent message)
{
_refreshDebounce.Execute();
diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs
index 2dfd4baee..0b20d3f6f 100644
--- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs
+++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownload.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
@@ -16,6 +16,7 @@ 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()
{
diff --git a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs
index 30bdc1e83..da0658895 100644
--- a/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs
+++ b/src/NzbDrone.Core/Download/TrackedDownloads/TrackedDownloadService.cs
@@ -103,7 +103,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/Notifications/Apprise/Apprise.cs b/src/NzbDrone.Core/Notifications/Apprise/Apprise.cs
index c42d1cb63..db39aadca 100644
--- a/src/NzbDrone.Core/Notifications/Apprise/Apprise.cs
+++ b/src/NzbDrone.Core/Notifications/Apprise/Apprise.cs
@@ -57,6 +57,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 f04eb163c..ef5850637 100644
--- a/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs
+++ b/src/NzbDrone.Core/Notifications/Boxcar/Boxcar.cs
@@ -56,6 +56,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 6cb38fa90..e48269e2b 100644
--- a/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs
+++ b/src/NzbDrone.Core/Notifications/CustomScript/CustomScript.cs
@@ -303,6 +303,32 @@ namespace NzbDrone.Core.Notifications.CustomScript
ExecuteScript(environmentVariables);
}
+ public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message)
+ {
+ var series = message.Series;
+ var environmentVariables = new StringDictionary();
+
+ environmentVariables.Add("Sonarr_EventType", "Download");
+ environmentVariables.Add("Sonarr_InstanceName", _configFileProvider.InstanceName);
+ environmentVariables.Add("Sonarr_ApplicationUrl", _configService.ApplicationUrl);
+ environmentVariables.Add("Sonarr_Series_Id", series.Id.ToString());
+ environmentVariables.Add("Sonarr_Series_Title", series.Title);
+ environmentVariables.Add("Sonarr_Series_TitleSlug", series.TitleSlug);
+ environmentVariables.Add("Sonarr_Series_Path", series.Path);
+ environmentVariables.Add("Sonarr_Series_TvdbId", series.TvdbId.ToString());
+ environmentVariables.Add("Sonarr_Series_TvMazeId", series.TvMazeId.ToString());
+ environmentVariables.Add("Sonarr_Series_ImdbId", series.ImdbId ?? string.Empty);
+ environmentVariables.Add("Sonarr_Series_Type", series.SeriesType.ToString());
+ environmentVariables.Add("Sonarr_Series_Year", series.Year.ToString());
+ environmentVariables.Add("Sonarr_Download_Client", message.DownloadClientName ?? string.Empty);
+ environmentVariables.Add("Sonarr_Download_Client_Type", message.DownloadClientType ?? string.Empty);
+ environmentVariables.Add("Sonarr_Download_Id", message.DownloadId ?? string.Empty);
+ environmentVariables.Add("Sonarr_Download_Size", message.TrackedDownload.DownloadItem.TotalSize.ToString());
+ environmentVariables.Add("Sonarr_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 8db72339d..05965b2b7 100644
--- a/src/NzbDrone.Core/Notifications/Discord/Discord.cs
+++ b/src/NzbDrone.Core/Notifications/Discord/Discord.cs
@@ -375,6 +375,96 @@ namespace NzbDrone.Core.Notifications.Discord
_proxy.SendPayload(payload, Settings);
}
+ public override void OnManualInteractionRequired(ManualInteractionRequiredMessage message)
+ {
+ var series = message.Series;
+ var episodes = message.Episode.Episodes;
+
+ var embed = new Embed
+ {
+ Author = new DiscordAuthor
+ {
+ Name = Settings.Author.IsNullOrWhiteSpace() ? Environment.MachineName : Settings.Author,
+ IconUrl = "https://raw.githubusercontent.com/Sonarr/Sonarr/develop/Logo/256.png"
+ },
+ Url = $"http://thetvdb.com/?tab=series&id={series.TvdbId}",
+ Description = "Manual interaction needed",
+ Title = GetTitle(series, episodes),
+ 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 = series.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Poster)?.Url
+ };
+ }
+
+ if (Settings.ManualInteractionFields.Contains((int)DiscordGrabFieldType.Fanart))
+ {
+ embed.Image = new DiscordImage
+ {
+ Url = series.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 = episodes.First().Overview ?? "";
+ discordField.Name = "Overview";
+ discordField.Value = overview.Length <= 300 ? overview : $"{overview.AsSpan(0, 300)}...";
+ break;
+ case DiscordManualInteractionFieldType.Rating:
+ discordField.Name = "Rating";
+ discordField.Value = episodes.First().Ratings.Value.ToString();
+ break;
+ case DiscordManualInteractionFieldType.Genres:
+ discordField.Name = "Genres";
+ discordField.Value = series.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.Episode.ParsedEpisodeInfo.ReleaseGroup;
+ break;
+ case DiscordManualInteractionFieldType.Size:
+ discordField.Name = "Size";
+ discordField.Value = BytesToString(message.TrackedDownload.DownloadItem.TotalSize);
+ 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(series);
+ break;
+ }
+
+ if (discordField.Name.IsNotNullOrWhiteSpace() && discordField.Value.IsNotNullOrWhiteSpace())
+ {
+ embed.Fields.Add(discordField);
+ }
+ }
+
+ var payload = CreatePayload(null, new List