diff --git a/server.js b/server.js index ae680bd..a4dfd8b 100644 --- a/server.js +++ b/server.js @@ -225,10 +225,20 @@ let rootmeCache = null; let rootmePrevScores = {}; // login → last known score const ROOTME_REQUEST_DELAY_MS = 500; +const ROOTME_RETRY_BASE_MS = 2 * 60 * 1000; // 2 min, doublé à chaque échec +const ROOTME_RETRY_MAX = 3; const rootmePlayerCache = {}; // id → { login, score, rank } +const retryQueue = new Map(); // id → { attempts, nextRetry } const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); +function parseRootmeUser(profile, id) { + const profileRaw = Array.isArray(profile) ? profile[0] : profile; + const user = profileRaw?.['0'] ?? profileRaw; + if (!user || user.error) return null; + return { login: user.nom || id, score: Number(user.score) || 0, rank: user.position || null }; +} + async function fetchRootmeRanking(apiKey) { const raw = fs.readFileSync(path.resolve('logins.txt'), 'utf8'); const ids = raw.split('\n').map(l => l.trim()).filter(Boolean); @@ -242,17 +252,12 @@ async function fetchRootmeRanking(apiKey) { { headers, timeout: 10000 } ); if (resp.status === 429) { - console.warn(`[rootme] rate-limited on id "${id}", using cached value`); + console.warn(`[rootme] rate-limited on id "${id}", scheduling retry`); if (rootmePlayerCache[id]) results.push(rootmePlayerCache[id]); + retryQueue.set(id, { attempts: 1, nextRetry: Date.now() + ROOTME_RETRY_BASE_MS }); } else { - const profile = await resp.json(); - const profileRaw = Array.isArray(profile) ? profile[0] : profile; - const user = profileRaw?.['0'] ?? profileRaw; - if (user && !user.error) { - const entry = { login: user.nom || id, score: Number(user.score) || 0, rank: user.position || null }; - rootmePlayerCache[id] = entry; - results.push(entry); - } + const entry = parseRootmeUser(await resp.json(), id); + if (entry) { rootmePlayerCache[id] = entry; results.push(entry); } } } catch (err) { console.error(`[rootme] fetch error for id "${id}":`, err.message); @@ -264,6 +269,60 @@ async function fetchRootmeRanking(apiKey) { return results.sort((a, b) => b.score - a.score); } +async function retryRateLimited() { + const apiKey = process.env.ROOTME_API_KEY; + if (!apiKey || retryQueue.size === 0) return; + + const now = Date.now(); + const headers = { 'Cookie': `api_key=${apiKey}`, 'User-Agent': 'CyberDashboard/1.0' }; + + for (const [id, state] of retryQueue) { + if (now < state.nextRetry) continue; + + try { + const resp = await fetch( + `https://api.www.root-me.org/auteurs/${id}`, + { headers, timeout: 10000 } + ); + + if (resp.status === 429) { + if (state.attempts >= ROOTME_RETRY_MAX) { + console.warn(`[rootme] retry exhausted for id "${id}", giving up until next poll`); + retryQueue.delete(id); + } else { + state.attempts++; + state.nextRetry = Date.now() + ROOTME_RETRY_BASE_MS * Math.pow(2, state.attempts - 1); + console.warn(`[rootme] retry 429 for id "${id}" (attempt ${state.attempts}/${ROOTME_RETRY_MAX}), next in ${Math.round((state.nextRetry - Date.now()) / 60000)} min`); + } + } else { + const entry = parseRootmeUser(await resp.json(), id); + if (entry) { + const prev = rootmePrevScores[entry.login]; + if (prev !== undefined && entry.score > prev) { + const gained = entry.score - prev; + console.log(`[rootme] FLAG (retry) ! ${entry.login} +${gained} pts`); + broadcast({ type: 'rootme_flag', login: entry.login, gained, newScore: entry.score }); + } + rootmePlayerCache[id] = entry; + rootmePrevScores[entry.login] = entry.score; + if (rootmeCache) { + const idx = rootmeCache.findIndex(u => u.login === entry.login); + if (idx !== -1) rootmeCache[idx] = entry; else rootmeCache.push(entry); + rootmeCache.sort((a, b) => b.score - a.score); + broadcast({ type: 'rootme_update', ranking: rootmeCache }); + } + console.log(`[rootme] retry OK for id "${id}" (${entry.login})`); + } + retryQueue.delete(id); + } + } catch (err) { + console.error(`[rootme] retry error for id "${id}":`, err.message); + retryQueue.delete(id); + } + await sleep(ROOTME_REQUEST_DELAY_MS); + } +} + async function pollRootme() { const apiKey = process.env.ROOTME_API_KEY; if (!apiKey) return; @@ -303,4 +362,5 @@ server.listen(PORT, () => { console.log(`Cyber Dashboard running on http://localhost:${PORT}`); pollRootme(); setInterval(pollRootme, ROOTME_POLL_MS); + setInterval(retryRateLimited, 30 * 1000); });