update to ignore 5h quota
This commit is contained in:
parent
4680b3ac44
commit
2aac95128f
2 changed files with 82 additions and 42 deletions
48
README.md
48
README.md
|
|
@ -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.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
76
index.js
76
index.js
|
|
@ -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)}`
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue