fix: Bilder und dumme min TImings!

This commit is contained in:
2026-03-01 18:19:37 +01:00
parent 25fb75ae0b
commit 6c485aa0f7
5 changed files with 55 additions and 25 deletions

View File

@@ -14,7 +14,7 @@ public class PluginConfiguration : BasePluginConfiguration
{
DiscordWebhookUrl = string.Empty;
NotificationDelayMinutes = 5;
GroupingWindowMinutes = 30;
GroupingWindowMinutes = 5;
EnableMovieNotifications = true;
EnableEpisodeNotifications = true;
SuppressUpgrades = true;

View File

@@ -88,7 +88,7 @@
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="txtNotificationDelayMinutes">Verzögerung (Minuten)</label>
<input is="emby-input" type="number" id="txtNotificationDelayMinutes" min="1" max="60" />
<input is="emby-input" type="number" id="txtNotificationDelayMinutes" min="0" max="60" />
<div class="fieldDescription">
Wartezeit bevor eine Benachrichtigung gesendet wird.
Erlaubt Metadaten-Aktualisierung und Erkennung von Ersetzungen.
@@ -97,7 +97,7 @@
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="txtGroupingWindowMinutes">Gruppierungsfenster (Minuten)</label>
<input is="emby-input" type="number" id="txtGroupingWindowMinutes" min="5" max="120" />
<input is="emby-input" type="number" id="txtGroupingWindowMinutes" min="0" max="120" />
<div class="fieldDescription">
Zeitfenster in dem Episoden derselben Serie zusammengefasst werden.
Z.B. bei 30 Minuten: Wenn Episode 1-12 innerhalb von 30 Minuten hinzugefügt werden,
@@ -131,8 +131,8 @@
document.querySelector('#chkEnableEpisodeNotifications').checked = config.EnableEpisodeNotifications;
document.querySelector('#chkEnableMovieNotifications').checked = config.EnableMovieNotifications;
document.querySelector('#chkSuppressUpgrades').checked = config.SuppressUpgrades;
document.querySelector('#txtNotificationDelayMinutes').value = config.NotificationDelayMinutes || 5;
document.querySelector('#txtGroupingWindowMinutes').value = config.GroupingWindowMinutes || 30;
document.querySelector('#txtNotificationDelayMinutes').value = config.NotificationDelayMinutes ?? 0;
document.querySelector('#txtGroupingWindowMinutes').value = config.GroupingWindowMinutes ?? 0;
Dashboard.hideLoadingMsg();
});
});
@@ -149,8 +149,8 @@
config.EnableEpisodeNotifications = document.querySelector('#chkEnableEpisodeNotifications').checked;
config.EnableMovieNotifications = document.querySelector('#chkEnableMovieNotifications').checked;
config.SuppressUpgrades = document.querySelector('#chkSuppressUpgrades').checked;
config.NotificationDelayMinutes = parseInt(document.querySelector('#txtNotificationDelayMinutes').value) || 5;
config.GroupingWindowMinutes = parseInt(document.querySelector('#txtGroupingWindowMinutes').value) || 30;
config.NotificationDelayMinutes = parseInt(document.querySelector('#txtNotificationDelayMinutes').value) || 0;
config.GroupingWindowMinutes = parseInt(document.querySelector('#txtGroupingWindowMinutes').value) || 0;
ApiClient.updatePluginConfiguration(SmartNotifyConfig.pluginUniqueId, config).then(function () {
Dashboard.processPluginConfigurationUpdateResult();
});

View File

