diff --git a/.github/images/banner.svg b/.github/images/banner.svg
new file mode 100644
index 0000000..257bdc4
--- /dev/null
+++ b/.github/images/banner.svg
@@ -0,0 +1,64 @@
+
diff --git a/.github/images/catalog.png b/.github/images/catalog.png
index 81b1b98..eae2d61 100644
Binary files a/.github/images/catalog.png and b/.github/images/catalog.png differ
diff --git a/.github/images/download.png b/.github/images/download.png
deleted file mode 100644
index 2835c9d..0000000
Binary files a/.github/images/download.png and /dev/null differ
diff --git a/.github/images/downloads.png b/.github/images/downloads.png
new file mode 100644
index 0000000..abdc596
Binary files /dev/null and b/.github/images/downloads.png differ
diff --git a/.github/images/log.png b/.github/images/logs.png
similarity index 100%
rename from .github/images/log.png
rename to .github/images/logs.png
diff --git a/.github/images/serie.png b/.github/images/serie.png
index 260531f..5cd18d2 100644
Binary files a/.github/images/serie.png and b/.github/images/serie.png differ
diff --git a/.github/images/settings.png b/.github/images/settings.png
new file mode 100644
index 0000000..60f4ff4
Binary files /dev/null and b/.github/images/settings.png differ
diff --git a/README.md b/README.md
index 886feee..2eaeac8 100644
--- a/README.md
+++ b/README.md
@@ -1,58 +1,72 @@
-# Fankarr
+# FanKarr





-

