From 79e73b603f0b2a0bc70331d8fad7056827cf71b0 Mon Sep 17 00:00:00 2001 From: MasutaYunikon Date: Thu, 26 Mar 2026 07:05:53 +0100 Subject: [PATCH] feat: hors-fankai badge and fix series datetime and typo --- server/index.ts | 33 +++++++++++++++++++++++++++++---- src/views/SerieView.vue | 40 ++++++++++++++++++++++------------------ 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/server/index.ts b/server/index.ts index 7a0c3c9..eb05336 100644 --- a/server/index.ts +++ b/server/index.ts @@ -511,7 +511,7 @@ app.get('/api/series/:id', requireAuth, async (req, res) => { // ── Torrents niveau épisode ───────────────────────────── for (const ep of season.episodes ?? []) { for (const t of (ep.torrents ?? [])) { - const ref = { torrent_url: t.torrent_url, magnet: t.magnet, type: 'episode', raw: t.title, manual: t.manual ?? false } + const ref = { torrent_url: t.torrent_url, magnet: t.magnet, type: 'episode', raw: t.title, manual: t.manual ?? false, fankai: t.fankai ?? true } episodeTorrentMap[ep.id] = ref availableEpisodeIds.add(ep.id) @@ -536,13 +536,38 @@ app.get('/api/series/:id', requireAuth, async (req, res) => { // ── Enrichissement des saisons API ─────────────────────────────── const enrichedSeasons = seasonsWithEpisodes.map((season: any) => { const eps = season.episodes.map((ep: any) => { - // Chercher le torrent : niveau épisode d'abord, sinon le torrent de saison/intégrale sert juste de disponibilité const epTorrent = episodeTorrentMap[ep.id] ?? null - // original_filename vient de l'API Fankai directement + + // Si pas de torrent épisode, chercher le fankai du torrent qui couvre cet épisode via paths + let fankai: boolean | null = epTorrent ? (epTorrent.fankai ?? true) : null + if (fankai === null && serieData) { + // Chercher dans les torrents série/saison quel torrent a un path pour cet épisode + outer: for (const sd_season of serieData.seasons ?? []) { + for (const sdEp of sd_season.episodes ?? []) { + if (sdEp.id !== ep.id) continue + for (const p of sdEp.paths ?? []) { + const ih = typeof p === 'object' ? p.infohash?.toLowerCase() : null + if (!ih) continue + // Chercher dans les torrents série + for (const t of serieData.torrents ?? []) { + if (t.infohash?.toLowerCase() === ih) { fankai = t.fankai ?? true; break outer } + } + // Chercher dans les torrents saison + for (const s of serieData.seasons ?? []) { + for (const t of s.torrents ?? []) { + if (t.infohash?.toLowerCase() === ih) { fankai = t.fankai ?? true; break outer } + } + } + } + } + } + } + return { ...ep, available : availableEpisodeIds.has(ep.id), - torrent : epTorrent, + torrent : epTorrent ? { ...epTorrent, fankai: fankai ?? true } : null, + fankai : fankai, organized : organizedEpisodeIds.has(ep.id), } }) diff --git a/src/views/SerieView.vue b/src/views/SerieView.vue index 90a257f..11a726f 100644 --- a/src/views/SerieView.vue +++ b/src/views/SerieView.vue @@ -60,7 +60,7 @@ {{ data.serie.plot }}

- +
- + @@ -188,7 +188,6 @@ :key="ep.id" class="px-5 py-3 hover:bg-white/[0.02] transition" > -
{{ formatDate(ep.aired) }} - {{ ep.duration }} min + {{ formatDuration(ep.duration) }}
- +
- + Hors Fankai - + - ✓ organisé + ✓ importé - + ✓ téléchargé @@ -263,7 +262,7 @@
- +
@@ -308,7 +307,6 @@ import { useRoute } from 'vue-router' import { useSeriesStore } from '@/stores/series' import type { Season } from '@/stores/series' -// ── Composant icône download ────────────────────────────────── const DownloadIcon = defineComponent({ props: { spinning: Boolean, size: { type: Number, default: 14 } }, setup(props) { @@ -320,7 +318,6 @@ const DownloadIcon = defineComponent({ } }) -// ── Setup ───────────────────────────────────────────────────── const route = useRoute() const store = useSeriesStore() @@ -329,7 +326,6 @@ const downloading = ref>(new Set()) const downloaded = ref>(new Set()) const toast = ref<{ type: 'success' | 'error'; message: string } | null>(null) -// ── Progression downloads ────────────────────────────────────── interface ActiveTorrent { hash: string; progress: number; state: string; name: string } const activeTorrents = ref([]) let pollTimer: ReturnType | null = null @@ -360,7 +356,6 @@ function extractHash(torrent: { magnet?: string | null; torrent_url?: string | n return m ? m[1].toLowerCase() : null } -// ── Data ─────────────────────────────────────────────────────── const data = computed(() => store.currentSerie) function seasonAvailableCount(season: Season) { @@ -377,6 +372,15 @@ function formatDate(d: string) { return new Date(d).toLocaleDateString('fr-FR', { year: 'numeric', month: 'short', day: 'numeric' }) } +// Durée en secondes → "1h 23min" ou "45 min" +function formatDuration(seconds: number): string { + if (!seconds || seconds <= 0) return '' + const h = Math.floor(seconds / 3600) + const m = Math.floor((seconds % 3600) / 60) + if (h > 0) return m > 0 ? `${h}h ${m}min` : `${h}h` + return `${m} min` +} + function showToast(type: 'success' | 'error', message: string) { toast.value = { type, message } setTimeout(() => { toast.value = null }, 3500)