Codex额度分析油猴脚本优化版本,可根据最近7天的用量强制计算,来看看你的额度有没有少

Licoy 2026-08-21 10:21 1

最近看到很多帖子都说codex的5x、20x等等的额度降了,我实际体感也是一样用的飞快,不过也可能是我一直都是Ultra+Fast的模式来走的,所以用的比较快,用论坛里面一个其他的脚本改了一下,然后找几个朋友测试了一下:他是从5x升级到20x的,但是已经用了一个多周期了,额度都重置了,但是用原来的脚本显示周估算额度不到4000$,但是强制7天计算就有13000$



脚本如下:


// ==UserScript==
// @name Codex Quota Compass (Visual Edition)
// @namespace http://tampermonkey.net/
// @version 1.10
// @description 人性化展示 Codex 配额,支持强制周窗口(最近 7 天)计算,强化 Credits 标注及历史合计(Token 以 M 为单位)
// @author Jun Zhao
// @match https://chatgpt.com/codex/cloud/settings/analytics*
// @grant GM_addStyle
// ==/UserScript==

(function () {
"use strict";

const CONFIG = {
USD_PER_CREDIT: 40 / 1000,
WEEK_SECONDS: 7 * 24 * 3600,
MODE_KEY: "codex-compass-window-mode",
};

const fmtNum = (n) => {
if (n >= 1e6) return (n / 1e6).toFixed(2) + "M";
if (n >= 1e3) return (n / 1e3).toFixed(2) + "K";
return n.toLocaleString();
};

const num = (v) => (Number.isFinite(Number(v)) ? Number(v) : 0);
const tokenTotal = (obj = {}) =>
num(obj.text_total_tokens) ||
num(obj.cached_text_input_tokens) +
num(obj.uncached_text_input_tokens) +
num(obj.text_output_tokens);

const ymdUTC = (tsSec) =>
new Date(tsSec * 1000).toISOString().split("T")[0];
const ymdDaysAgo = (days) =>
new Date(Date.now() - days * 86400000).toISOString().split("T")[0];
const addDaysYmd = (ymd, delta) => {
const d = new Date(`${ymd}T00:00:00Z`);
if (Number.isNaN(d.getTime())) return ymdDaysAgo(-delta);
d.setUTCDate(d.getUTCDate() + delta);
return d.toISOString().split("T")[0];
};
const rollingWeekStart = (dailyList) => {
const dates = (dailyList || [])
.map((d) => String(d.date || ""))
.filter(Boolean)
.sort();
const latest = dates[dates.length - 1];
return latest ? addDaysYmd(latest, -6) : ymdDaysAgo(6);
};

const getMode = () => {
const v = localStorage.getItem(CONFIG.MODE_KEY);
return v === "weekly" ? "weekly" : "auto";
};
const setMode = (mode) => localStorage.setItem(CONFIG.MODE_KEY, mode);

const windowSecs = (w) =>
num(w?.limit_window_seconds) ||
num(w?.window_seconds) ||
num(w?.window_minutes) * 60;

const windowKind = (w, hint) => {
if (!w) return null;
const secs = windowSecs(w);
if (secs >= 6 * 86400) return "weekly";
if (secs > 0 && secs <= 12 * 3600) return "5h";
if (hint === "secondary") return "weekly";
if (hint === "primary") return "5h";
return "unknown";
};

const pickWindows = (usage) => {
const rl = usage?.rate_limit || {};
const primary = rl.primary_window || rl.primary || null;
const secondary = rl.secondary_window || rl.secondary || null;
const weekly =
(windowKind(secondary, "secondary") === "weekly" ? secondary : null) ||
(windowKind(primary) === "weekly" ? primary : null);
const fiveHour =
(windowKind(primary, "primary") === "5h" ? primary : null) ||
(windowKind(secondary) === "5h" ? secondary : null);
return { primary, secondary, weekly, fiveHour };
};

const resolveView = (mode, windows, dailyList) => {
if (mode === "weekly") {
const meter = windows.weekly || windows.secondary || null;
const kind = windowKind(meter, meter === windows.secondary ? "secondary" : undefined);
const isFiveHour = kind === "5h";
return {
meter: isFiveHour ? null : meter,
cycleStartDate: rollingWeekStart(dailyList),
kind: "weekly-forced",
canExtrapolate: !isFiveHour && !!meter && num(meter?.used_percent) > 0,
title: "周窗口 · 最近7天",
};
}

const meter =
windows.weekly ||
windows.secondary ||
windows.fiveHour ||
windows.primary;
const kind =
windowKind(
meter,
meter === windows.secondary
? "secondary"
: meter === windows.primary
? "primary"
: undefined,
) || "auto";
const secs = windowSecs(meter) || CONFIG.WEEK_SECONDS;
const resetAt = num(meter?.reset_at);
const cycleStartDate = resetAt
? ymdUTC(resetAt - secs)
: ymdDaysAgo(6);
return {
meter,
cycleStartDate,
kind,
canExtrapolate: !!meter && num(meter?.used_percent) > 0,
title: kind === "weekly" ? "周窗口" : kind === "5h" ? "5小时窗口" : "自动窗口",
};
};

GM_addStyle(`
#codex-compass-root {
position: fixed; top: 5%; left: 50%; transform: translateX(-50%);
width: 720px; max-height: 90vh; background: #fff;
border-radius: 12px; box-shadow: 0 10px 50px rgba(0,0,0,0.3);
z-index: 10001; padding: 24px; display: none;
flex-direction: column; border: 1px solid #e5e5e5; color: #333;
font-family: -apple-system, system-ui, sans-serif;
}
.compass-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; gap: 12px; }
.compass-header-left { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.compass-header-right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.compass-title { font-size: 18px; font-weight: 600; }
.compass-meta { font-size: 12px; color: #888; font-weight: 400; }
.compass-close { cursor: pointer; font-size: 28px; color: #999; line-height: 1; }
.compass-toggle { display: inline-flex; border: 1px solid #d1f2e1; border-radius: 8px; overflow: hidden; }
.compass-toggle button {
border: none; background: #fff; padding: 6px 12px; cursor: pointer;
color: #666; font-size: 12px; font-weight: 600; line-height: 1.2;
}
.compass-toggle button.active { background: #10a37f; color: #fff; }
.compass-notice {
font-size: 12px; color: #8a6d3b; background: #fff8e6;
border: 1px solid #f0e0b2; border-radius: 8px; padding: 8px 10px;
margin-bottom: 12px; line-height: 1.45;
}
.compass-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 20px; }
.compass-card { background: #f9f9f9; padding: 12px; border-radius: 8px; border: 1px solid #eee; }
.compass-card.highlight { background: #eefaf5; border-color: #d1f2e1; }
.compass-card.muted .card-value { color: #999; }
.card-label { font-size: 12px; color: #666; margin-bottom: 4px; }
.card-value { font-size: 15px; font-weight: bold; color: #10a37f; white-space: nowrap; }
.card-sub { font-size: 11px; color: #999; margin-top: 4px; font-weight: 400; }

.table-container {
max-height: 220px; overflow-y: auto; border: 1px solid #eee;
border-radius: 6px; margin-bottom: 15px;
}
.compass-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.compass-table thead { position: sticky; top: 0; background: #f5f5f5; z-index: 1; }
.compass-table th { text-align: left; padding: 8px; border-bottom: 1px solid #eee; color: #666; }
.compass-table td { padding: 8px; border-bottom: 1px solid #f0f0f0; }
.compass-footer-row { background: #fafafa; font-weight: bold; position: sticky; bottom: 0; border-top: 2px solid #eee; }

#codex-compass-btn {
position: fixed; bottom: 20px; right: 20px; z-index: 10000;
padding: 10px 20px; background: #10a37f; color: white;
border: none; border-radius: 8px; cursor: pointer; font-weight: 600;
}
`);

async function apiGet(path, token) {
const res = await fetch(path, {
headers: { Authorization: `Bearer ${token}` },
});
return res.json();
}

let lastSnapshot = null;

const showPanel = (snapshot) => {
lastSnapshot = snapshot;
const root = document.getElementById("codex-compass-root");
const mode = getMode();
const { windows, dailyList } = snapshot;
const view = resolveView(mode, windows, dailyList);
const meter = view.meter;

const currentCycleList = [];
const historyList = [];
dailyList.forEach((item) => {
if (String(item.date) >= view.cycleStartDate) {
currentCycleList.push(item);
} else {
historyList.push(item);
}
});

const getStats = (list) => {
const credits = list.reduce(
(sum, d) => sum + (d.totals?.credits || 0),
0,
);
const turns = list.reduce((sum, d) => sum + (d.totals?.turns || 0), 0);
const tokens = list.reduce((sum, d) => sum + tokenTotal(d.totals), 0);
return { credits, turns, tokens, usd: credits * CONFIG.USD_PER_CREDIT };
};

const currentStats = getStats(currentCycleList);
const historyStats = getStats(historyList);

const usedPercent = num(meter?.used_percent);
const ratio = usedPercent / 100;
const canEst = view.canExtrapolate && ratio > 0;
const estCredits = canEst ? currentStats.credits / ratio : 0;
const weekValue = estCredits * CONFIG.USD_PER_CREDIT;

const weeklyPct = windows.weekly
? `${num(windows.weekly.used_percent)}%`
: "无";
const fivePct = windows.fiveHour
? `${num(windows.fiveHour.used_percent)}%`
: "无";

let historyRangeTitle = "⏳ 历史记录 (本周期外)";
if (historyList.length > 0) {
const sortedHistory = [...historyList].sort(
(a, b) => String(a.date).localeCompare(String(b.date)),
);
const startStr = sortedHistory[0].date;
const endStr = sortedHistory[sortedHistory.length - 1].date;
historyRangeTitle = `⏳ 历史记录 (本周期外 ${startStr} 至 ${endStr})`;
}

const isWeeklyView = mode === "weekly" || view.kind === "weekly";
const usedLabel = isWeeklyView ? "本周已用" : "本窗口已用";
const estLabel = isWeeklyView ? "推算周额度" : "推算总额";
const valueLabel = isWeeklyView ? "周价值 (估算)" : "窗口价值 (估算)";

let notice = "";
if (mode === "auto" && view.kind === "5h") {
notice = `当前自动选中的是 <b>5 小时窗口</b>,明细可能只有 1 天。点右上角「周窗口」按最近 7 天重算。`;
} else if (mode === "weekly") {
notice = `已强制按 <b>最近 7 个自然日</b> 汇总 Credits(${view.cycleStartDate} 起,${currentCycleList.length} 天)。占用率使用周窗口百分比。若 API 周重置晚于这 7 天起点,总额可能含上一周用量。`;
if (!canEst) {
notice += ` 当前没有可用的周占用率,推算周额度 / 周价值不外推。`;
}
}

const renderTable = (list, stats) => `
<div class="table-container">
<table class="compass-table">
<thead>
<tr><th>日期</th><th>Credits</th><th>Tokens</th><th>金额</th><th>轮数</th></tr>
</thead>
<tbody>
${[...list]
.reverse()
.map(
(row) => `
<tr>
<td>${row.date}</td>
<td style="font-family:monospace">${(row.totals?.credits || 0).toFixed(3)}</td>
<td style="font-family:monospace">${fmtNum(tokenTotal(row.totals))}</td>
<td>$ ${((row.totals?.credits || 0) * CONFIG.USD_PER_CREDIT).toFixed(2)}</td>
<td>${row.totals?.turns || 0}</td>
</tr>
`,
)
.join("")}
</tbody>
<tfoot>
<tr class="compass-footer-row">
<td>合计</td>
<td>${stats.credits.toFixed(3)}</td>
<td style="font-family:monospace">${fmtNum(stats.tokens)}</td>
<td style="color:#10a37f">$ ${stats.usd.toFixed(2)}</td>
<td>${stats.turns}</td>
</tr>
</tfoot>
</table>
</div>
`;

root.innerHTML = `
<div class="compass-header">
<div class="compass-header-left">
<div class="compass-title">📊 Codex 配额深度分析 (V1.10)</div>
<div class="compass-meta">计算窗口:${view.title} · 5小时 ${fivePct} · 周 ${weeklyPct}</div>
</div>
<div class="compass-header-right">
<div class="compass-toggle" id="compass-window-toggle">
<button type="button" data-mode="auto" class="${mode === "auto" ? "active" : ""}">自动</button>
<button type="button" data-mode="weekly" class="${mode === "weekly" ? "active" : ""}">周窗口</button>
</div>
<div class="compass-close" id="compass-close-btn">&times;</div>
</div>
</div>
${notice ? `<div class="compass-notice">${notice}</div>` : ""}
<div class="compass-grid">
<div class="compass-card">
<div class="card-label">已用比例</div>
<div class="card-value">${usedPercent || 0}%</div>
<div class="card-sub">${view.title}</div>
</div>
<div class="compass-card">
<div class="card-label">${usedLabel}</div>
<div class="card-value">${currentStats.credits.toFixed(1)} <span style="font-size:10px; font-weight:normal">Credits</span></div>
<div class="card-sub">${currentCycleList.length} 天</div>
</div>
<div class="compass-card highlight ${canEst ? "" : "muted"}">
<div class="card-label">${estLabel}</div>
<div class="card-value">${canEst ? estCredits.toFixed(1) : "—"} <span style="font-size:10px; font-weight:normal">${canEst ? "Credits" : ""}</span></div>
</div>
<div class="compass-card ${canEst ? "" : "muted"}">
<div class="card-label">${valueLabel}</div>
<div class="card-value">${canEst ? "$ " + weekValue.toFixed(2) : "—"}</div>
</div>
</div>

<div style="font-weight:600; margin-bottom:8px; font-size:13px;">📅 ${isWeeklyView ? "本周明细" : "本周期明细"} (始于 ${view.cycleStartDate})</div>
${renderTable(currentCycleList, currentStats)}

${
historyList.length > 0
? `
<div style="font-weight:600; margin-bottom:8px; font-size:13px; color: #666; margin-top: 5px;">${historyRangeTitle}</div>
${renderTable(historyList, historyStats)}
`
: ""
}
`;

root.style.display = "flex";

if (!root.dataset.bound) {
root.dataset.bound = "1";
root.addEventListener("click", (e) => {
if (e.target.closest("#compass-close-btn")) {
root.style.display = "none";
return;
}
const modeBtn = e.target.closest("#compass-window-toggle [data-mode]");
if (!modeBtn) return;
e.preventDefault();
e.stopPropagation();
const next = modeBtn.getAttribute("data-mode");
if (next !== "auto" && next !== "weekly") return;
if (next === getMode()) return;
setMode(next);
if (lastSnapshot) showPanel(lastSnapshot);
});
}
};

const run = async () => {
const btn = document.getElementById("codex-compass-btn");
const bootstrapData =
document.getElementById("client-bootstrap")?.textContent;
const token = bootstrapData?.match(
/[\w-]{30,}\.[\w-]{30,}\.[\w-]{30,}/,
)?.[0];
if (!token) return alert("令牌获取失败,请确保已登录。");

btn.innerText = "分析中...";
try {
const usage = await apiGet("/backend-api/wham/usage", token);
const windows = pickWindows(usage);

const endDate = new Date(Date.now() + 86400000)
.toISOString()
.split("T")[0];
const startDate = new Date(Date.now() - 30 * 86400000)
.toISOString()
.split("T")[0];

const dailyData = await apiGet(
`/backend-api/wham/analytics/daily-workspace-usage-counts?start_date=${startDate}&end_date=${endDate}&group_by=day`,
token,
);

showPanel({
windows,
dailyList: dailyData.data || [],
});
} catch (e) {
alert("错误: " + e.message);
} finally {
btn.innerText = "📊 运行用量分析";
}
};

const btn = document.createElement("button");
btn.id = "codex-compass-btn";
btn.innerText = "📊 运行用量分析";
btn.onclick = run;
document.body.appendChild(btn);

const root = document.createElement("div");
root.id = "codex-compass-root";
document.body.appendChild(root);
})();

最新回复 (3)
  • zaoan 08-21 10:43
    1

    https://chatgpt.com/codex/cloud/settings/analytics



    大佬,之前的脚本可以发下吗?想参考一下呢

  • Licoy 楼主 08-21 11:12
    2

    之前的脚本和这个一样,只不过就是没有右上角的 自动、周窗口 的切换

  • Hiskens 08-22 18:22
    3

    佬 为什么我用下来credits都是0,额度没计算

* 帖子来源Linux.do
返回