Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7aadb40c82 | ||
|
|
48e347f5c7 | ||
| 76161230e1 | |||
|
|
205cbd91af | ||
| a71ced086a | |||
| 85fd002f4e | |||
|
|
f9aacf2436 | ||
|
|
caee267f8b | ||
| 5a60a6f5b4 | |||
|
|
133c00fab0 | ||
| 0bafe691a0 | |||
| 82b34e288c | |||
|
|
8c77f82d2c | ||
|
|
cb0dfe2c21 | ||
| eaf6ea91e1 | |||
|
|
180a998be1 | ||
| 6fd2638414 | |||
| 0e10e3c089 | |||
|
|
e729c7b8d5 | ||
|
|
fb8161f976 | ||
| 0f5ce9726b | |||
|
|
8d93c17bde | ||
| b1444094ad | |||
| cc2d02983f | |||
| aa26dbb40b | |||
| 5954974add | |||
|
|
52b3588933 |
@@ -11,24 +11,36 @@ permissions:
|
||||
jobs:
|
||||
release-pr:
|
||||
name: Create Release PR
|
||||
if: "!contains(gitea.event.head_commit.message, 'chore(main): release') && !contains(gitea.event.head_commit.message, 'chore: update manifest')"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Skip if release/manifest commit
|
||||
run: |
|
||||
COMMIT_MSG="${{ gitea.event.head_commit.message }}"
|
||||
if echo "$COMMIT_MSG" | grep -qE 'chore\(main\): release|chore: update manifest'; then
|
||||
echo "Skipping release/manifest commit"
|
||||
exit 0
|
||||
fi
|
||||
echo "SHOULD_RUN=true" >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: env.SHOULD_RUN == 'true'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GT_TOKEN }}
|
||||
|
||||
- name: Setup Python
|
||||
if: env.SHOULD_RUN == 'true'
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
if: env.SHOULD_RUN == 'true'
|
||||
run: |
|
||||
pip install gitpython packaging
|
||||
|
||||
- name: Create Release PR
|
||||
if: env.SHOULD_RUN == 'true'
|
||||
env:
|
||||
GIT_TOKEN: ${{ secrets.GT_TOKEN }}
|
||||
REPO: "TDPI/jellyfin-plugin-smartnotify"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "0.1.0"
|
||||
".": "0.1.4"
|
||||
}
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.4 (2026-04-07)
|
||||
|
||||
## 0.1.4 (2026-04-07)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
Langsamer Start von Jellyfin wegen Überprüfung bestehender Einträge.
|
||||
Läuft nun asynchron.
|
||||
## 0.1.3 (2026-04-05)
|
||||
|
||||
## 0.1.3 (2026-04-05)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
Gebe keine Notification wenn der Name der Serie tmdbid oder ähnliches enthält.
|
||||
Dies verhindert das die Notification kommt wenn noch nicht die Metadaten gezogen wurden.
|
||||
## 0.1.2 (2026-04-04)
|
||||
|
||||
## 0.1.2 (2026-04-04)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Problem behoben das der Serienname vor der Benachrichtigung nicht aktualisiert wurde.
|
||||
## 0.1.1 (2026-04-03)
|
||||
|
||||
## 0.1.1 (2026-04-03)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix: unknown series
|
||||
|
||||
### Chores
|
||||
|
||||
* chore: workflow fix
|
||||
* chore: removed old versions
|
||||
* chore: update manifest for v0.1.0
|
||||
## 0.1.0 (2026-03-05)
|
||||
|
||||
## 0.1.0 (2026-03-05)
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<RootNamespace>Jellyfin.Plugin.SmartNotify</RootNamespace>
|
||||
<AssemblyVersion>0.1.0.0</AssemblyVersion>
|
||||
<FileVersion>0.1.0.0</FileVersion>
|
||||
<AssemblyVersion>0.1.4.0</AssemblyVersion>
|
||||
<FileVersion>0.1.4.0</FileVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
@@ -49,11 +50,7 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
{
|
||||
_logger.LogInformation("SmartNotify background service starting");
|
||||
|
||||
// Pre-populate DB with all existing library items so they're recognized as "known".
|
||||
// This prevents mass notifications on first run or after DB reset.
|
||||
SeedExistingLibraryItems();
|
||||
|
||||
// Subscribe to library events
|
||||
// Subscribe to library events (before seeding so we don't miss items added during seed).
|
||||
_libraryManager.ItemAdded += OnItemAdded;
|
||||
_libraryManager.ItemRemoved += OnItemRemoved;
|
||||
|
||||
@@ -63,6 +60,10 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
_processTimer.AutoReset = true;
|
||||
_processTimer.Start();
|
||||
|
||||
// Pre-populate DB with existing library items in the background
|
||||
// so we don't block Jellyfin startup.
|
||||
_ = Task.Run(() => SeedExistingLibraryItems(), cancellationToken);
|
||||
|
||||
_logger.LogInformation("SmartNotify is now monitoring library changes");
|
||||
|
||||
return Task.CompletedTask;
|
||||
@@ -318,17 +319,24 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
|
||||
if (item is Episode episode)
|
||||
{
|
||||
notification.SeriesName = episode.SeriesName;
|
||||
notification.SeriesId = episode.SeriesId.ToString();
|
||||
var seriesObj = episode.Series;
|
||||
var rawSeriesName = episode.SeriesName ?? seriesObj?.Name;
|
||||
// If the name still looks like a Sonarr folder name, leave it null
|
||||
// so the incomplete-episode logic defers until metadata scrape is done.
|
||||
notification.SeriesName = LooksLikeFolderName(rawSeriesName) ? null : rawSeriesName;
|
||||
notification.SeriesId = (episode.SeriesId != Guid.Empty
|
||||
? episode.SeriesId
|
||||
: seriesObj?.Id ?? Guid.Empty).ToString();
|
||||
notification.SeasonNumber = episode.ParentIndexNumber;
|
||||
notification.EpisodeNumber = episode.IndexNumber;
|
||||
notification.Year = episode.ProductionYear;
|
||||
|
||||
// Use series provider IDs for external links — episode provider IDs
|
||||
// (e.g. AniDB episode ID) lead to wrong URLs when used with /anime/ paths
|
||||
if (episode.SeriesId != Guid.Empty)
|
||||
var resolvedSeriesId = notification.SeriesId;
|
||||
if (resolvedSeriesId != Guid.Empty.ToString() && Guid.TryParse(resolvedSeriesId, out var seriesGuid))
|
||||
{
|
||||
var series = _libraryManager.GetItemById(episode.SeriesId);
|
||||
var series = seriesObj ?? _libraryManager.GetItemById(seriesGuid);
|
||||
if (series?.ProviderIds != null && series.ProviderIds.Count > 0)
|
||||
{
|
||||
notification.ProviderIdsJson = JsonSerializer.Serialize(series.ProviderIds);
|
||||
@@ -424,17 +432,51 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
|
||||
var changed = false;
|
||||
|
||||
if (string.IsNullOrEmpty(notification.SeriesName) || notification.SeriesName == "Unknown Series")
|
||||
// Refresh SeriesName from the library. At queue time the name is often
|
||||
// the Sonarr folder name (e.g. "Chained Soldier (2024) [tmdbid-139060]").
|
||||
// After Jellyfin's metadata scrape completes, the name changes to the
|
||||
// correct provider name (e.g. "Demon Slave - The Chained Soldier").
|
||||
// We detect the unscraped folder name by checking for bracket patterns
|
||||
// like [tmdbid-...] or (2024) and clear the SeriesName so the
|
||||
// incomplete-episode logic defers sending until the scrape is done.
|
||||
{
|
||||
notification.SeriesName = episode.SeriesName;
|
||||
changed = true;
|
||||
var freshSeriesName = episode.SeriesName ?? episode.Series?.Name;
|
||||
if (!string.IsNullOrEmpty(freshSeriesName))
|
||||
{
|
||||
if (LooksLikeFolderName(freshSeriesName))
|
||||
{
|
||||
// Still the Sonarr folder name — clear so we keep waiting
|
||||
if (!string.IsNullOrEmpty(notification.SeriesName))
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"[DEBUG Refresh] SeriesName '{Name}' looks like a folder name, clearing to defer",
|
||||
freshSeriesName);
|
||||
notification.SeriesName = null;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
else if (freshSeriesName != notification.SeriesName)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"[DEBUG Refresh] SeriesName changed: '{Old}' -> '{New}'",
|
||||
notification.SeriesName,
|
||||
freshSeriesName);
|
||||
notification.SeriesName = freshSeriesName;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(notification.SeriesId) || notification.SeriesId == Guid.Empty.ToString())
|
||||
{
|
||||
if (episode.SeriesId != Guid.Empty)
|
||||
// Same fallback: SeriesId property may be empty, but Series object may exist
|
||||
var resolvedSeriesId = episode.SeriesId != Guid.Empty
|
||||
? episode.SeriesId
|
||||
: episode.Series?.Id ?? Guid.Empty;
|
||||
|
||||
if (resolvedSeriesId != Guid.Empty)
|
||||
{
|
||||
notification.SeriesId = episode.SeriesId.ToString();
|
||||
notification.SeriesId = resolvedSeriesId.ToString();
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -451,6 +493,17 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Always refresh the item name — it may have been a placeholder at queue time
|
||||
if (!string.IsNullOrEmpty(episode.Name) && episode.Name != notification.Name)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"[DEBUG Refresh] Name changed: '{Old}' -> '{New}'",
|
||||
notification.Name,
|
||||
episode.Name);
|
||||
notification.Name = episode.Name;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// Refresh image if missing
|
||||
if (string.IsNullOrEmpty(notification.ImagePath) && episode.SeriesId != Guid.Empty)
|
||||
{
|
||||
@@ -568,16 +621,43 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
.GroupBy(n => n.SeriesId!)
|
||||
.ToList();
|
||||
|
||||
// Handle episodes that still have no series info (send individually as fallback)
|
||||
var orphanEpisodes = pendingNotifications
|
||||
.Where(n => n.ItemType == "Episode" && (string.IsNullOrEmpty(n.SeriesId) || n.SeriesId == emptyGuid))
|
||||
// Episodes without SeriesName or SeriesId must NEVER be sent.
|
||||
// Wait up to 30 minutes for metadata to resolve, then drop.
|
||||
var maxMetadataWait = DateTime.UtcNow.AddMinutes(-30);
|
||||
var incompleteEpisodes = pendingNotifications
|
||||
.Where(n => n.ItemType == "Episode"
|
||||
&& (string.IsNullOrEmpty(n.SeriesName)
|
||||
|| string.IsNullOrEmpty(n.SeriesId)
|
||||
|| n.SeriesId == emptyGuid))
|
||||
.ToList();
|
||||
|
||||
if (orphanEpisodes.Count > 0)
|
||||
if (incompleteEpisodes.Count > 0)
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"{Count} episode notifications have no series info even after refresh",
|
||||
orphanEpisodes.Count);
|
||||
// Split into: still waiting vs. timed out
|
||||
var timedOut = incompleteEpisodes.Where(n => n.QueuedAt < maxMetadataWait).ToList();
|
||||
var stillWaiting = incompleteEpisodes.Where(n => n.QueuedAt >= maxMetadataWait).ToList();
|
||||
|
||||
if (timedOut.Count > 0)
|
||||
{
|
||||
var dropIds = timedOut.Select(n => n.Id).ToList();
|
||||
_historyService.RemoveNotifications(dropIds);
|
||||
pendingNotifications.RemoveAll(n => dropIds.Contains(n.Id));
|
||||
_logger.LogWarning(
|
||||
"Dropped {Count} episode notifications after 30min without series metadata: {Names}",
|
||||
timedOut.Count,
|
||||
string.Join(", ", timedOut.Select(n => n.Name)));
|
||||
}
|
||||
|
||||
if (stillWaiting.Count > 0)
|
||||
{
|
||||
// Remove from this processing cycle, keep in queue for next attempt
|
||||
var waitIds = stillWaiting.Select(n => n.Id).ToHashSet();
|
||||
pendingNotifications.RemoveAll(n => waitIds.Contains(n.Id));
|
||||
_logger.LogInformation(
|
||||
"Deferring {Count} episode notifications, waiting for series metadata: {Names}",
|
||||
stillWaiting.Count,
|
||||
string.Join(", ", stillWaiting.Select(n => n.Name)));
|
||||
}
|
||||
}
|
||||
|
||||
// Process each series group
|
||||
@@ -619,31 +699,6 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Process orphan episodes (no series info - send individually)
|
||||
foreach (var orphan in orphanEpisodes)
|
||||
{
|
||||
var oldAge = DateTime.UtcNow.AddMinutes(-groupingWindowMinutes);
|
||||
if (orphan.QueuedAt > oldAge)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Sending individual episode notification for: {Name} (no series info)", orphan.Name);
|
||||
|
||||
var success = await _discordService.SendGroupedEpisodeNotificationAsync(
|
||||
new[] { orphan },
|
||||
CancellationToken.None);
|
||||
|
||||
if (success)
|
||||
{
|
||||
_historyService.RemoveNotifications(new[] { orphan.Id });
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Discord send failed for orphan episode {Name}, keeping in queue for retry", orphan.Name);
|
||||
}
|
||||
}
|
||||
|
||||
// Process movies
|
||||
var movies = pendingNotifications
|
||||
.Where(n => n.ItemType == "Movie")
|
||||
@@ -672,6 +727,19 @@ public class SmartNotifyBackgroundService : IHostedService, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detects Sonarr-style folder names that haven't been replaced by a metadata scrape yet.
|
||||
/// Matches patterns like "[tmdbid-139060]", "[tvdbid-412656]", "[imdbid-tt1234]".
|
||||
/// </summary>
|
||||
private static readonly Regex FolderNamePattern = new(
|
||||
@"\[(tmdbid|tvdbid|imdbid)-[^\]]+\]",
|
||||
RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
|
||||
private static bool LooksLikeFolderName(string? name)
|
||||
{
|
||||
return !string.IsNullOrEmpty(name) && FolderNamePattern.IsMatch(name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -57,7 +57,7 @@ public class DiscordNotificationService
|
||||
}
|
||||
|
||||
var first = notificationList.First();
|
||||
var seriesName = first.SeriesName ?? "Unknown Series";
|
||||
var seriesName = first.SeriesName!;
|
||||
|
||||
// Group by season
|
||||
var bySeason = notificationList
|
||||
|
||||
118
README.md
118
README.md
@@ -1,115 +1,65 @@
|
||||
# SmartNotify - Jellyfin Plugin
|
||||
|
||||
**Intelligente Discord-Benachrichtigungen für Jellyfin 10.11+**
|
||||
**Discord-Benachrichtigungen für neue Filme und Episoden in Jellyfin 10.11+**
|
||||
|
||||
## Das Problem
|
||||
Kennt das Problem: Du tauschst eine Serie gegen bessere Qualität aus und Jellyfin meldet jede Folge als "neu". SmartNotify erkennt den Unterschied zwischen wirklich neuen Inhalten und Qualitäts-Upgrades — und nervt dich nicht mit Spam.
|
||||
|
||||
Wenn du eine Serie gegen eine bessere Qualität austauschst, schreit Jellyfin "NEUE EPISODE!" - obwohl es nur ein Upgrade ist. Das nervt.
|
||||
## Was kann es?
|
||||
|
||||
## Die Lösung
|
||||
|
||||
SmartNotify erkennt automatisch:
|
||||
- **Wirklich neue Inhalte** → Benachrichtigung wird gesendet
|
||||
- **Qualitäts-Upgrades** → Keine Benachrichtigung (oder optional eigener Typ)
|
||||
|
||||
Zusätzlich gruppiert SmartNotify Episoden intelligent:
|
||||
- Statt 12 einzelner Nachrichten: **"Staffel 1: Episode 1-12 hinzugefügt"**
|
||||
- Bei Lücken: **"Episode 1-4, 6, 8-12"**
|
||||
|
||||
## Features
|
||||
|
||||
- ✅ Erkennt Qualitäts-Upgrades automatisch
|
||||
- ✅ Intelligente Episoden-Gruppierung
|
||||
- ✅ Unterstützt AniDB, AniList, TMDB, TVDB, IMDB
|
||||
- ✅ Konfigurierbare Verzögerung (für Metadaten-Updates)
|
||||
- ✅ Konfigurierbare Gruppierungsfenster
|
||||
- ✅ Discord Webhook Support
|
||||
- ✅ Deutsche Oberfläche
|
||||
|
||||
## Wie funktioniert die Erkennung?
|
||||
|
||||
SmartNotify führt eine lokale Datenbank mit allen bekannten Inhalten:
|
||||
|
||||
1. **Neues Item kommt rein**
|
||||
2. SmartNotify generiert einen "Content Key" basierend auf:
|
||||
- Bei Episoden: Serie (Provider-IDs) + Staffel + Episode
|
||||
- Bei Filmen: Provider-IDs (AniDB, TMDB, etc.)
|
||||
3. **Prüfung 1:** Existiert dieser Content Key schon in der DB? → Upgrade!
|
||||
4. **Prüfung 2:** Gibt es gerade ein anderes Item mit gleichem Content Key? → Duplikat (neues File vor Löschung des alten)
|
||||
5. Nur wenn beide Prüfungen negativ sind → Benachrichtigung
|
||||
- **Upgrade-Erkennung** — Erkennt ob eine Datei neu ist oder nur ein Qualitäts-Upgrade. Keine falschen Benachrichtigungen mehr beim Austausch von Dateien.
|
||||
- **Episoden-Gruppierung** — Statt 12 einzelner Nachrichten bekommst du eine: *"Staffel 1: Episode 1-12"*. Bei Lücken entsprechend: *"Episode 1-4, 6, 8-12"*.
|
||||
- **Discord-Embeds** — Benachrichtigungen mit Bild, Beschreibung und direkten Links zu IMDb, TMDb, AniDB, AniList und TVDB.
|
||||
- **Shokofin/VFS-kompatibel** — Die Erkennung basiert auf Provider-IDs statt Jellyfin-internen IDs, die sich bei VFS ständig ändern.
|
||||
|
||||
## Installation
|
||||
|
||||
## Manuelle Installation
|
||||
In Jellyfin unter **Dashboard** > **Plugins** > **Repositories** die folgende URL als Repository hinzufügen:
|
||||
|
||||
1. Plugin von Releases herunterladen
|
||||
2. ZIP entpacken in `plugins/SmartNotify/`
|
||||
3. Jellyfin neustarten
|
||||
```
|
||||
https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/raw/branch/main/manifest.json
|
||||
```
|
||||
|
||||
## Konfiguration
|
||||
Danach im **Katalog** SmartNotify installieren und Jellyfin neustarten.
|
||||
|
||||
Nach der Installation im Jellyfin Dashboard:
|
||||
1. **Dashboard** → **Plugins** → **SmartNotify**
|
||||
2. Discord Webhook URL eintragen
|
||||
3. Server URL eintragen (für Bilder in Discord)
|
||||
4. Optional: Verzögerung und Gruppierungsfenster anpassen
|
||||
### Manuell
|
||||
|
||||
### Einstellungen
|
||||
Plugin-ZIP von den [Releases](https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases) herunterladen, nach `plugins/SmartNotify/` entpacken und Jellyfin neustarten.
|
||||
|
||||
## Einrichtung
|
||||
|
||||
Nach der Installation unter **Dashboard** > **Plugins** > **SmartNotify**:
|
||||
|
||||
1. Discord Webhook URL eintragen
|
||||
2. Server URL eintragen (wird für Bilder in Discord gebraucht)
|
||||
3. Fertig — Standardeinstellungen passen für die meisten Setups
|
||||
|
||||
### Optionale Einstellungen
|
||||
|
||||
| Einstellung | Standard | Beschreibung |
|
||||
|-------------|----------|--------------|
|
||||
| Discord Webhook URL | - | Die Webhook-URL deines Discord-Kanals |
|
||||
| Server URL | - | Öffentliche Jellyfin-URL (für Bilder) |
|
||||
| Verzögerung | 5 min | Wartezeit für Metadaten-Updates |
|
||||
| Gruppierungsfenster | 30 min | Zeitfenster für Episoden-Gruppierung |
|
||||
| Upgrades unterdrücken | ✓ | Keine Benachrichtigung bei Ersetzungen |
|
||||
| Verzögerung | 5 min | Wartezeit damit Jellyfin Metadaten laden kann |
|
||||
| Gruppierungsfenster | 30 min | Wie lange auf weitere Episoden gewartet wird bevor gesendet wird |
|
||||
| Upgrades unterdrücken | An | Keine Benachrichtigung wenn nur die Qualität besser wird |
|
||||
| Film-Benachrichtigungen | An | Benachrichtigungen für neue Filme |
|
||||
| Episoden-Benachrichtigungen | An | Benachrichtigungen für neue Episoden |
|
||||
| Bot-Name | Jellyfin SmartNotify | Anzeigename in Discord |
|
||||
| Embed-Farbe | Blau | Farbe des Discord-Embeds |
|
||||
|
||||
## Beispiel-Benachrichtigungen
|
||||
## Beispiele
|
||||
|
||||
### Einzelne Episode
|
||||
```
|
||||
📺 Demon Slayer
|
||||
Neue Episoden hinzugefügt:
|
||||
Staffel 1: Episode 5
|
||||
```
|
||||
|
||||
### Mehrere Episoden (gruppiert)
|
||||
**Mehrere Episoden:**
|
||||
```
|
||||
📺 Attack on Titan
|
||||
Neue Episoden hinzugefügt:
|
||||
Staffel 4: Episode 1-12
|
||||
```
|
||||
|
||||
### Episoden mit Lücken
|
||||
```
|
||||
📺 One Piece
|
||||
Neue Episoden hinzugefügt:
|
||||
Staffel 1: Episode 1-4, 6, 8-12
|
||||
```
|
||||
|
||||
### Film
|
||||
**Neuer Film:**
|
||||
```
|
||||
🎬 Your Name (2016)
|
||||
Kimi no Na wa - Ein Junge und ein Mädchen...
|
||||
```
|
||||
|
||||
## Bekannte Einschränkungen
|
||||
|
||||
- Provider-IDs müssen vorhanden sein (AniDB, TMDB, etc.)
|
||||
- Bei Items ohne Provider-IDs wird auf Name+Jahr zurückgefallen
|
||||
- Die Datenbank wächst mit der Zeit (kann bei Bedarf gelöscht werden)
|
||||
|
||||
## Installation via Repository
|
||||
|
||||
In Jellyfin:
|
||||
1. **Dashboard** → **Plugins** → **Repositories** → **+**
|
||||
2. URL einfügen:
|
||||
```
|
||||
https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/raw/branch/main/manifest.json
|
||||
```
|
||||
3. **Katalog** → SmartNotify installieren
|
||||
4. Jellyfin neustarten
|
||||
|
||||
## Lizenz
|
||||
|
||||
MIT
|
||||
|
||||
@@ -9,52 +9,44 @@
|
||||
"imageUrl": "",
|
||||
"versions": [
|
||||
{
|
||||
"version": "0.0.19.0",
|
||||
"changelog": "### Bug Fixes\n\n* fix: debuging\n\n### Chores\n\n* chore: update manifest for v0.0.18",
|
||||
"version": "0.1.4.0",
|
||||
"changelog": "## 0.1.4 (2026-04-07)\n\n### Bug Fixes\n\nLangsamer Start von Jellyfin wegen Überprüfung bestehender Einträge.\nLäuft nun asynchron.",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.0.19/smartnotify_0.0.19.zip",
|
||||
"checksum": "a79dec5dc65282ff8bb5f130931f9480",
|
||||
"timestamp": "2026-03-04T17:22:49Z"
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.1.4/smartnotify_0.1.4.zip",
|
||||
"checksum": "6241bdf445f91732a917a8e56c956226",
|
||||
"timestamp": "2026-04-07T06:40:42Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.18.0",
|
||||
"changelog": "### Bug Fixes\n\n* fix: build error",
|
||||
"version": "0.1.3.0",
|
||||
"changelog": "## 0.1.3 (2026-04-05)\n\n### Bug Fixes\n\nGebe keine Notification wenn der Name der Serie tmdbid oder ähnliches enthält.\nDies verhindert das die Notification kommt wenn noch nicht die Metadaten gezogen wurden.",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.0.18/smartnotify_0.0.18.zip",
|
||||
"checksum": "4b5857ce309974f8e64ab81885d9b67d",
|
||||
"timestamp": "2026-03-03T13:53:53Z"
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.1.3/smartnotify_0.1.3.zip",
|
||||
"checksum": "0c8a24dcfed8884f768e5b2aad5f251c",
|
||||
"timestamp": "2026-04-05T16:13:21Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.16.0",
|
||||
"changelog": "### Bug Fixes\n\n* fix: ist das wirklich ein Fix und kein defix?\n\n### Chores\n\n* chore: update manifest for v0.0.15",
|
||||
"version": "0.1.2.0",
|
||||
"changelog": "## 0.1.2 (2026-04-04)\n\n### Bug Fixes\n\n* Problem behoben das der Serienname vor der Benachrichtigung nicht aktualisiert wurde.",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.0.16/smartnotify_0.0.16.zip",
|
||||
"checksum": "fbe2b3ce339c92204961df605bfe276b",
|
||||
"timestamp": "2026-03-02T18:58:41Z"
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.1.2/smartnotify_0.1.2.zip",
|
||||
"checksum": "56e2b0005f5bc3368c9a7f53b2f806a0",
|
||||
"timestamp": "2026-04-04T09:39:49Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.15.0",
|
||||
"changelog": "### Bug Fixes\n\n* fix: timestamps!\n\n### Chores\n\n* chore: update manifest for v0.0.14",
|
||||
"version": "0.1.1.0",
|
||||
"changelog": "## 0.1.1 (2026-04-03)\n\n### Bug Fixes\n\n* fix: unknown series\n\n### Chores\n\n* chore: workflow fix\n* chore: removed old versions\n* chore: update manifest for v0.1.0",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.0.15/smartnotify_0.0.15.zip",
|
||||
"checksum": "c3dc638240b5688de030f77eccaf9c50",
|
||||
"timestamp": "2026-03-01T17:49:47Z"
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.1.1/smartnotify_0.1.1.zip",
|
||||
"checksum": "459569cbae49ba569291011ac9a54202",
|
||||
"timestamp": "2026-04-03T17:32:41Z"
|
||||
},
|
||||
{
|
||||
"version": "0.0.14.0",
|
||||
"changelog": "### Bug Fixes\n\n* fix: claude hat bugs im Kopf\n\n### Chores\n\n* chore: update manifest for v0.0.13",
|
||||
"version": "0.1.0.0",
|
||||
"changelog": "## 0.1.0 (2026-03-05)\n\n**Features**\n\nIntelligente Discord-Benachrichtigungen\n\nAutomatische Benachrichtigungen bei neuen Filmen und Episoden via Discord Webhook\nSchöne Discord-Embeds mit Thumbnail, Beschreibung und Links zu externen Datenbanken (IMDb, TMDb, AniDB, AniList, TVDB)\n\n**Smarte Episoden-Gruppierung**\n\nEpisoden werden intelligent gebündelt statt einzeln gemeldet — z.B. \"Staffel 1: Episode 1-12\" statt 12 einzelne Nachrichten\nKonfigurierbares Zeitfenster für die Gruppierung\n\n**Qualitäts-Upgrade-Erkennung**\n\nErkennt automatisch ob eine Datei neu ist oder nur ein Qualitäts-Upgrade einer bestehenden Datei\nKeine Spam-Benachrichtigungen mehr beim Ersetzen von Dateien durch bessere Versionen\nStabile Erkennung über Provider-IDs (AniDB, TMDb etc.) — funktioniert auch mit Shokofin/VFS\n\n**Robuste Metadaten-Verarbeitung**\n\nVerzögerte Verarbeitung damit Jellyfin Zeit hat Metadaten zu laden\nDreistufige Validierung: beim Hinzufügen, beim Einreihen und beim Senden\nAutomatische Unterdrückung von reorganisierten Items (Pfad-/Metadata-Änderungen)\n\n**Konfigurierbar**\n\nBenachrichtigungen für Filme und Episoden einzeln aktivierbar\nUpgrade-Unterdrückung optional\nAnpassbare Verzögerung, Gruppierungsfenster, Bot-Name und Embed-Farbe",
|
||||
"targetAbi": "10.11.0.0",
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.0.14/smartnotify_0.0.14.zip",
|
||||
"checksum": "428e0cc3a00c873381dc2f2f198f3907",
|
||||
"timestamp": "2026-03-01T17:36:26Z"
|
||||
},
|
||||
{
|
||||
"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.13/smartnotify_0.0.13.zip",
|
||||
"checksum": "2d303e8dc214a58e038f516076840d5b",
|
||||
"timestamp": "2026-03-01T17:21:50Z"
|
||||
"sourceUrl": "https://git.tdpi.dev/TDPI/jellyfin-plugin-smartnotify/releases/download/v0.1.0/smartnotify_0.1.0.zip",
|
||||
"checksum": "2db7d09e4fd669a5cd25a6649397ea98",
|
||||
"timestamp": "2026-03-05T16:15:11Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user