From 9b4dc4f7825bbfbb3c01adadad0c27f8d9b671d0 Mon Sep 17 00:00:00 2001 From: MasutaYunikon Date: Fri, 10 Apr 2026 17:37:49 +0200 Subject: [PATCH] feat: ajout d'une hashmap pour matcher les titres sur synologie et ajouter les hash de torrent manuellement --- server/index.ts | 13 ++++++++++--- server/torrent-clients/index.ts | 33 ++++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/server/index.ts b/server/index.ts index e3e1339..eea00af 100644 --- a/server/index.ts +++ b/server/index.ts @@ -170,6 +170,11 @@ async function readAvailable(): Promise { catch { return [] } } +async function readInfohashMap(): Promise> { + try { return await githubGet('infohash_map.json') as Record } + catch { return {} } +} + async function readSerieData(serieId: number): Promise { try { return await githubGet(`series/${serieId}.json`) } catch { return null } @@ -277,7 +282,7 @@ app.get('/api/series', requireAuth, async (_req, res) => { let organized: Record> = {} try { const p = path.join(DATA_DIR, 'organized.json'); if (fs.existsSync(p)) organized = JSON.parse(fs.readFileSync(p, 'utf-8')) } catch {} const activeTorrents = new Set() - try { const { category } = readSettings(); const active = await dispatchList(category ?? 'fankai'); for (const t of active) { if (t.hash && t.state === 'downloading') activeTorrents.add(t.hash.toLowerCase()) } } catch {} + try { const { category } = readSettings(); const infohashMap = await readInfohashMap(); const active = await dispatchList(category ?? 'fankai', infohashMap); for (const t of active) { if (t.hash && t.state === 'downloading') activeTorrents.add(t.hash.toLowerCase()) } } catch {} const seriesRaw = (Array.isArray(apiData) ? apiData : (apiData.series ?? [])).map(normalizeSerie) const serieDataMap = new Map() await Promise.all(seriesRaw.filter((s: any) => availableSet.has(s.id)).map(async (s: any) => { const sd = await readSerieData(s.id); if (sd) serieDataMap.set(s.id, sd) })) @@ -392,7 +397,8 @@ app.post('/api/download', requireAuth, async (req, res) => { app.get('/api/downloads', requireAuth, async (_req, res) => { try { const { category } = readSettings() - const torrents = await dispatchList(category ?? 'fankai') + const infohashMap = await readInfohashMap() + const torrents = await dispatchList(category ?? 'fankai', infohashMap) let organized: Record> = {} 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) => { @@ -828,9 +834,10 @@ server.listen(PORT, async () => { const autoOrganize = async () => { try { const { category } = readSettings() + const infohashMap = await readInfohashMap() const seriesData = await loadEnrichedSeriesData() await autoOrganizeAll( - () => dispatchList(category ?? 'fankai'), + () => dispatchList(category ?? 'fankai', infohashMap), seriesData, (result) => { if (result.done > 0 || result.errors > 0) { diff --git a/server/torrent-clients/index.ts b/server/torrent-clients/index.ts index 51821b0..01cfa0e 100644 --- a/server/torrent-clients/index.ts +++ b/server/torrent-clients/index.ts @@ -237,15 +237,27 @@ export async function dispatchDownload(url: string): Promise<{ uuid: string; nam // ─── Dispatch list ───────────────────────────────────────────────────────────── -export async function dispatchList(category?: string): Promise<(TorrentInfo & { client_uuid: string; client_name: string })[]> { - const clients = loadClients() +// Map inversée : titre normalisé → infohash (pour matcher les torrents Synology) +function buildTitleIndex(infohashMap: Record): Map { + const index = new Map() + for (const [hash, title] of Object.entries(infohashMap)) { + index.set(title.toLowerCase().trim(), hash.toLowerCase()) + } + return index +} + +export async function dispatchList( + category? : string, + infohashMap? : Record, +): Promise<(TorrentInfo & { client_uuid: string; client_name: string })[]> { + const clients = loadClients() const results: (TorrentInfo & { client_uuid: string; client_name: string })[] = [] + const titleIndex = infohashMap ? buildTitleIndex(infohashMap) : null for (const client of clients) { const driver = getDriver(client.type) if (!driver) continue - // Skip si le client est en cooldown suite à des échecs d'auth if (isInCooldown(client.uuid)) { const remaining = cooldownRemaining(client.uuid) logger.debug('torrent-clients', `Client "${client.name}" en cooldown — retry dans ${remaining}`) @@ -266,12 +278,23 @@ export async function dispatchList(category?: string): Promise<(TorrentInfo & { const save_path = hasRemap ? remapSavePath(t.save_path, remotePath, localPath) : t.save_path - results.push({ ...t, save_path, client_uuid: client.uuid, client_name: client.name }) + + // Pour Synology (hash = dbid_xxx), on essaie de résoudre le vrai infohash + // en matchant le titre du torrent dans la infohashMap + let hash = t.hash + if (titleIndex && t.hash.startsWith('dbid_')) { + const resolved = titleIndex.get(t.name.toLowerCase().trim()) + if (resolved) { + logger.debug('torrent-clients', `Synology hash résolu : "${t.name}" → ${resolved.slice(0, 8)}…`) + hash = resolved + } + } + + results.push({ ...t, hash, save_path, client_uuid: client.uuid, client_name: client.name }) } } catch (err) { const msg = err instanceof Error ? err.message : 'Erreur inconnue' logger.error('torrent-clients', `Impossible de récupérer la liste depuis "${client.name}" : ${msg}`) - // Déclencher le backoff si c'est une erreur d'auth const isAuthError = msg.toLowerCase().includes('login') || msg.toLowerCase().includes('auth') || msg.toLowerCase().includes('ban') ||