+
-**Gestionnaire de téléchargements pour le catalogue [Fankai](https://fankai.fr)**
+**Gestionnaire de téléchargements pour le catalogue [Fankai](https://fankai.fr)**
Inspiré de Radarr/Sonarr — interface dédiée aux éditions Kai & Yabai
+
+
---
+## Aperçu
+
+

-> Vue catalogue : toutes les séries Fankai avec leur état d'organisation. Les badges indiquent si les fichiers sont déjà présents dans votre médiathèque Jellyfin.
+> Vue catalogue : toutes les séries Fankai. Une barre en bas de chaque affiche indique l'état — disponible, en cours de téléchargement ou déjà importé dans votre médiathèque.
+
+---
+
+
+
+
+> Vue série : épisodes avec badge de langue (VOSTFR / MULTI), progression par fichier, et dropdown de sélection quand plusieurs sources sont disponibles.
+
+---
+
+
+
+
+> Vue téléchargements : suivi en temps réel, état d'organisation par torrent et détail des erreurs au survol.
---
## Fonctionnalités
-- **Catalogue complet** des séries Fankai avec affiches, statut et badges d'état
-- **Téléchargement en un clic** vers qBittorrent — par épisode, par saison ou intégrale
-- **Organisation automatique** des fichiers téléchargés vers votre médiathèque (hardlink ou déplacement)
-- **Suivi par épisode** — badge ✓ organisé sur chaque épisode et compteur par saison
-- **Notifications** toast automatiques quand une organisation se termine
-- **Page téléchargements** avec statut d'organisation et badge erreurs avec détail au hover
-- **Logs centralisés** avec filtre par niveau/source, rotation automatique et clear depuis l'UI
-- **Multi-client torrent** — architecture extensible (qBittorrent supporté)
+### 📥 Téléchargement
+- **Catalogue complet** des séries Fankai avec affiches et état de disponibilité
+- **Téléchargement en un clic** vers votre client torrent — par épisode, par saison ou en intégrale
+- **Dropdown intelligent** sur chaque bouton quand plusieurs sources sont disponibles (qualités différentes, packs différents)
+- **"Tout télécharger"** unifié — sélectionne automatiquement les épisodes non couverts par l'intégrale choisie et complète avec les packs individuels
+- **Progression par fichier** — barre de téléchargement au niveau de l'épisode, même pour les fichiers dans un pack intégrale
+
+### 🗂️ Organisation
+- **Import automatique** des fichiers terminés vers votre médiathèque, déclenché dès la fin d'un téléchargement ou toutes les 5 minutes en arrière-plan
+- **Scan de médiathèque** — détecte les fichiers déjà présents sur le disque et les référence sans les déplacer
+- **Mode hardlink** (recommandé), **déplacement** ou **copie** — le hardlink préserve le seeding du client torrent
+- **Désimport automatique** optionnel quand un fichier est supprimé du disque
+
+### 🏷️ Métadonnées & Affichage
+- **Badges de langue** VOSTFR et MULTI avec drapeaux sur chaque épisode (détectés automatiquement)
+- **Support NFO** — nommage compatible avec les plugins Jellyfin/Plex utilisant les fichiers `.nfo`
+- **Intégration Plex** — connexion à votre serveur Plex pour déclencher le scan médiathèque après import
+
+### ⚙️ Système
+- **Logs centralisés** — filtre par niveau et source, rotation automatique, clear depuis l'UI
- **Authentification** par mot de passe avec session JWT
-- **Compatible Runtipi / Docker**
-
----
-
-
-
-> Vue série : détail des épisodes avec leur état de disponibilité torrent et d'organisation dans Jellyfin.
-
----
-
-
-
-> Vue téléchargements : suivi en temps réel de l'organisation avec détail des erreurs au survol.
-
----
-
-
-
-> Page logs : historique complet des événements avec filtres et gestion de la taille.
+- **Multi-client torrent** — architecture extensible (qBittorrent supporté)
+- **Compatible Docker / Runtipi / Binaire autonome**
---
@@ -60,9 +74,9 @@ Inspiré de Radarr/Sonarr — interface dédiée aux éditions Kai & Yabai
Les données du catalogue sont récupérées depuis le repo [`fankarr-scraper`](https://github.com/masutayunikon/fankarr-scraper).
-Ce repo contient un pipeline de scripts Python qui collecte, parse et résout les torrents Fankai depuis les trackers publics, puis les croise avec l'API metadata de Fankai pour identifier chaque épisode. Les scripts tournent automatiquement **toutes les 6 heures** via GitHub Actions et publient le fichier `torrent_final.json` dans le repo.
+Ce repo contient un pipeline Python qui collecte, parse et résout les torrents Fankai depuis les trackers publics, les croise avec l'API metadata de Fankai pour identifier chaque épisode, et publie le résultat automatiquement **toutes les 6 heures** via GitHub Actions. Les données sont organisées par série dans un dossier `/serie` et des fichiers d'index à la racine du repo.
-Fankarr récupère ce fichier au démarrage et via le bouton **Mettre à jour** dans les paramètres. Aucun scraping ne se fait en local.
+FanKarr récupère ces données au démarrage et via le bouton **Mettre à jour** dans les paramètres. Aucun scraping ne se fait en local.
---
@@ -70,7 +84,7 @@ Fankarr récupère ce fichier au démarrage et via le bouton **Mettre à jour**
### Docker Compose (recommandé)
-**Prérequis** : Docker + Docker Compose, qBittorrent accessible en réseau.
+**Prérequis** : Docker + Docker Compose, un client torrent accessible en réseau.
```yaml
services:
@@ -93,83 +107,121 @@ services:
docker compose up -d
```
-> **⚠️ Note sur les hardlinks** : Pour que le mode `hardlink` fonctionne, vos dossiers de téléchargement complets et votre médiathèque doivent être sur le **même filesystem**. La solution recommandée est de monter un seul volume racine (ex: `/votre/chemin:/media`) et de configurer vos chemins à l'intérieur — comme le font Radarr et Sonarr.
+> **⚠️ Note sur les hardlinks** : Pour que le mode `hardlink` fonctionne, vos dossiers de téléchargement complets et votre médiathèque doivent être sur le **même filesystem**. La solution recommandée est de monter un seul volume racine (ex: `/votre/chemin:/media`) et de configurer vos chemins à l'intérieur — comme Radarr et Sonarr le font.
>
-> Vous pouvez tout à fait monter plusieurs volumes si vous le souhaitez, mais dans ce cas les hardlinks ne seront pas possibles entre deux volumes différents et Fankarr basculera automatiquement en copie.
+> Plusieurs volumes sont possibles mais les hardlinks ne fonctionneront pas entre deux volumes différents — FanKarr basculera automatiquement en copie.
+
+---
### Binaire autonome (Windows / Linux / macOS)
Téléchargez l'archive correspondant à votre système depuis les [Releases GitHub](https://github.com/masutayunikon/fankarr/releases/latest) :
-| Système | Archive |
-| --------------------- | ------------------------- |
-| Windows x64 | `fankarr-windows-x64.zip` |
-| Linux x64 | `fankarr-linux-x64.zip` |
-| macOS (Apple Silicon) | `fankarr-macos.zip` |
+| Système | Archive |
+| -------------------------------- | -------------------------------- |
+| Windows x64 | `fankarr-windows-x64.zip` |
+| Windows Legacy (anciens CPU) | `fankarr-windows-legacy.zip` |
+| Linux x64 | `fankarr-linux-x64.zip` |
+| macOS (Apple Silicon) | `fankarr-macos.zip` |
-Extrayez l'archive — elle contient le binaire **et** le dossier `public/` qui doivent rester ensemble :
+Extrayez l'archive — tous les fichiers doivent rester ensemble dans le même répertoire :
```
fankarr-windows-x64/
-├── fankarr.exe ← lancer ce fichier
-└── public/ ← ne pas déplacer séparément
+├── fankarr.exe ← lancer ce fichier
+├── organize-worker.js ← worker d'organisation (ne pas déplacer)
+├── .env ← configuration optionnelle (voir Variables d'environnement)
+└── public/ ← assets frontend (ne pas déplacer)
```
-**Linux / macOS** : rendre le binaire exécutable avant le premier lancement :
+> Le fichier `.env` permet de configurer le port, la durée des sessions, etc. sans passer par des variables d'environnement système. Il est chargé automatiquement au démarrage si présent à côté du binaire.
+
+**Linux / macOS** — rendre le binaire exécutable avant le premier lancement :
```bash
chmod +x fankarr
./fankarr
```
-Fankarr sera accessible sur `http://localhost:9898`. Les données (config, logs, base de données) sont sauvegardées dans un dossier `data/` créé automatiquement à côté du binaire.
+FanKarr sera accessible sur `http://localhost:9898`. La configuration, les logs et la base de données sont sauvegardés dans un dossier `config/` créé automatiquement à côté du binaire.
+
+---
### Variables d'environnement
-| Variable | Défaut | Description |
-| ---------------- | ------------ | ----------------------------------------------------------------------- |
-| `PUID` | `1000` | UID utilisateur pour les permissions fichiers (Docker) |
-| `PGID` | `1000` | GID utilisateur pour les permissions fichiers (Docker) |
-| `TZ` | — | Timezone (ex: `Europe/Paris`) |
-| `PORT` | `9898` | Port d'écoute du serveur |
-| `JWT_SECRET` | auto-généré | Secret JWT — généré automatiquement dans `/config/secret.key` si absent |
-| `GITHUB_RAW_URL` | repo scraper | URL du `torrent_final.json` à utiliser |
+| Variable | Défaut | Description |
+| ------------------ | ------------- | ------------------------------------------------------------------------------- |
+| `PUID` | `1000` | UID utilisateur pour les permissions fichiers (Docker) |
+| `PGID` | `1000` | GID utilisateur pour les permissions fichiers (Docker) |
+| `TZ` | — | Timezone (ex: `Europe/Paris`) |
+| `PORT` | `9898` | Port d'écoute du serveur |
+| `JWT_SECRET` | auto-généré | Secret JWT — généré automatiquement dans `/config/secret.key` si absent |
+| `AUTH_TOKEN_EXPIRY`| `30d` | Durée de validité du token de connexion (ex: `7d`, `1y`, `never`) |
+| `DATA_DIR` | `./config` | Répertoire de stockage des données (binaire uniquement) |
+| `GITHUB_RAW_URL` | repo scraper | URL de base du repo scraper si vous hébergez votre propre instance |
+
+---
### Premier démarrage
1. Ouvrir `http://localhost:9898`
2. Créer un mot de passe à la première connexion
-3. Aller dans **Paramètres** → ajouter votre client qBittorrent
-4. Vérifier les chemins `completePath` et `mediaPath`
-5. Choisir le mode d'organisation : `hardlink` (recommandé) ou `move`
-6. Retourner au catalogue et télécharger
+3. Aller dans **Paramètres** → configurer votre client torrent
+4. Renseigner le chemin vers le dossier des **torrents terminés** et celui de votre **médiathèque**
+5. Choisir le mode d'organisation : `hardlink` (recommandé), `move` ou `copy`
+6. Optionnel : lancer le **scan** pour référencer les fichiers déjà présents sur le disque
+7. Retourner au catalogue et télécharger
+
+
+
---
## Organisation des fichiers
-Fankarr organise automatiquement les fichiers terminés toutes les **30 secondes** vers la structure attendue par Jellyfin :
+FanKarr organise les fichiers terminés vers la structure attendue par Jellyfin / Plex :
```
-/media/Kai/
-└── Black Lagoon Henshū/
- └── Saison 1/
- ├── Black Lagoon Henshū S01E01.mkv
- ├── Black Lagoon Henshū S01E02.mkv
- └── ...
+Black Lagoon Henshū/
+├── Saison 1/
+│ ├── Black Lagoon Henshū.S01E01.MULTI.1080p.x264-FANKAI.mkv
+│ ├── Black Lagoon Henshū.S01E02.MULTI.1080p.x264-FANKAI.mkv
+│ ├── Black Lagoon Henshū.S01E03.MULTI.1080p.x264-FANKAI.mkv
+│ └── Black Lagoon Henshū.S01E04.MULTI.1080p.x264-FANKAI.mkv
+├── Saison 2/
+│ ├── Black Lagoon Henshū.S02E05.MULTI.1080p.x264-FANKAI.mkv
+│ ├── Black Lagoon Henshū.S02E06.MULTI.1080p.x264-FANKAI.mkv
+│ └── Black Lagoon Henshū.S02E07.MULTI.1080p.x264-FANKAI.mkv
+└── Saison 3/
+ └── Black Lagoon Henshū.S03E08.MULTI.1080p.x264-FANKAI.mkv
```
-Le mode **hardlink** est recommandé si vos dossiers `complete` et `media` sont sur le même filesystem — les fichiers ne sont pas déplacés, qBittorrent continue de seeder.
+Le mode **hardlink** est recommandé si vos dossiers de téléchargement et de médiathèque sont sur le même filesystem — les fichiers ne sont pas déplacés et le client torrent continue de seeder sans interruption.
+
+L'organisation se déclenche :
+- **Automatiquement** dès la fin d'un téléchargement, et toutes les 5 minutes en arrière-plan pour les torrents déjà en seeding
+- **Manuellement** depuis le bouton **Importer** dans la vue téléchargements
+
+Le **scan médiathèque** permet de référencer des fichiers déjà présents sur le disque sans les déplacer — utile si vous avez déjà une bibliothèque existante.
+
+---
+
+## Logs
+
+
+
+
+> Tous les événements sont horodatés et filtrables par source (`organize`, `api`, `torrent`, …) et niveau (`info`, `warn`, `error`, `debug`).
---
## Runtipi
-Fankarr est disponible dans l'appstore personnalisé. Pour l'installer :
+FanKarr est disponible dans l'appstore personnalisé. Pour l'installer :
1. Dans Runtipi, aller dans **Paramètres → App Stores**
2. Ajouter l'URL : `https://github.com/Masutayunikon/runtipi-appstore`
-3. Fankarr apparaît dans le catalogue → **Installer**
+3. FanKarr apparaît dans le catalogue → **Installer**
---
@@ -177,9 +229,10 @@ Fankarr est disponible dans l'appstore personnalisé. Pour l'installer :
| Couche | Technologie |
| ----------------- | ---------------------------------------- |
-| Frontend | Vue 3 + Vite + Tailwind CSS |
+| Frontend | Vue 3 + Vite + Tailwind CSS v4 |
| Backend | Express 5 + TypeScript |
| Auth | JWT + bcrypt |
+| Worker | Node Worker Threads (organisation async) |
| Packaging Docker | `node:20-slim` + pnpm + gosu (PUID/PGID) |
| Packaging binaire | Bun (self-contained, pas de Node requis) |
@@ -190,4 +243,4 @@ Fankarr est disponible dans l'appstore personnalisé. Pour l'installer :
- [fankai.fr](https://fankai.fr) — Le projet Fankai
- [Plugin Jellyfin Fankai](https://github.com/Nackophilz/fankai_jellyfin) — Reconnaissance des métadonnées dans Jellyfin
- [fankarr-scraper](https://github.com/masutayunikon/fankarr-scraper) — Pipeline de collecte des torrents
-- [runtipi-appstore](https://github.com/Masutayunikon/runtipi-appstore) — Appstore Runtipi personnel
\ No newline at end of file
+- [runtipi-appstore](https://github.com/Masutayunikon/runtipi-appstore) — Appstore Runtipi personnel
diff --git a/src/components/serie/SerieSeasonCard.vue b/src/components/serie/SerieSeasonCard.vue
index 9f29d28..76bf951 100644
--- a/src/components/serie/SerieSeasonCard.vue
+++ b/src/components/serie/SerieSeasonCard.vue
@@ -97,7 +97,7 @@
@click="!isDownloaded(`season-${season.id}-${i}`) && !isAlreadyQueued(t) && (emit('download', `season-${season.id}-${i}`, t.torrent_url, t.magnet), seasonMenuOpen = false)"
>
- {{ torrentLabel(t, i) }}
+ {{ groupLabels(season.torrents)[i] }}
@@ -191,7 +191,7 @@
@click="!isDownloaded(`ep-${ep.id}-${i}`) && !isAlreadyQueued(t) && (emit('download', `ep-${ep.id}-${i}`, t.torrent_url, t.magnet, t.file_index ?? null, t.file_path ?? null), epOptionsOpen = null)"
>
- {{ torrentLabel(t, i) }}
+ {{ groupLabels(ep.torrents)[i] }}
@@ -456,15 +456,21 @@ function epLang(ep: any): 'MULTI' | 'VOSTFR' | null {
return null
}
-function torrentLabel(torrent: any, index: number): string {
- if (torrent.torrent_name) return torrent.torrent_name
- const raw = torrent.raw ?? ''
- const quality = raw.match(/\b(2160p|4K|1080p|720p|480p)\b/i)?.[1]?.toUpperCase()
- const lang = raw.match(/\b(VOSTFR|MULTI|VF|VO|FR|EN)\b/i)?.[1]?.toUpperCase()
- if (quality && lang) return `${quality} · ${lang}`
- if (quality) return quality
- if (lang) return lang
- return raw.length > 40 ? raw.slice(0, 40) + '…' : (raw || `Option ${index + 1}`)
+// ── Labels de groupe ───────────────────────────────────────────
+// Si tous les torrent_name sont présents ET distincts → on les utilise.
+// Sinon → titre Nyaa brut (raw), plus informatif quand les noms se ressemblent.
+function labelFromRaw(raw: string, index: number): string {
+ if (!raw) return `Option ${index + 1}`
+ return raw.length > 65 ? raw.slice(0, 65) + '…' : raw
+}
+
+function groupLabels(torrents: any[]): string[] {
+ const names = torrents.map((t: any) => t.torrent_name ?? null)
+ const allPresent = names.every(n => n !== null && String(n).trim() !== '')
+ const allUnique = allPresent && new Set(names).size === names.length
+ if (allUnique) return names as string[]
+ // Fallback : titre Nyaa brut
+ return torrents.map((t: any, i: number) => labelFromRaw(t.raw ?? '', i))
}
// Packs parents distincts couvrant cette saison (quand pas de pack saison explicite)
@@ -472,17 +478,19 @@ function torrentLabel(torrent: any, index: number): string {
const uniquePackOptions = computed(() => {
if (props.season.torrents?.length) return []
const seen = new Set()
- const opts: { infohash: string; label: string }[] = []
+ const raw: { infohash: string; torrent: any }[] = []
for (const ep of props.season.episodes) {
if (!ep.available) continue
for (const t of (ep.torrents ?? [])) {
const hash = t.infohash ?? extractHash(t)
if (!hash || seen.has(hash)) continue
seen.add(hash)
- opts.push({ infohash: hash, label: torrentLabel(t, opts.length) })
+ raw.push({ infohash: hash, torrent: t })
}
}
- return opts.length > 1 ? opts : []
+ if (raw.length <= 1) return []
+ const labels = groupLabels(raw.map(r => r.torrent))
+ return raw.map((r, i) => ({ infohash: r.infohash, label: labels[i] }))
})
function formatDate(d: string): string {