root-me: better retry on 429
This commit is contained in:
parent
a68734afda
commit
46fcb3cd00
78
server.js
78
server.js
|
|
@ -225,10 +225,20 @@ let rootmeCache = null;
|
||||||
let rootmePrevScores = {}; // login → last known score
|
let rootmePrevScores = {}; // login → last known score
|
||||||
|
|
||||||
const ROOTME_REQUEST_DELAY_MS = 500;
|
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 rootmePlayerCache = {}; // id → { login, score, rank }
|
||||||
|
const retryQueue = new Map(); // id → { attempts, nextRetry }
|
||||||
|
|
||||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
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) {
|
async function fetchRootmeRanking(apiKey) {
|
||||||
const raw = fs.readFileSync(path.resolve('logins.txt'), 'utf8');
|
const raw = fs.readFileSync(path.resolve('logins.txt'), 'utf8');
|
||||||
const ids = raw.split('\n').map(l => l.trim()).filter(Boolean);
|
const ids = raw.split('\n').map(l => l.trim()).filter(Boolean);
|
||||||
|
|
@ -242,17 +252,12 @@ async function fetchRootmeRanking(apiKey) {
|
||||||
{ headers, timeout: 10000 }
|
{ headers, timeout: 10000 }
|
||||||
);
|
);
|
||||||
if (resp.status === 429) {
|
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]);
|
if (rootmePlayerCache[id]) results.push(rootmePlayerCache[id]);
|
||||||
|
retryQueue.set(id, { attempts: 1, nextRetry: Date.now() + ROOTME_RETRY_BASE_MS });
|
||||||
} else {
|
} else {
|
||||||
const profile = await resp.json();
|
const entry = parseRootmeUser(await resp.json(), id);
|
||||||
const profileRaw = Array.isArray(profile) ? profile[0] : profile;
|
if (entry) { rootmePlayerCache[id] = entry; results.push(entry); }
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[rootme] fetch error for id "${id}":`, err.message);
|
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);
|
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() {
|
async function pollRootme() {
|
||||||
const apiKey = process.env.ROOTME_API_KEY;
|
const apiKey = process.env.ROOTME_API_KEY;
|
||||||
if (!apiKey) return;
|
if (!apiKey) return;
|
||||||
|
|
@ -303,4 +362,5 @@ server.listen(PORT, () => {
|
||||||
console.log(`Cyber Dashboard running on http://localhost:${PORT}`);
|
console.log(`Cyber Dashboard running on http://localhost:${PORT}`);
|
||||||
pollRootme();
|
pollRootme();
|
||||||
setInterval(pollRootme, ROOTME_POLL_MS);
|
setInterval(pollRootme, ROOTME_POLL_MS);
|
||||||
|
setInterval(retryRateLimited, 30 * 1000);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue