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; DiscordWebhookUrl = string.Empty;
NotificationDelayMinutes = 5; NotificationDelayMinutes = 5;
GroupingWindowMinutes = 30; GroupingWindowMinutes = 5;
EnableMovieNotifications = true; EnableMovieNotifications = true;
EnableEpisodeNotifications = true; EnableEpisodeNotifications = true;
SuppressUpgrades = true; SuppressUpgrades = true;

View File

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

View File

@@ -8,6 +8,7 @@ using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Timer = System.Timers.Timer; using Timer = System.Timers.Timer;
@@ -185,10 +186,11 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
Overview = item.Overview Overview = item.Overview
}; };
// Set image URL // Set local image path for attachment-based sending
if (!string.IsNullOrEmpty(serverUrl)) 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) if (item is Episode episode)
@@ -199,10 +201,15 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
notification.EpisodeNumber = episode.IndexNumber; notification.EpisodeNumber = episode.IndexNumber;
notification.Year = episode.ProductionYear; notification.Year = episode.ProductionYear;
// Use series image if episode doesn't have one // Use series image if episode doesn't have its own
if (!string.IsNullOrEmpty(serverUrl) && episode.SeriesId != Guid.Empty) 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) else if (item is Movie movie)

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
@@ -69,14 +70,11 @@ public class DiscordNotificationService
var title = $"📺 {seriesName}"; var title = $"📺 {seriesName}";
var description = $"Neue Episoden hinzugefügt:\n{episodeDescription}"; var description = $"Neue Episoden hinzugefügt:\n{episodeDescription}";
// Get image from first notification
var imageUrl = first.ImageUrl;
await SendDiscordWebhookAsync( await SendDiscordWebhookAsync(
config, config,
title, title,
description, description,
imageUrl, first.ImagePath,
BuildExternalLinks(first.ProviderIdsJson), BuildExternalLinks(first.ProviderIdsJson),
cancellationToken); cancellationToken);
} }
@@ -182,7 +180,7 @@ public class DiscordNotificationService
config, config,
title, title,
description, description,
notification.ImageUrl, notification.ImagePath,
BuildExternalLinks(notification.ProviderIdsJson), BuildExternalLinks(notification.ProviderIdsJson),
cancellationToken); cancellationToken);
} }
@@ -236,13 +234,13 @@ public class DiscordNotificationService
} }
/// <summary> /// <summary>
/// Sends the actual Discord webhook request. /// Sends the actual Discord webhook request, with optional image attachment.
/// </summary> /// </summary>
private async Task SendDiscordWebhookAsync( private async Task SendDiscordWebhookAsync(
PluginConfiguration config, PluginConfiguration config,
string title, string title,
string description, string description,
string? imageUrl, string? imagePath,
string externalLinks, string externalLinks,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
@@ -253,9 +251,11 @@ public class DiscordNotificationService
["color"] = config.EmbedColor ["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)) if (!string.IsNullOrEmpty(externalLinks))
@@ -277,9 +277,27 @@ public class DiscordNotificationService
try try
{ {
using var client = _httpClientFactory.CreateClient(); 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) if (!response.IsSuccessStatusCode)
{ {

View File

@@ -139,10 +139,15 @@ public class PendingNotification
public NotificationType Type { get; set; } public NotificationType Type { get; set; }
/// <summary> /// <summary>
/// Gets or sets the image URL. /// Gets or sets the image URL (for public servers).
/// </summary> /// </summary>
public string? ImageUrl { get; set; } 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> /// <summary>
/// Gets or sets the provider IDs JSON. /// Gets or sets the provider IDs JSON.
/// </summary> /// </summary>