import { Router } from 'express' import axios from 'axios' const router = Router() let sid: string | null = null let sidExpiry = 0 async function getCookie(): Promise { if (sid && Date.now() < sidExpiry) return sid const host = process.env.QBT_HOST const user = process.env.QBT_USER const pass = process.env.QBT_PASSWORD if (!host || !user || !pass) throw new Error('QBT_HOST / QBT_USER / QBT_PASSWORD not configured') const loginUrl = `${host}/api/v2/auth/login` console.log(`[qbt] POST ${loginUrl} user=${user}`) const res = await axios.post( loginUrl, new URLSearchParams({ username: user, password: pass }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': host, 'Origin': host }, validateStatus: s => s < 400, } ) console.log(`[qbt] status=${res.status} headers=${JSON.stringify(res.headers)} body=${JSON.stringify(res.data)}`) const cookie = (res.headers['set-cookie'] ?? []).find((c: string) => c.startsWith('SID=')) if (!cookie) throw new Error(`qBittorrent login failed (${res.status}): "${String(res.data).trim()}"`) sid = cookie.split(';')[0] sidExpiry = Date.now() + 55 * 60 * 1000 return sid } router.get('/stats', async (_req, res) => { try { const host = process.env.QBT_HOST if (!host) { res.status(503).json({ error: 'QBT_HOST not configured' }); return } const cookie = await getCookie() const headers = { Cookie: cookie, Referer: host, Origin: host } const [infoRes, torrentsRes] = await Promise.all([ axios.get(`${host}/api/v2/transfer/info`, { headers }), axios.get(`${host}/api/v2/torrents/info`, { headers }), ]) const info = infoRes.data // eslint-disable-next-line @typescript-eslint/no-explicit-any const torrents: any[] = torrentsRes.data ?? [] const downloading = torrents.filter(t => ['downloading', 'stalledDL', 'metaDL', 'forcedDL'].includes(t.state)) const seeding = torrents.filter(t => ['uploading', 'stalledUP', 'forcedUP'].includes(t.state)) const paused = torrents.filter(t => ['pausedDL', 'pausedUP'].includes(t.state)) res.json({ dlSpeed: info.dl_info_speed ?? 0, ulSpeed: info.ul_info_speed ?? 0, downloading: downloading.length, seeding: seeding.length, paused: paused.length, total: torrents.length, active: downloading.slice(0, 5).map(t => ({ name: t.name, progress: t.progress, dlSpeed: t.dlspeed, size: t.size, state: t.state, })), }) } catch (err: unknown) { if (err instanceof Error && err.message.includes('login failed')) sid = null const msg = err instanceof Error ? err.message : 'Unknown error' res.status(500).json({ error: msg }) } }) export default router