diff --git a/Jellyfin.Plugin.SmartNotify/Notifiers/SmartNotifyBackgroundService.cs b/Jellyfin.Plugin.SmartNotify/Notifiers/SmartNotifyBackgroundService.cs
index d3c4c4d..6bb1077 100644
--- a/Jellyfin.Plugin.SmartNotify/Notifiers/SmartNotifyBackgroundService.cs
+++ b/Jellyfin.Plugin.SmartNotify/Notifiers/SmartNotifyBackgroundService.cs
@@ -254,7 +254,12 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
return;
}
- _logger.LogDebug("Processing {Count} pending notifications", pendingNotifications.Count);
+ _logger.LogInformation(
+ "Processing {Count} pending notifications (delay={Delay}min, grouping={Grouping}min, cutoff={Cutoff})",
+ pendingNotifications.Count,
+ delayMinutes,
+ groupingWindowMinutes,
+ cutoff);
// Group episodes by series
var episodesBySeries = pendingNotifications
@@ -262,6 +267,17 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
.GroupBy(n => n.SeriesId!)
.ToList();
+ // Log unmatched notifications (neither episode with series nor movie)
+ var unmatchedCount = pendingNotifications.Count
+ - pendingNotifications.Count(n => n.ItemType == "Episode" && !string.IsNullOrEmpty(n.SeriesId))
+ - pendingNotifications.Count(n => n.ItemType == "Movie");
+ if (unmatchedCount > 0)
+ {
+ _logger.LogWarning(
+ "{Count} notifications are neither episodes (with SeriesId) nor movies and will be skipped",
+ unmatchedCount);
+ }
+
// Process each series group
foreach (var seriesGroup in episodesBySeries)
{
@@ -271,19 +287,34 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
// Only process if the oldest notification is outside the grouping window
if (oldestInGroup > groupingCutoff)
{
- _logger.LogDebug(
- "Waiting for grouping window for series {SeriesId}, oldest: {Oldest}",
+ _logger.LogInformation(
+ "Waiting for grouping window for series {SeriesName} ({SeriesId}), oldest queued: {Oldest}, grouping cutoff: {Cutoff}",
+ seriesGroup.First().SeriesName,
seriesGroup.Key,
- oldestInGroup);
+ oldestInGroup,
+ groupingCutoff);
continue;
}
- await _discordService.SendGroupedEpisodeNotificationAsync(
+ _logger.LogInformation(
+ "Sending grouped notification for {SeriesName}: {Count} episodes",
+ seriesGroup.First().SeriesName,
+ seriesGroup.Count());
+
+ var success = await _discordService.SendGroupedEpisodeNotificationAsync(
seriesGroup,
CancellationToken.None);
- var idsToRemove = seriesGroup.Select(n => n.Id).ToList();
- _historyService.RemoveNotifications(idsToRemove);
+ if (success)
+ {
+ var idsToRemove = seriesGroup.Select(n => n.Id).ToList();
+ _historyService.RemoveNotifications(idsToRemove);
+ _logger.LogInformation("Removed {Count} processed episode notifications from queue", idsToRemove.Count);
+ }
+ else
+ {
+ _logger.LogWarning("Discord send failed for {SeriesName}, keeping notifications in queue for retry", seriesGroup.First().SeriesName);
+ }
}
// Process movies
@@ -293,8 +324,19 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
foreach (var movie in movies)
{
- await _discordService.SendMovieNotificationAsync(movie, CancellationToken.None);
- _historyService.RemoveNotifications(new[] { movie.Id });
+ _logger.LogInformation("Sending movie notification for: {Name}", movie.Name);
+
+ var success = await _discordService.SendMovieNotificationAsync(movie, CancellationToken.None);
+
+ if (success)
+ {
+ _historyService.RemoveNotifications(new[] { movie.Id });
+ _logger.LogInformation("Removed processed movie notification from queue: {Name}", movie.Name);
+ }
+ else
+ {
+ _logger.LogWarning("Discord send failed for movie {Name}, keeping in queue for retry", movie.Name);
+ }
}
}
catch (Exception ex)
diff --git a/Jellyfin.Plugin.SmartNotify/Services/DiscordNotificationService.cs b/Jellyfin.Plugin.SmartNotify/Services/DiscordNotificationService.cs
index a8be990..5eaf34d 100644
--- a/Jellyfin.Plugin.SmartNotify/Services/DiscordNotificationService.cs
+++ b/Jellyfin.Plugin.SmartNotify/Services/DiscordNotificationService.cs
@@ -38,8 +38,8 @@ public class DiscordNotificationService
///
/// The notifications to group and send.
/// The cancellation token.
- /// A task representing the async operation.
- public async Task SendGroupedEpisodeNotificationAsync(
+ /// True if the notification was sent successfully.
+ public async Task SendGroupedEpisodeNotificationAsync(
IEnumerable notifications,
CancellationToken cancellationToken)
{
@@ -47,13 +47,13 @@ public class DiscordNotificationService
if (config == null || string.IsNullOrEmpty(config.DiscordWebhookUrl))
{
_logger.LogWarning("Discord webhook URL not configured");
- return;
+ return false;
}
var notificationList = notifications.ToList();
if (notificationList.Count == 0)
{
- return;
+ return false;
}
var first = notificationList.First();
@@ -70,7 +70,7 @@ public class DiscordNotificationService
var title = $"📺 {seriesName}";
var description = $"Neue Episoden hinzugefügt:\n{episodeDescription}";
- await SendDiscordWebhookAsync(
+ return await SendDiscordWebhookAsync(
config,
title,
description,
@@ -153,7 +153,8 @@ public class DiscordNotificationService
///
/// The notification.
/// The cancellation token.
- public async Task SendMovieNotificationAsync(
+ /// True if the notification was sent successfully.
+ public async Task SendMovieNotificationAsync(
PendingNotification notification,
CancellationToken cancellationToken)
{
@@ -161,7 +162,7 @@ public class DiscordNotificationService
if (config == null || string.IsNullOrEmpty(config.DiscordWebhookUrl))
{
_logger.LogWarning("Discord webhook URL not configured");
- return;
+ return false;
}
var title = $"🎬 {notification.Name}";
@@ -176,7 +177,7 @@ public class DiscordNotificationService
description = description.Substring(0, 297) + "...";
}
- await SendDiscordWebhookAsync(
+ return await SendDiscordWebhookAsync(
config,
title,
description,
@@ -236,7 +237,8 @@ public class DiscordNotificationService
///
/// Sends the actual Discord webhook request, with optional image attachment.
///
- private async Task SendDiscordWebhookAsync(
+ /// True if the webhook was sent successfully.
+ private async Task SendDiscordWebhookAsync(
PluginConfiguration config,
string title,
string description,
@@ -260,7 +262,16 @@ public class DiscordNotificationService
if (!string.IsNullOrEmpty(externalLinks))
{
- embed["footer"] = new Dictionary { ["text"] = externalLinks };
+ var fields = new List>
+ {
+ new Dictionary
+ {
+ ["name"] = "Links",
+ ["value"] = externalLinks,
+ ["inline"] = false
+ }
+ };
+ embed["fields"] = fields;
}
embed["timestamp"] = DateTime.UtcNow.ToString("o");
@@ -272,7 +283,7 @@ public class DiscordNotificationService
};
var json = JsonSerializer.Serialize(payload);
- _logger.LogDebug("Sending Discord webhook: {Json}", json);
+ _logger.LogInformation("Sending Discord webhook for: {Title}", title);
try
{
@@ -281,6 +292,7 @@ public class DiscordNotificationService
if (hasImage)
{
+ _logger.LogInformation("Attaching image from: {Path}", imagePath);
// Send as multipart/form-data with image attachment
using var form = new MultipartFormDataContent();
form.Add(new StringContent(json, Encoding.UTF8, "application/json"), "payload_json");
@@ -306,15 +318,16 @@ public class DiscordNotificationService
"Discord webhook failed with status {Status}: {Body}",
response.StatusCode,
responseBody);
+ return false;
}
- else
- {
- _logger.LogInformation("Discord notification sent successfully: {Title}", title);
- }
+
+ _logger.LogInformation("Discord notification sent successfully: {Title}", title);
+ return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to send Discord webhook");
+ return false;
}
}
}