From 6ce59b9e0458ecf5b3bc55d703c132a4ac39db66 Mon Sep 17 00:00:00 2001 From: MasutaYunikon Date: Fri, 22 May 2026 19:10:52 +0200 Subject: [PATCH] feat(client): ajour de alldebrid client fix(log): changement pour une notification plus explicit lors du test client fix(import): supprime les entrees organiser quand on supprime un fichier fix(start): url cliquable au demarrage dans le terminal --- server/index.ts | 6 +++++- server/routes/import.ts | 16 ++++++++++++++++ server/torrent-clients/qbittorrent.ts | 17 +++++++++++++++-- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/server/index.ts b/server/index.ts index 8050572..afb5a81 100644 --- a/server/index.ts +++ b/server/index.ts @@ -12,6 +12,7 @@ import synologyDsDriver from './torrent-clients/synology-ds.js' import utorrentDriver from './torrent-clients/utorrent.js' import rtorrentDriver from './torrent-clients/rtorrent.js' import realDebridDriver from './torrent-clients/real-debrid.js' +import allDebridDriver from './torrent-clients/alldebrid.js' import { autoOrganizeAll, scanMediaPath, syncFilenameChanges, migrateOrganizedEpisodeIds } from './organize.js' import { logger } from './logger.js' import { DATA_DIR, BASE_DIR } from './config.js' @@ -37,6 +38,7 @@ registerDriver(synologyDsDriver) registerDriver(utorrentDriver) registerDriver(rtorrentDriver) registerDriver(realDebridDriver) +registerDriver(allDebridDriver) const app = express() const PORT = Number(process.env.PORT) || 9898 @@ -76,7 +78,9 @@ if (fs.existsSync(PUBLIC_PATH)) { const server = http.createServer({ maxHeaderSize: 32768 }, app) server.listen(PORT, async () => { - logger.info('api', `Serveur démarré sur le port ${PORT}`) + const url = `http://localhost:${PORT}` + console.log(`\n FanKarr → \x1b]8;;${url}\x1b\\${url}\x1b]8;;\x1b\\\n`) + logger.info('api', `Serveur démarré — ${url}`) try { const available = await readAvailable() logger.info('api', `Cache GitHub initialisé — ${available.length} séries disponibles`) diff --git a/server/routes/import.ts b/server/routes/import.ts index 29fe589..aabeb8e 100644 --- a/server/routes/import.ts +++ b/server/routes/import.ts @@ -89,6 +89,22 @@ router.post('/manual-import', requireAuth, async (req, res) => { if (file_path !== destPath) { const serieRootPath = path.join(mediaPath, serieTitle) const isInSeriePath = file_path.startsWith(serieRootPath + path.sep) || file_path.startsWith(serieRootPath + '/') + // Si une ancienne version de cet épisode existe à la destination (réassignation), + // la supprimer avant le rename pour éviter un état incohérent. + if (isInSeriePath && fs.existsSync(destPath) && destPath !== file_path) { + fs.unlinkSync(destPath) + logger.info('api', `Import manuel (réassignation) : suppression ancien fichier "${destFilename}"`) + // Nettoyer toute entrée organized qui pointait sur ce fichier supprimé + for (const [h, eps] of Object.entries(organized)) { + for (const [epId, entry] of Object.entries(eps)) { + if (entry?.dest_path === destPath) { + delete (organized[h] as any)[epId] + logger.info('api', `Import manuel : suppression entrée stale ep ${epId} (ancien fichier écrasé)`) + } + } + if (Object.keys(organized[h]).length === 0) delete organized[h] + } + } if (isInSeriePath && !fs.existsSync(destPath)) { fs.renameSync(file_path, destPath) logger.info('api', `Import manuel (rename) : "${srcFilename}" → "${destFilename}"`) diff --git a/server/torrent-clients/qbittorrent.ts b/server/torrent-clients/qbittorrent.ts index 3e04d02..33960ff 100644 --- a/server/torrent-clients/qbittorrent.ts +++ b/server/torrent-clients/qbittorrent.ts @@ -6,6 +6,17 @@ import type { TorrentClientDriver, TorrentInfo, DownloadOptions } from './index.js' import { logger } from '../logger.js' +// qBittorrent répond normalement "Ok." mais certains setups (reverse proxy, etc.) +// peuvent wrapper la réponse en JSON — on accepte les deux +function isQbAddSuccess(text: string): boolean { + if (text.trim() === 'Ok.') return true + try { + const json = JSON.parse(text) + if (typeof json?.success_count === 'number') return json.success_count > 0 + } catch {} + return false +} + function mapState(state: string): TorrentInfo['state'] { if (['downloading', 'metaDL', 'queuedDL', 'stalledDL', 'forcedDL'].includes(state)) return 'downloading' if (['uploading', 'queuedUP', 'stalledUP', 'forcedUP'].includes(state)) return 'seeding' @@ -37,6 +48,7 @@ async function qbLogin(config: Record): Promise const text = await res.text() if (!res.ok) throw new Error(`Login échoué : ${text.trim() || res.status}`) + if (text.trim() === 'Fails.') throw new Error('Identifiants incorrects (vérifiez le nom d\'utilisateur et le mot de passe)') const cookie = (res.headers.get('set-cookie') ?? '').split(';')[0].trim() if (!cookie) throw new Error('Cookie de session introuvable') @@ -165,6 +177,7 @@ const QB: TorrentClientDriver = { }) if (!res.ok) return { online: false } const version = (await res.text()).trim() + if (version.startsWith('<')) throw new Error('Réponse HTML reçue — clé API invalide ou non supportée par cette version de qBittorrent') logger.debug('qbittorrent', `Healthcheck OK — version ${version}`) return { online: true, version } } catch (err) { @@ -296,7 +309,7 @@ const QB: TorrentClientDriver = { res = await fetch(`${config.url}/api/v2/torrents/add`, { method: 'POST', body: form, headers: authH }) } const text = await res.text() - if (text !== 'Ok.') throw new Error(`Ajout échoué : ${text}`) + if (!isQbAddSuccess(text)) throw new Error(`Ajout échoué : ${text}`) if (hash) { logger.info('qbittorrent', `Torrent ajouté — sélection fichier ${options.file_index} en attente des métadonnées`) @@ -339,7 +352,7 @@ const QB: TorrentClientDriver = { headers: { ...authH, 'Content-Type': 'application/x-www-form-urlencoded' }, }) const text = await res.text() - if (text !== 'Ok.') throw new Error(`Ajout échoué : ${text}`) + if (!isQbAddSuccess(text)) throw new Error(`Ajout échoué : ${text}`) logger.info('qbittorrent', `Torrent ajouté (catégorie: ${config.category ?? 'aucune'}${config.savePath ? `, dossier: ${config.savePath}` : ''})`) },