Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 06cbbec97b | |||
|
|
877cf59e44 | ||
| 895aafb987 | |||
|
|
3925e502ec | ||
| 732dbda337 | |||
|
|
579331c79f | ||
| 063f594753 | |||
| b383b5cd81 | |||
| 6c485aa0f7 | |||
|
|
5110c999b6 | ||
| b67dc3aa1a | |||
|
|
33a07a19c2 | ||
| 25fb75ae0b | |||
| c9d5fd80ce | |||
|
|
8a445cc507 | ||
| f20364b25f | |||
|
|
b191779fde | ||
| 7425a18241 | |||
| 25ed431d5a |
@@ -37,24 +37,24 @@ jobs:
|
||||
echo "TAG=$TAG" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Install JPRM
|
||||
run: pip install jprm
|
||||
run: |
|
||||
pip install jprm
|
||||
mkdir -p ./artifacts
|
||||
|
||||
- name: Build plugin with JPRM
|
||||
run: |
|
||||
jprm --verbosity=debug plugin build Jellyfin.Plugin.SmartNotify \
|
||||
ARTIFACT="$(jprm --verbosity=debug plugin build . \
|
||||
--dotnet-framework="net9.0" \
|
||||
--version="${VERSION}.0" \
|
||||
--output=./artifacts
|
||||
|
||||
- name: Prepare artifact
|
||||
run: |
|
||||
# Find the ZIP that JPRM created
|
||||
ARTIFACT=$(ls artifacts/*.zip | head -1)
|
||||
-v "${VERSION}.0" \
|
||||
--dotnet-configuration Release \
|
||||
-o ./artifacts)"
|
||||
echo "ARTIFACT=$ARTIFACT" >> "$GITHUB_ENV"
|
||||
cp "$ARTIFACT" "smartnotify_${VERSION}.zip"
|
||||
|
||||
# Calculate MD5 checksum
|
||||
CHECKSUM=$(md5sum "smartnotify_${VERSION}.zip" | cut -d' ' -f1)
|
||||
echo "CHECKSUM=$CHECKSUM" >> "$GITHUB_ENV"
|
||||
echo "Artifact: $ARTIFACT"
|
||||
echo "Checksum: $CHECKSUM"
|
||||
|
||||
- name: Upload ZIP to Release
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.0.10"
|
||||
".": "0.0.14"
|
||||
}
|
||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -1,5 +1,46 @@
|
||||
# Changelog
|
||||
|
||||
## 0.0.14 (2026-03-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix: claude hat bugs im Kopf
|
||||
|
||||
### Chores
|
||||
|
||||
* chore: update manifest for v0.0.13
|
||||
|
||||
|
||||
## 0.0.13 (2026-03-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix: removed drecks versionen
|
||||
* fix: Bilder und dumme min TImings!
|
||||
|
||||
### Chores
|
||||
|
||||
* chore: update manifest for v0.0.12
|
||||
|
||||
|
||||
## 0.0.12 (2026-03-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix: glaube ich net
|
||||
|
||||
### Chores
|
||||
|
||||
* chore: update manifest for v0.0.11
|
||||
|
||||
|
||||
## 0.0.11 (2026-03-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix: claude ist auch für 100€ im Monat noch dumm
|
||||
|
||||
|
||||
## 0.0.10 (2026-03-01)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -14,7 +14,7 @@ public class PluginConfiguration : BasePluginConfiguration
|
||||
{
|
||||
DiscordWebhookUrl = string.Empty;
|
||||
NotificationDelayMinutes = 5;
|
||||
GroupingWindowMinutes = 30;
|
||||
GroupingWindowMinutes = 5;
|
||||
EnableMovieNotifications = true;
|
||||
EnableEpisodeNotifications = true;
|
||||
SuppressUpgrades = true;
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RootNamespace>Jellyfin.Plugin.SmartNotify</RootNamespace>
|
||||
<AssemblyVersion>0.0.10.0</AssemblyVersion>
|
||||
<FileVersion>0.0.10.0</FileVersion>
|
||||
<AssemblyVersion>0.0.14.0</AssemblyVersion>
|
||||
<FileVersion>0.0.14.0</FileVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
@@ -14,10 +14,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Jellyfin.Controller" Version="10.11.*">
|
||||
<PackageReference Include="Jellyfin.Controller" Version="10.11.0">
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Jellyfin.Model" Version="10.11.*">
|
||||
<PackageReference Include="Jellyfin.Model" Version="10.11.0">
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="LiteDB" Version="5.0.21" />
|
||||
|
||||
@@ -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)
|
||||
@@ -247,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
|
||||
@@ -255,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)
|
||||
{
|
||||
@@ -264,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
|
||||
@@ -286,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)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
@@ -37,8 +38,8 @@ public class DiscordNotificationService
|
||||
/// </summary>
|
||||
/// <param name="notifications">The notifications to group and send.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task representing the async operation.</returns>
|
||||
public async Task SendGroupedEpisodeNotificationAsync(
|
||||
/// <returns>True if the notification was sent successfully.</returns>
|
||||
public async Task<bool> SendGroupedEpisodeNotificationAsync(
|
||||
IEnumerable<PendingNotification> notifications,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -46,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();
|
||||
@@ -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(
|
||||
return await SendDiscordWebhookAsync(
|
||||
config,
|
||||
title,
|
||||
description,
|
||||
imageUrl,
|
||||
first.ImagePath,
|
||||
BuildExternalLinks(first.ProviderIdsJson),
|
||||
cancellationToken);
|
||||
}
|
||||
@@ -155,7 +153,8 @@ public class DiscordNotificationService
|
||||
/// </summary>
|
||||
/// <param name="notification">The notification.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
public async Task SendMovieNotificationAsync(
|
||||
/// <returns>True if the notification was sent successfully.</returns>
|
||||
public async Task<bool> SendMovieNotificationAsync(
|
||||
PendingNotification notification,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -163,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}";
|
||||
@@ -178,11 +177,11 @@ public class DiscordNotificationService
|
||||
description = description.Substring(0, 297) + "...";
|
||||
}
|
||||
|
||||
await SendDiscordWebhookAsync(
|
||||
return await SendDiscordWebhookAsync(
|
||||
config,
|
||||
title,
|
||||
description,
|
||||
notification.ImageUrl,
|
||||
notification.ImagePath,
|
||||
BuildExternalLinks(notification.ProviderIdsJson),
|
||||
cancellationToken);
|
||||
}
|
||||
@@ -236,13 +235,14 @@ 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(
|
||||
/// <returns>True if the webhook was sent successfully.</returns>
|
||||
private async Task<bool> SendDiscordWebhookAsync(
|
||||
PluginConfiguration config,
|
||||
string title,
|
||||
string description,
|
||||
string? imageUrl,
|
||||
string? imagePath,
|
||||
string externalLinks,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -253,14 +253,25 @@ 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))
|
||||
{
|
||||
embed["footer"] = new Dictionary<string, string> { ["text"] = externalLinks };
|
||||
var fields = new List<Dictionary<string, object>>
|
||||
{
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["name"] = "Links",
|
||||
["value"] = externalLinks,
|
||||
["inline"] = false
|
||||
}
|
||||
};
|
||||
embed["fields"] = fields;
|
||||
}
|
||||
|
||||
embed["timestamp"] = DateTime.UtcNow.ToString("o");
|
||||
@@ -272,14 +283,33 @@ public class DiscordNotificationService
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(payload);
|
||||
_logger.LogDebug("Sending Discord webhook: {Json}", json);
|
||||
_logger.LogInformation("Sending Discord webhook for: {Title}", title);
|
||||
|
||||
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)
|
||||
{
|
||||
_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");
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -288,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -9,44 +9,12 @@
|
||||
"imageUrl": "",
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.0.9.0",
|
||||
"changelog": "### Bug Fixes\n\n* fix: angeblich..\n\n### Chores\n\n* chore: update manifest for v0.0.8",
|
||||
"version": "0.0.13.0",
|
||||
"changelog": "### Bug Fixes\n\n* fix: removed drecks versionen\n* fix: Bilder und dumme min TImings!\n\n### Chores\n\n* chore: update manifest for v0.0.12",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.0.9/smartnotify_0.0.9.zip",
|
||||
"checksum": "9a1870e82fc3270ca37960a19b289cbd",
|
||||
"timestamp": "2026-03-01T16:18:31Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.8.0",
|
||||
"changelog": "### Bug Fixes\n\n* fix: notsupported again\n\n### Chores\n\n* chore: update manifest for v0.0.7",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.0.8/smartnotify_0.0.8.zip",
|
||||
"checksum": "21b5826fb120dd0db1ffacc2badb17f7",
|
||||
"timestamp": "2026-03-01T16:07:10Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.7.0",
|
||||
"changelog": "### Bug Fixes\n\n* fix: build lief net!\n* fix: build lief nicht\n* fix: removed Claudes dummes gefriemel\n* fix: notSupported\n\n### Chores\n\n* chore(main): release 0.0.6\n* chore: update manifest for v0.0.5",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.0.7/smartnotify_0.0.7.zip",
|
||||
"checksum": "6f8ef2061ce35e90c1827a5c8e9a1fe3",
|
||||
"timestamp": "2026-03-01T15:57:08Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.5.0",
|
||||
"changelog": "### Bug Fixes\n\n* fix: remove Jellyfin Trash from Build\n\n### Chores\n\n* chore: loop gefixt\n* chore: update manifest for v0.0.4",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.0.5/smartnotify_0.0.5.zip",
|
||||
"checksum": "74288c4e9b7b25d113fd1e45cf40d0a2",
|
||||
"timestamp": "2026-03-01T15:48:20Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.4.0",
|
||||
"changelog": "### Bug Fixes\n\n* fix: json fehler\n* fix: Dumme Readme",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.0.4/smartnotify_0.0.4.zip",
|
||||
"checksum": "d454711f1e6f59de0ab539134a4fd9a7",
|
||||
"timestamp": "2026-03-01T15:29:46Z"
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.0.13/smartnotify_0.0.13.zip",
|
||||
"checksum": "2d303e8dc214a58e038f516076840d5b",
|
||||
"timestamp": "2026-03-01T17:21:50Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user