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
This commit is contained in:
MasutaYunikon committed 2026-05-22 19:10:52 +02:00
1 parent c9040ea68c
commit 6ce59b9e04
3 files changed
+36 -3

No files matched your search

+5 -1
View File
@@ -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`)
+16
View File
@@ -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}"`)
+15 -2
View File
@@ -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<string, string | number>): Promise<string>
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}` : ''})`)
},