Files
FanKarr/server/routes/downloads.ts
T

85 lines
4.5 KiB
TypeScript

import { Router } from 'express'
import path from 'path'
import fs from 'fs'
import { requireAuth } from '../auth.js'
import { logger } from '../logger.js'
import { DATA_DIR } from '../config.js'
import { readSettings } from '../settings.js'
import { dispatchDownload, dispatchList, dispatchRemove } from '../torrent-clients/index.js'
import { readAvailable, readInfohashMap, readSerieData } from '../lib/github-cache.js'
import { extractTorrentsFromSerieData, buildResolvedEpisodes } from '../lib/serie-helpers.js'
import { recentOrganized } from '../lib/notifs.js'
const router = Router()
router.post('/download', requireAuth, async (req, res) => {
// Préférer le fichier .torrent au magnet : métadonnées immédiates, pas de dépendance DHT/trackers
const torrent_url: string | null = req.body.torrent_url ?? null
const magnet : string | null = req.body.magnet ?? null
const url = torrent_url ?? magnet
if (!url) { res.status(400).json({ error: 'Lien du torrent ou magnet requis' }); return }
const file_index: number | null = req.body.file_index != null ? Number(req.body.file_index) : null
const file_path : string | null = req.body.file_path ?? null
const infohash : string | null = req.body.infohash ?? null
logger.info('api', `Téléchargement demandé via ${torrent_url ? '.torrent' : 'magnet'}${file_path ? ` (${file_path})` : ''}`)
const results = await dispatchDownload(url, { file_index, file_path, infohash, magnet })
res.status(results.every((r: any) => r.ok) ? 200 : 207).json({ results })
})
router.delete('/torrent/:hash', requireAuth, async (req, res) => {
const hash = String(req.params.hash)
const deleteFiles = req.query.deleteFiles === 'true'
if (!hash) { res.status(400).json({ error: 'Hash du torrent requis' }); return }
try {
const result = await dispatchRemove(hash, deleteFiles)
res.json(result)
} catch (err) {
logger.error('api', `Échec de la suppression du torrent : ${err instanceof Error ? err.message : err}`)
res.status(500).json({ error: err instanceof Error ? err.message : 'Erreur inattendue, consultez les journaux' })
}
})
router.get('/downloads', requireAuth, async (_req, res) => {
try {
const { category } = readSettings()
const infohashMap = await readInfohashMap()
const torrents = await dispatchList(category ?? 'fankai', infohashMap)
let organized: Record<string, Record<string, any>> = {}
try { const p = path.join(DATA_DIR, 'organized.json'); if (fs.existsSync(p)) organized = JSON.parse(fs.readFileSync(p, 'utf-8')) } catch {}
const enriched = await Promise.all(torrents.map(async (t: any) => {
const orgFiles = organized[t.hash] ?? {}
let totalFiles = 1
let serieName: string | undefined
let serieId: number | undefined
let episodes: any[] = []
try {
const availableIds = await readAvailable()
for (const id of availableIds) {
const sd = await readSerieData(id)
if (!sd) continue
const match = extractTorrentsFromSerieData(sd).find((st: any) => st.infohash?.toLowerCase() === t.hash?.toLowerCase())
if (match) {
const resolved = buildResolvedEpisodes(sd, match.infohash, match.season_number)
totalFiles = resolved.length || 1
serieName = sd.title ?? sd.show_title
serieId = sd.id
episodes = resolved
break
}
}
} catch {}
const doneFiles = Object.keys(orgFiles).length
const organizeState: 'none' | 'partial' | 'done' = doneFiles >= totalFiles ? 'done' : doneFiles > 0 ? 'partial' : 'none'
const notif = recentOrganized.find(n => n.hash === t.hash)
const imported = recentOrganized.find(n => n.hash === t.hash && n.done > 0)
return { ...t, organizeState, organizeProgress: { done: doneFiles, total: totalFiles }, errorFiles: notif?.errorFiles ?? [], importedAt: imported?.at ?? null, serieId, serieName, episodes }
}))
res.json(enriched)
} catch (err) {
logger.error('api', `Échec du chargement des téléchargements : ${err instanceof Error ? err.message : err}`)
res.status(500).json({ error: err instanceof Error ? err.message : 'Erreur inattendue, consultez les journaux' })
}
})
export default router