From 2aac95128f04a556139827cfc2860792ad094e20 Mon Sep 17 00:00:00 2001 From: Shinuwa Date: Mon, 7 Sep 2026 16:16:43 +0200 Subject: [PATCH] update to ignore 5h quota --- README.md | 48 +++++++++++++++++++---------------- index.js | 76 ++++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 82 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 802b397..e403e66 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # Codex Reset Alerts 🚀 -Petit watcher Node.js qui surveille ton quota **OpenAI Codex** et envoie des notifications **ntfy** au lancement, puis dès que ton quota est de nouveau disponible. +Petit watcher Node.js qui surveille ton quota global **OpenAI Codex** et envoie des notifications **ntfy** au lancement, puis dès que ce quota global est de nouveau disponible. -L'idée est simple : lorsque Codex indique que ton quota est épuisé, tu lances le watcher et tu peux passer à autre chose. Il surveille le quota en arrière-plan, t'envoie une notification lors du reset réel, puis s'arrête automatiquement. +L'idée est simple : lorsque Codex indique que ton quota global est épuisé, tu lances le watcher et tu peux passer à autre chose. Il surveille le quota global en arrière-plan, t'envoie une notification lors du reset réel, puis s'arrête automatiquement. ## Fonctionnement ```text -Quota Codex épuisé 🔴 +Quota global Codex épuisé 🔴 │ ▼ npm start @@ -20,10 +20,10 @@ Codex App Server └── account/rateLimits/updated │ ▼ -Surveillance du quota +Surveillance du quota global │ ▼ -Quota disponible 🟢 +Quota global disponible 🟢 │ ▼ ntfy @@ -40,9 +40,11 @@ Le watcher utilise deux mécanismes : * les événements `account/rateLimits/updated` envoyés par Codex ; * un polling périodique comme filet de sécurité. -Le timestamp `resetsAt` fourni par Codex est également surveillé, mais il n'est **pas considéré comme une garantie** : le quota peut être réinitialisé avant la date annoncée. +Codex expose deux fenêtres de quota : `primary` sur 300 minutes, soit 5h, et `secondary` sur 10080 minutes, soit 7 jours. Le watcher ignore la fenêtre 5h et utilise uniquement la fenêtre globale. -Une notification de démarrage est envoyée dès le lancement. La notification de reset n'est envoyée que lorsqu'un passage réel de **quota épuisé → quota disponible** est détecté. +Le timestamp `resetsAt` global fourni par Codex est également surveillé, mais il n'est **pas considéré comme une garantie** : le quota global peut être réinitialisé avant la date annoncée. + +Une notification de démarrage est envoyée dès le lancement. La notification de reset n'est envoyée que lorsqu'un passage réel de **quota global épuisé → quota global disponible** est détecté. ## Prérequis @@ -131,7 +133,7 @@ Les événements envoyés directement par Codex permettent généralement de dé ## Utilisation -Lorsque ton quota Codex est épuisé : +Lorsque ton quota global Codex est épuisé : ```bash npm start @@ -145,23 +147,24 @@ Si le quota est effectivement épuisé : ✅ Connecté. 🔎 Source : démarrage -📊 Utilisé : 100% -🔴 État : quota épuisé -🕒 Reset annoncé : jeudi 20 août 2026 à 13:58:03 -⏳ Temps théorique : 3j 1h 55min +📊 Global utilisé : 100% +📊 Fenêtre 300min ignorée : 100% +🔴 État : quota global épuisé +🕒 Reset global annoncé : jeudi 20 août 2026 à 13:58:03 +⏳ Temps théorique global : 3j 1h 55min -🔴 Quota épuisé. -👀 Surveillance du reset activée. +🔴 Quota global épuisé. +👀 Surveillance du reset global activée. 👀 Polling toutes les 60s. ``` Tu peux alors laisser le watcher tourner. -Lorsqu'un reset est détecté : +Lorsqu'un reset global est détecté : ```text -🎉 RESET RÉEL DÉTECTÉ ! +🎉 RESET GLOBAL RÉEL DÉTECTÉ ! 📉 100% → 0% 📱 Notification ntfy envoyée. @@ -172,7 +175,7 @@ Lorsqu'un reset est détecté : Tu recevras en parallèle une notification sur ton téléphone : ```text -🚀 Ton quota Codex est de nouveau disponible ! +🚀 Ton quota global Codex est de nouveau disponible ! Avant : 100% utilisé Maintenant : 0% utilisé @@ -180,7 +183,7 @@ Maintenant : 0% utilisé Le watcher s'arrête ensuite automatiquement. -## Si le quota n'est pas épuisé +## Si le quota global n'est pas épuisé Tu peux lancer le programme sans risque : @@ -188,13 +191,14 @@ Tu peux lancer le programme sans risque : npm start ``` -S'il reste du quota, le watcher ne reste pas inutilement actif : +S'il reste du quota global, le watcher ne reste pas inutilement actif : ```text -📊 Utilisé : 42% -🟢 État : quota disponible +📊 Global utilisé : 42% +📊 Fenêtre 300min ignorée : 100% +🟢 État : quota global disponible -✅ Ton quota Codex est encore disponible. +✅ Ton quota global Codex est encore disponible. 👋 Rien à surveiller, arrêt. ``` diff --git a/index.js b/index.js index 17b61be..eb29b06 100644 --- a/index.js +++ b/index.js @@ -75,17 +75,41 @@ function getCodexLimit(result) { ); } -function getState(limit) { - const usedPercent = Number(limit?.primary?.usedPercent ?? 0); +function getGlobalLimitWindow(limit) { + const windows = [ + limit?.primary, + limit?.secondary, + ].filter(Boolean); - const blocked = - limit?.rateLimitReachedType === "rate_limit_reached" || - usedPercent >= 100; + return ( + windows.find( + (window) => Number(window?.windowDurationMins) === 10080 + ) ?? + windows.sort( + (a, b) => + Number(b?.windowDurationMins ?? 0) - + Number(a?.windowDurationMins ?? 0) + )[0] ?? + null + ); +} + +function getState(limit) { + const globalWindow = getGlobalLimitWindow(limit); + const usedPercent = Number(globalWindow?.usedPercent ?? 0); + + const blocked = usedPercent >= 100; return { usedPercent, blocked, - resetsAt: limit?.primary?.resetsAt ?? null, + resetsAt: globalWindow?.resetsAt ?? null, + windowDurationMins: + globalWindow?.windowDurationMins ?? null, + primaryUsedPercent: + limit?.primary?.usedPercent ?? null, + primaryWindowDurationMins: + limit?.primary?.windowDurationMins ?? null, reachedType: limit?.rateLimitReachedType ?? null, }; } @@ -149,7 +173,7 @@ async function sendStartupNotification() { priority: "default", body: `👀 Codex Reset Alerts vient de démarrer.\n\n` + - `Surveillance du quota Codex en cours.`, + `Surveillance du quota global Codex en cours.`, }); } @@ -159,7 +183,7 @@ async function sendResetNotification(oldState, newState) { tags: "robot,white_check_mark", priority: "high", body: - `🚀 Ton quota Codex est de nouveau disponible !\n\n` + + `🚀 Ton quota global Codex est de nouveau disponible !\n\n` + `Avant : ${oldState.usedPercent}% utilisé\n` + `Maintenant : ${newState.usedPercent}% utilisé`, }); @@ -226,16 +250,28 @@ async function processLimit(limit, source) { console.log(""); console.log(`🔎 Source : ${source}`); - console.log(`📊 Utilisé : ${state.usedPercent}%`); + console.log(`📊 Global utilisé : ${state.usedPercent}%`); + + if ( + state.primaryUsedPercent !== null && + state.primaryWindowDurationMins !== null && + state.primaryWindowDurationMins !== state.windowDurationMins + ) { + console.log( + `📊 Fenêtre ${state.primaryWindowDurationMins}min ignorée : ` + + `${state.primaryUsedPercent}%` + ); + } + console.log( state.blocked - ? "🔴 État : quota épuisé" - : "🟢 État : quota disponible" + ? "🔴 État : quota global épuisé" + : "🟢 État : quota global disponible" ); if (state.resetsAt) { console.log( - `🕒 Reset annoncé : ${formatDate(state.resetsAt)}` + `🕒 Reset global annoncé : ${formatDate(state.resetsAt)}` ); const remaining = @@ -244,7 +280,7 @@ async function processLimit(limit, source) { if (remaining > 0) { console.log( - `⏳ Temps théorique : ${formatDuration(remaining)}` + `⏳ Temps théorique global : ${formatDuration(remaining)}` ); } } @@ -255,7 +291,7 @@ async function processLimit(limit, source) { if (!state.blocked) { console.log(""); - console.log("✅ Ton quota Codex est encore disponible."); + console.log("✅ Ton quota global Codex est encore disponible."); console.log("👋 Rien à surveiller, arrêt."); setTimeout(() => stop(0), 100); @@ -263,8 +299,8 @@ async function processLimit(limit, source) { } console.log(""); - console.log("🔴 Quota épuisé."); - console.log("👀 Surveillance du reset activée."); + console.log("🔴 Quota global épuisé."); + console.log("👀 Surveillance du reset global activée."); scheduleTheoreticalResetCheck(state); @@ -273,13 +309,13 @@ async function processLimit(limit, source) { // La transition importante : // - // bloqué -> disponible + // quota global épuisé -> quota global disponible if ( previousState.blocked === true && state.blocked === false ) { console.log(""); - console.log("🎉 RESET RÉEL DÉTECTÉ !"); + console.log("🎉 RESET GLOBAL RÉEL DÉTECTÉ !"); console.log( `📉 ${previousState.usedPercent}% → ${state.usedPercent}%` ); @@ -310,7 +346,7 @@ async function processLimit(limit, source) { if (state.usedPercent < previousState.usedPercent) { console.log( - `📉 Utilisation en baisse : ` + + `📉 Utilisation globale en baisse : ` + `${previousState.usedPercent}% → ${state.usedPercent}%` ); } @@ -320,7 +356,7 @@ async function processLimit(limit, source) { state.resetsAt && previousState.resetsAt !== state.resetsAt ) { - console.log("🔄 Date de reset modifiée par Codex."); + console.log("🔄 Date de reset global modifiée par Codex."); console.log( ` Avant : ${formatDate(previousState.resetsAt)}` );