@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Timer = System.Timers.Timer;
@@ -185,10 +186,11 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
Overview = item.Overview
};
// Set image URL
if (!string.IsNullOrEmpty(serverUrl))
// Set local image path for attachment-based sending
var imagePath = item.GetImagePath(ImageType.Primary, 0);
if (!string.IsNullOrEmpty(imagePath))
{
notification.ImageUrl = $"{serverUrl}/Items/{item.Id}/Images/Primary";
notification.ImagePath = imagePath;
}
if (item is Episode episode)
@@ -199,10 +201,15 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
notification.EpisodeNumber = episode.IndexNumber;
notification.Year = episode.ProductionYear;
// Use series image if episode doesn't have one
if (!string.IsNullOrEmpty(serverUrl) && episode.SeriesId != Guid.Empty)
// Use series image if episode doesn't have its own
if (episode.SeriesId != Guid.Empty)
{
notification.ImageUrl = $"{serverUrl}/Items/{episode.SeriesId}/Images/Primary";
var series = _libraryManager.GetItemById(episode.SeriesId);
var seriesImage = series?.GetImagePath(ImageType.Primary, 0);
if (!string.IsNullOrEmpty(seriesImage))
{
notification.ImagePath = seriesImage;
}
}
}
else if (item is Movie movie)

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
@@ -69,14 +70,11 @@ public class DiscordNotificationService
var title = $"📺 {seriesName}";
var description = $"Neue Episoden hinzugefügt:\n{episodeDescription}";
// Get image from first notification
var imageUrl = first.ImageUrl;
await SendDiscordWebhookAsync(
config,
title,
description,
imageUrl,
first.ImagePath,
BuildExternalLinks(first.ProviderIdsJson),
cancellationToken);
}
@@ -182,7 +180,7 @@ public class DiscordNotificationService
config,
title,
description,
notification.ImageUrl,
notification.ImagePath,
BuildExternalLinks(notification.ProviderIdsJson),
cancellationToken);
}
@@ -236,13 +234,13 @@ public class DiscordNotificationService
}
/// <summary>
/// Sends the actual Discord webhook request.
/// Sends the actual Discord webhook request, with optional image attachment.
/// </summary>
private async Task SendDiscordWebhookAsync(
PluginConfiguration config,
string title,
string description,
string? imageUrl,
string? imagePath,
string externalLinks,
CancellationToken cancellationToken)
{
@@ -253,9 +251,11 @@ public class DiscordNotificationService
["color"] = config.EmbedColor
};
if (!string.IsNullOrEmpty(imageUrl))
// If we have a local image file, reference it as attachment
var hasImage = !string.IsNullOrEmpty(imagePath) && File.Exists(imagePath);
if (hasImage)
{
embed["thumbnail"] = new Dictionary<string, string> { ["url"] = imageUrl };
embed["thumbnail"] = new Dictionary<string, string> { ["url"] = "attachment://poster.jpg" };
}
if (!string.IsNullOrEmpty(externalLinks))
@@ -277,9 +277,27 @@ public class DiscordNotificationService
try
{
using var client = _httpClientFactory.CreateClient();
using var content = new StringContent(json, Encoding.UTF8, "application/json");
HttpResponseMessage response;
var response = await client.PostAsync(config.DiscordWebhookUrl, content, cancellationToken);
if (hasImage)
{
// Send as multipart/form-data with image attachment
using var form = new MultipartFormDataContent();
form.Add(new StringContent(json, Encoding.UTF8, "application/json"), "payload_json");
var imageBytes = await File.ReadAllBytesAsync(imagePath!, cancellationToken);
var imageContent = new ByteArrayContent(imageBytes);
imageContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("image/jpeg");
form.Add(imageContent, "files[0]", "poster.jpg");
response = await client.PostAsync(config.DiscordWebhookUrl, form, cancellationToken);
}
else
{
// Send as plain JSON (no image)
using var content = new StringContent(json, Encoding.UTF8, "application/json");
response = await client.PostAsync(config.DiscordWebhookUrl, content, cancellationToken);
}
if (!response.IsSuccessStatusCode)
{

View File

@@ -139,10 +139,15 @@ public class PendingNotification
public NotificationType Type { get; set; }
/// <summary>
/// Gets or sets the image URL.
/// Gets or sets the image URL (for public servers).
/// </summary>
public string? ImageUrl { get; set; }
/// <summary>
/// Gets or sets the local image file path (for attachment-based sending).
/// </summary>
public string? ImagePath { get; set; }
/// <summary>
/// Gets or sets the provider IDs JSON.
/// </summary>