update to ignore 5h quota

This commit is contained in:
Shinuwa 2026-09-07 16:16:43 +02:00
parent 4680b3ac44
commit 2aac95128f
2 changed files with 82 additions and 42 deletions

View file

@ -1,13 +1,13 @@
# Codex Reset Alerts 🚀 # 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 ## Fonctionnement
```text ```text
Quota Codex épuisé 🔴 Quota global Codex épuisé 🔴
│ │
▼ ▼
npm start npm start
@ -20,10 +20,10 @@ Codex App Server
└── account/rateLimits/updated └── account/rateLimits/updated
│ │
▼ ▼
Surveillance du quota Surveillance du quota global
│ │
▼ ▼
Quota disponible 🟢 Quota global disponible 🟢
│ │
▼ ▼
ntfy ntfy
@ -40,9 +40,11 @@ Le watcher utilise deux mécanismes :
* les événements `account/rateLimits/updated` envoyés par Codex ; * les événements `account/rateLimits/updated` envoyés par Codex ;
* un polling périodique comme filet de sécurité. * 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 ## Prérequis
@ -131,7 +133,7 @@ Les événements envoyés directement par Codex permettent généralement de dé
## Utilisation ## Utilisation
Lorsque ton quota Codex est épuisé : Lorsque ton quota global Codex est épuisé :
```bash ```bash
npm start npm start
@ -145,23 +147,24 @@ Si le quota est effectivement épuisé :
✅ Connecté. ✅ Connecté.
🔎 Source : démarrage 🔎 Source : démarrage
📊 Utilisé : 100% 📊 Global utilisé : 100%
🔴 État : quota épuisé 📊 Fenêtre 300min ignorée : 100%
🕒 Reset annoncé : jeudi 20 août 2026 à 13:58:03 🔴 État : quota global épuisé
⏳ Temps théorique : 3j 1h 55min 🕒 Reset global annoncé : jeudi 20 août 2026 à 13:58:03
⏳ Temps théorique global : 3j 1h 55min
🔴 Quota épuisé. 🔴 Quota global épuisé.
👀 Surveillance du reset activée. 👀 Surveillance du reset global activée.
👀 Polling toutes les 60s. 👀 Polling toutes les 60s.
``` ```
Tu peux alors laisser le watcher tourner. Tu peux alors laisser le watcher tourner.
Lorsqu'un reset est détecté : Lorsqu'un reset global est détecté :
```text ```text
🎉 RESET RÉEL DÉTECTÉ ! 🎉 RESET GLOBAL RÉEL DÉTECTÉ !
📉 100% → 0% 📉 100% → 0%
📱 Notification ntfy envoyée. 📱 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 : Tu recevras en parallèle une notification sur ton téléphone :
```text ```text
🚀 Ton quota Codex est de nouveau disponible ! 🚀 Ton quota global Codex est de nouveau disponible !
Avant : 100% utilisé Avant : 100% utilisé
Maintenant : 0% utilisé Maintenant : 0% utilisé
@ -180,7 +183,7 @@ Maintenant : 0% utilisé
Le watcher s'arrête ensuite automatiquement. 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 : Tu peux lancer le programme sans risque :
@ -188,13 +191,14 @@ Tu peux lancer le programme sans risque :
npm start 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 ```text
📊 Utilisé : 42% 📊 Global utilisé : 42%
🟢 État : quota disponible 📊 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. 👋 Rien à surveiller, arrêt.
``` ```

View file

@ -75,17 +75,41 @@ function getCodexLimit(result) {
); );
} }
function getState(limit) { function getGlobalLimitWindow(limit) {
const usedPercent = Number(limit?.primary?.usedPercent ?? 0); const windows = [
limit?.primary,
limit?.secondary,
].filter(Boolean);
const blocked = return (
limit?.rateLimitReachedType === "rate_limit_reached" || windows.find(
usedPercent >= 100; (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 { return {
usedPercent, usedPercent,
blocked, 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, reachedType: limit?.rateLimitReachedType ?? null,
}; };
} }
@ -149,7 +173,7 @@ async function sendStartupNotification() {
priority: "default", priority: "default",
body: body:
`👀 Codex Reset Alerts vient de démarrer.\n\n` + `👀 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", tags: "robot,white_check_mark",
priority: "high", priority: "high",
body: 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` + `Avant : ${oldState.usedPercent}% utilisé\n` +
`Maintenant : ${newState.usedPercent}% utilisé`, `Maintenant : ${newState.usedPercent}% utilisé`,
}); });
@ -226,16 +250,28 @@ async function processLimit(limit, source) {
console.log(""); console.log("");
console.log(`🔎 Source : ${source}`); 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( console.log(
state.blocked state.blocked
? "🔴 État : quota épuisé" ? "🔴 État : quota global épuisé"
: "🟢 État : quota disponible" : "🟢 État : quota global disponible"
); );
if (state.resetsAt) { if (state.resetsAt) {
console.log( console.log(
`🕒 Reset annoncé : ${formatDate(state.resetsAt)}` `🕒 Reset global annoncé : ${formatDate(state.resetsAt)}`
); );
const remaining = const remaining =
@ -244,7 +280,7 @@ async function processLimit(limit, source) {
if (remaining > 0) { if (remaining > 0) {
console.log( 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) { if (!state.blocked) {
console.log(""); 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."); console.log("👋 Rien à surveiller, arrêt.");
setTimeout(() => stop(0), 100); setTimeout(() => stop(0), 100);
@ -263,8 +299,8 @@ async function processLimit(limit, source) {
} }
console.log(""); console.log("");
console.log("🔴 Quota épuisé."); console.log("🔴 Quota global épuisé.");
console.log("👀 Surveillance du reset activée."); console.log("👀 Surveillance du reset global activée.");
scheduleTheoreticalResetCheck(state); scheduleTheoreticalResetCheck(state);
@ -273,13 +309,13 @@ async function processLimit(limit, source) {
// La transition importante : // La transition importante :
// //
// bloqué -> disponible // quota global épuisé -> quota global disponible
if ( if (
previousState.blocked === true && previousState.blocked === true &&
state.blocked === false state.blocked === false
) { ) {
console.log(""); console.log("");
console.log("🎉 RESET RÉEL DÉTECTÉ !"); console.log("🎉 RESET GLOBAL RÉEL DÉTECTÉ !");
console.log( console.log(
`📉 ${previousState.usedPercent}% → ${state.usedPercent}%` `📉 ${previousState.usedPercent}% → ${state.usedPercent}%`
); );
@ -310,7 +346,7 @@ async function processLimit(limit, source) {
if (state.usedPercent < previousState.usedPercent) { if (state.usedPercent < previousState.usedPercent) {
console.log( console.log(
`📉 Utilisation en baisse : ` + `📉 Utilisation globale en baisse : ` +
`${previousState.usedPercent}% → ${state.usedPercent}%` `${previousState.usedPercent}% → ${state.usedPercent}%`
); );
} }
@ -320,7 +356,7 @@ async function processLimit(limit, source) {
state.resetsAt && state.resetsAt &&
previousState.resetsAt !== 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( console.log(
` Avant : ${formatDate(previousState.resetsAt)}` ` Avant : ${formatDate(previousState.resetsAt)}`
); );