diff --git a/Jellyfin.Plugin.Webhook/Configuration/Web/config.js b/Jellyfin.Plugin.Webhook/Configuration/Web/config.js
index f42bd07..8eeb515 100644
--- a/Jellyfin.Plugin.Webhook/Configuration/Web/config.js
+++ b/Jellyfin.Plugin.Webhook/Configuration/Web/config.js
@@ -40,6 +40,7 @@ export default function (view) {
template: document.querySelector("#template-notification-type"),
values: {
"ItemAdded": "Item Added",
+ "ItemDeleted": "Item Deleted",
"PlaybackStart": "Playback Start",
"PlaybackProgress": "Playback Progress",
"PlaybackStop": "Playback Stop",
diff --git a/Jellyfin.Plugin.Webhook/Destinations/NotificationType.cs b/Jellyfin.Plugin.Webhook/Destinations/NotificationType.cs
index 27fce7f..b647b0b 100644
--- a/Jellyfin.Plugin.Webhook/Destinations/NotificationType.cs
+++ b/Jellyfin.Plugin.Webhook/Destinations/NotificationType.cs
@@ -123,5 +123,10 @@ public enum NotificationType
///
/// User data saved.
///
- UserDataSaved = 23
+ UserDataSaved = 23,
+
+ ///
+ /// Item Deleted notification.
+ ///
+ ItemDeleted = 24
}
diff --git a/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/IItemDeletedManager.cs b/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/IItemDeletedManager.cs
new file mode 100644
index 0000000..b80e569
--- /dev/null
+++ b/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/IItemDeletedManager.cs
@@ -0,0 +1,22 @@
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+
+namespace Jellyfin.Plugin.Webhook.Notifiers.ItemDeletedNotifier;
+
+///
+/// Item deleted manager interface.
+///
+public interface IItemDeletedManager
+{
+ ///
+ /// Process the current queue.
+ ///
+ /// A representing the asynchronous operation.
+ public Task ProcessItemsAsync();
+
+ ///
+ /// Add item to process queue.
+ ///
+ /// The deleted item.
+ public void AddItem(BaseItem item);
+}
\ No newline at end of file
diff --git a/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/ItemDeletedManager.cs b/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/ItemDeletedManager.cs
new file mode 100644
index 0000000..b47f4f2
--- /dev/null
+++ b/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/ItemDeletedManager.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Collections.Concurrent;
+using System.Threading.Tasks;
+using Jellyfin.Plugin.Webhook.Destinations;
+using Jellyfin.Plugin.Webhook.Helpers;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Jellyfin.Plugin.Webhook.Notifiers.ItemDeletedNotifier;
+
+///
+public class ItemDeletedManager : IItemDeletedManager
+{
+ private readonly ILogger _logger;
+ private readonly ILibraryManager _libraryManager;
+ private readonly IServerApplicationHost _applicationHost;
+ private readonly ConcurrentDictionary _itemProcessQueue;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public ItemDeletedManager(
+ ILogger logger,
+ ILibraryManager libraryManager,
+ IServerApplicationHost applicationHost)
+ {
+ _logger = logger;
+ _libraryManager = libraryManager;
+ _applicationHost = applicationHost;
+ _itemProcessQueue = new ConcurrentDictionary();
+ }
+
+ ///
+ public async Task ProcessItemsAsync()
+ {
+ _logger.LogDebug("ProcessItemsAsync");
+ // Attempt to process all items in queue.
+ if (!_itemProcessQueue.IsEmpty)
+ {
+ var scope = _applicationHost.ServiceProvider!.CreateAsyncScope();
+ await using (scope.ConfigureAwait(false))
+ {
+ var webhookSender = scope.ServiceProvider.GetRequiredService();
+ foreach (var (key, item) in _itemProcessQueue)
+ {
+ if (item != null)
+ {
+ _logger.LogDebug("Item {ItemName}", item.Name);
+
+ // Skip notification if item type is Studio
+ if (item.GetType().Name == "Studio")
+ {
+ _logger.LogDebug("Skipping notification for item type Studio");
+ _itemProcessQueue.TryRemove(key, out _);
+ continue;
+ }
+
+ _logger.LogDebug("Notifying for {ItemName}", item.Name);
+
+ // Send notification to each configured destination.
+ var dataObject = DataObjectHelpers
+ .GetBaseDataObject(_applicationHost, NotificationType.ItemDeleted)
+ .AddBaseItemData(item);
+
+ var itemType = item.GetType();
+ await webhookSender.SendNotification(NotificationType.ItemDeleted, dataObject, itemType)
+ .ConfigureAwait(false);
+
+ // Remove item from queue.
+ _itemProcessQueue.TryRemove(key, out _);
+ }
+ }
+ }
+ }
+ }
+
+ ///
+ public void AddItem(BaseItem item)
+ {
+ _itemProcessQueue.TryAdd(item.Id, item);
+ _logger.LogDebug("Queued {ItemName} for notification", item.Name);
+ }
+}
diff --git a/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/ItemDeletedNotifierEntryPoint.cs b/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/ItemDeletedNotifierEntryPoint.cs
new file mode 100644
index 0000000..f68245b
--- /dev/null
+++ b/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/ItemDeletedNotifierEntryPoint.cs
@@ -0,0 +1,53 @@
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Library;
+using Microsoft.Extensions.Hosting;
+
+namespace Jellyfin.Plugin.Webhook.Notifiers.ItemDeletedNotifier;
+
+///
+/// Notifier when a library item is deleted.
+///
+public class ItemDeletedNotifierEntryPoint : IHostedService
+{
+ private readonly IItemDeletedManager _itemDeletedManager;
+ private readonly ILibraryManager _libraryManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public ItemDeletedNotifierEntryPoint(
+ IItemDeletedManager itemDeletedManager,
+ ILibraryManager libraryManager)
+ {
+ _itemDeletedManager = itemDeletedManager;
+ _libraryManager = libraryManager;
+ }
+
+ private void ItemDeletedHandler(object? sender, ItemChangeEventArgs itemChangeEventArgs)
+ {
+ // Never notify on virtual items.
+ if (itemChangeEventArgs.Item.IsVirtualItem)
+ {
+ return;
+ }
+
+ _itemDeletedManager.AddItem(itemChangeEventArgs.Item);
+ }
+
+ ///
+ public Task StartAsync(CancellationToken cancellationToken)
+ {
+ _libraryManager.ItemRemoved += ItemDeletedHandler;
+ return Task.CompletedTask;
+ }
+
+ ///
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ _libraryManager.ItemRemoved -= ItemDeletedHandler;
+ return Task.CompletedTask;
+ }
+}
\ No newline at end of file
diff --git a/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/ItemDeletedScheduledTask.cs b/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/ItemDeletedScheduledTask.cs
new file mode 100644
index 0000000..4e88336
--- /dev/null
+++ b/Jellyfin.Plugin.Webhook/Notifiers/ItemDeletedNotifier/ItemDeletedScheduledTask.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Globalization;
+using MediaBrowser.Model.Tasks;
+
+namespace Jellyfin.Plugin.Webhook.Notifiers.ItemDeletedNotifier;
+
+///
+/// Scheduled task that processes item deleted events.
+///
+public class ItemDeletedScheduledTask : IScheduledTask, IConfigurableScheduledTask
+{
+ private const int RecheckIntervalSec = 30;
+ private readonly IItemDeletedManager _itemDeletedManager;
+ private readonly ILocalizationManager _localizationManager;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public ItemDeletedScheduledTask(
+ IItemDeletedManager itemDeletedManager,
+ ILocalizationManager localizationManager)
+ {
+ _itemDeletedManager = itemDeletedManager;
+ _localizationManager = localizationManager;
+ }
+
+ ///
+ public string Name => "Webhook Item Deleted Notifier";
+
+ ///
+ public string Key => "WebhookItemDeleted";
+
+ ///
+ public string Description => "Processes item deleted queue";
+
+ ///
+ public string Category => _localizationManager.GetLocalizedString("TasksLibraryCategory");
+
+ ///
+ public bool IsHidden => false;
+
+ ///
+ public bool IsEnabled => true;
+
+ ///
+ public bool IsLogged => false;
+
+ ///
+ public Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken)
+ {
+ return _itemDeletedManager.ProcessItemsAsync();
+ }
+
+ ///
+ public IEnumerable GetDefaultTriggers()
+ {
+ return new[]
+ {
+ new TaskTriggerInfo
+ {
+ Type = TaskTriggerInfo.TriggerInterval,
+ IntervalTicks = TimeSpan.FromSeconds(RecheckIntervalSec).Ticks
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/Jellyfin.Plugin.Webhook/PluginServiceRegistrator.cs b/Jellyfin.Plugin.Webhook/PluginServiceRegistrator.cs
index 131f790..9550a38 100644
--- a/Jellyfin.Plugin.Webhook/PluginServiceRegistrator.cs
+++ b/Jellyfin.Plugin.Webhook/PluginServiceRegistrator.cs
@@ -13,6 +13,7 @@
using Jellyfin.Plugin.Webhook.Helpers;
using Jellyfin.Plugin.Webhook.Notifiers;
using Jellyfin.Plugin.Webhook.Notifiers.ItemAddedNotifier;
+using Jellyfin.Plugin.Webhook.Notifiers.ItemDeletedNotifier;
using Jellyfin.Plugin.Webhook.Notifiers.UserDataSavedNotifier;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller;
@@ -58,6 +59,7 @@ public void RegisterServices(IServiceCollection serviceCollection, IServerApplic
// Library consumers.
serviceCollection.AddScoped, SubtitleDownloadFailureNotifier>();
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
// Security consumers.
serviceCollection.AddScoped, AuthenticationFailureNotifier>();
@@ -90,6 +92,7 @@ public void RegisterServices(IServiceCollection serviceCollection, IServerApplic
serviceCollection.AddHostedService();
serviceCollection.AddHostedService();
+ serviceCollection.AddHostedService();
serviceCollection.AddHostedService();
}
}