feat: ajout d'une hashmap pour matcher les titres sur synologie et ajouter les hash de torrent manuellement

This commit is contained in:
MasutaYunikon committed 2026-04-10 17:37:49 +02:00
1 parent bb6d094383
commit 9b4dc4f782
2 files changed
+38 -8

No files matched your search

+10 -3
View File
@@ -170,6 +170,11 @@ async function readAvailable(): Promise<number[]> {
catch { return [] } catch { return [] }
} }
async function readInfohashMap(): Promise<Record<string, string>> {
try { return await githubGet('infohash_map.json') as Record<string, string> }
catch { return {} }
}
async function readSerieData(serieId: number): Promise<any | null> { async function readSerieData(serieId: number): Promise<any | null> {
try { return await githubGet(`series/${serieId}.json`) } try { return await githubGet(`series/${serieId}.json`) }
catch { return null } catch { return null }
@@ -277,7 +282,7 @@ app.get('/api/series', requireAuth, async (_req, res) => {
let organized: Record<string, Record<string, any>> = {} 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 {} 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<string>() const activeTorrents = new Set<string>()
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 seriesRaw = (Array.isArray(apiData) ? apiData : (apiData.series ?? [])).map(normalizeSerie)
const serieDataMap = new Map<number, any>() const serieDataMap = new Map<number, any>()
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) })) 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) => { app.get('/api/downloads', requireAuth, async (_req, res) => {
try { try {
const { category } = readSettings() const { category } = readSettings()
const torrents = await dispatchList(category ?? 'fankai') const infohashMap = await readInfohashMap()
const torrents = await dispatchList(category ?? 'fankai', infohashMap)
let organized: Record<string, Record<string, any>> = {} 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 {} 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 enriched = await Promise.all(torrents.map(async (t: any) => {
@@ -828,9 +834,10 @@ server.listen(PORT, async () => {
const autoOrganize = async () => { const autoOrganize = async () => {
try { try {
const { category } = readSettings() const { category } = readSettings()
const infohashMap = await readInfohashMap()
const seriesData = await loadEnrichedSeriesData() const seriesData = await loadEnrichedSeriesData()
await autoOrganizeAll( await autoOrganizeAll(
() => dispatchList(category ?? 'fankai'), () => dispatchList(category ?? 'fankai', infohashMap),
seriesData, seriesData,
(result) => { (result) => {
if (result.done > 0 || result.errors > 0) { if (result.done > 0 || result.errors > 0) {
+28 -5
View File
@@ -237,15 +237,27 @@ export async function dispatchDownload(url: string): Promise<{ uuid: string; nam
// ─── Dispatch list ───────────────────────────────────────────────────────────── // ─── Dispatch list ─────────────────────────────────────────────────────────────
export async function dispatchList(category?: string): Promise<(TorrentInfo & { client_uuid: string; client_name: string })[]> { // Map inversée : titre normalisé → infohash (pour matcher les torrents Synology)
const clients = loadClients() function buildTitleIndex(infohashMap: Record<string, string>): Map<string, string> {
const index = new Map<string, string>()
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<string, string>,
): Promise<(TorrentInfo & { client_uuid: string; client_name: string })[]> {
const clients = loadClients()
const results: (TorrentInfo & { client_uuid: string; client_name: string })[] = [] const results: (TorrentInfo & { client_uuid: string; client_name: string })[] = []
const titleIndex = infohashMap ? buildTitleIndex(infohashMap) : null
for (const client of clients) { for (const client of clients) {
const driver = getDriver(client.type) const driver = getDriver(client.type)
if (!driver) continue if (!driver) continue
// Skip si le client est en cooldown suite à des échecs d'auth
if (isInCooldown(client.uuid)) { if (isInCooldown(client.uuid)) {
const remaining = cooldownRemaining(client.uuid) const remaining = cooldownRemaining(client.uuid)
logger.debug('torrent-clients', `Client "${client.name}" en cooldown — retry dans ${remaining}`) 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 const save_path = hasRemap
? remapSavePath(t.save_path, remotePath, localPath) ? remapSavePath(t.save_path, remotePath, localPath)
: t.save_path : 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) { } catch (err) {
const msg = err instanceof Error ? err.message : 'Erreur inconnue' const msg = err instanceof Error ? err.message : 'Erreur inconnue'
logger.error('torrent-clients', `Impossible de récupérer la liste depuis "${client.name}" : ${msg}`) 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') || const isAuthError = msg.toLowerCase().includes('login') ||
msg.toLowerCase().includes('auth') || msg.toLowerCase().includes('auth') ||
msg.toLowerCase().includes('ban') || msg.toLowerCase().includes('ban') ||