Last active
May 31, 2026 14:43
-
-
Save BlueSkyXN/528e810b98affcecca170e6b9d53d7da to your computer and use it in GitHub Desktop.
codex-quota-compass.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (async () => { | |
| 'use strict'; | |
| // ============================================================ | |
| // codex-quota-compass.js(发布版) | |
| // ============================================================ | |
| // 用法:在 https://chatgpt.com/codex/cloud/settings/analytics#usage | |
| // 或任意 chatgpt.com 页面打开 DevTools Console,粘贴运行。 | |
| // | |
| // 安全: | |
| // - 不打印 accessToken / cookie。 | |
| // - 不返回 /usage 原始响应,避免泄露 email / user_id。 | |
| // - MANUAL_ACCESS_TOKEN 默认留空;只有自动取不到 token 时,才在自己电脑临时填写。 | |
| // | |
| // 说明: | |
| // - /backend-api/wham/usage 的 reset_at 是 epoch seconds,不受时区影响。 | |
| // - daily-workspace-usage-counts 只接受 YYYY-MM-DD,且 group_by 只支持 day/week/month。 | |
| // - 本脚本默认把用量日期桶按 UTC 解释;同时输出 UTC / 本地时区诊断。 | |
| const CONFIG = { | |
| DATE_BUCKET_MODE: 'utc', // 推荐:'utc';可改为 'local' 对比 | |
| USD_PER_CREDIT: 40 / 1000, // 1000 credits = 40 USD | |
| ROLLING_DAYS: 30, | |
| // 不建议使用。只有自动取不到 accessToken 时,才在自己电脑临时填写 Bearer 后面的内容。 | |
| // 发布脚本、截图、复制输出前,必须保持为空。 | |
| MANUAL_ACCESS_TOKEN: '', | |
| USAGE_PATH: '/backend-api/wham/usage', | |
| DAILY_USAGE_PATH: | |
| '/backend-api/wham/analytics/daily-workspace-usage-counts', | |
| }; | |
| if (location.hostname !== 'chatgpt.com') { | |
| throw new Error('请在 chatgpt.com 页面运行,例如 Codex Usage / Analytics 页面。'); | |
| } | |
| // ============================================================ | |
| // 基础工具 | |
| // ============================================================ | |
| const DAY_MS = 24 * 60 * 60 * 1000; | |
| const n = (v) => (Number.isFinite(Number(v)) ? Number(v) : 0); | |
| const round = (v, d = 2) => Number(Number(v).toFixed(d)); | |
| const pad2 = (x) => String(x).padStart(2, '0'); | |
| const last = (arr) => (arr.length ? arr[arr.length - 1] : undefined); | |
| const fmtLocal = (ms) => new Date(ms).toLocaleString(); | |
| const fmtUTC = (ms) => | |
| new Date(ms).toISOString().replace('T', ' ').replace('.000Z', ' UTC'); | |
| const ymdUTC = (x) => { | |
| const d = new Date(x); | |
| return [ | |
| d.getUTCFullYear(), | |
| pad2(d.getUTCMonth() + 1), | |
| pad2(d.getUTCDate()), | |
| ].join('-'); | |
| }; | |
| const ymdLocal = (x) => { | |
| const d = new Date(x); | |
| d.setMinutes(d.getMinutes() - d.getTimezoneOffset()); | |
| return d.toISOString().slice(0, 10); | |
| }; | |
| const ymdForApi = (ms) => | |
| CONFIG.DATE_BUCKET_MODE === 'utc' ? ymdUTC(ms) : ymdLocal(ms); | |
| const addDaysLocalMs = (ms, days) => { | |
| const d = new Date(ms); | |
| d.setDate(d.getDate() + days); | |
| return d.getTime(); | |
| }; | |
| const addDaysForApi = (ms, days) => | |
| CONFIG.DATE_BUCKET_MODE === 'utc' | |
| ? ms + days * DAY_MS | |
| : addDaysLocalMs(ms, days); | |
| const firstDayOfMonthUTC = (ms) => { | |
| const d = new Date(ms); | |
| return `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}-01`; | |
| }; | |
| const firstDayOfMonthLocal = (ms) => { | |
| const d = new Date(ms); | |
| return ymdLocal(new Date(d.getFullYear(), d.getMonth(), 1).getTime()); | |
| }; | |
| const firstDayOfMonthForApi = (ms) => | |
| CONFIG.DATE_BUCKET_MODE === 'utc' | |
| ? firstDayOfMonthUTC(ms) | |
| : firstDayOfMonthLocal(ms); | |
| const utcOffsetLabel = (ms) => { | |
| const offsetMinutes = -new Date(ms).getTimezoneOffset(); | |
| const sign = offsetMinutes >= 0 ? '+' : '-'; | |
| const abs = Math.abs(offsetMinutes); | |
| return `UTC${sign}${pad2(Math.floor(abs / 60))}:${pad2(abs % 60)}`; | |
| }; | |
| const tokenTotal = (obj = {}) => | |
| n(obj.text_total_tokens) || | |
| n(obj.cached_text_input_tokens) + | |
| n(obj.uncached_text_input_tokens) + | |
| n(obj.text_output_tokens); | |
| const stripBearer = (s) => | |
| String(s || '') | |
| .replace(/^Bearer\s+/i, '') | |
| .trim(); | |
| const looksLikeJwt = (s) => | |
| typeof s === 'string' && s.length > 100 && s.split('.').length >= 3; | |
| // ============================================================ | |
| // Auth:自动从 session 中找 accessToken,不打印敏感信息 | |
| // ============================================================ | |
| function findAccessToken(obj, depth = 0) { | |
| if (!obj || typeof obj !== 'object' || depth > 8) return ''; | |
| for (const [key, value] of Object.entries(obj)) { | |
| if ( | |
| typeof value === 'string' && | |
| /access/i.test(key) && | |
| looksLikeJwt(value) | |
| ) { | |
| return value; | |
| } | |
| if (value && typeof value === 'object') { | |
| const found = findAccessToken(value, depth + 1); | |
| if (found) return found; | |
| } | |
| } | |
| return ''; | |
| } | |
| async function getAccessToken() { | |
| const manual = stripBearer(CONFIG.MANUAL_ACCESS_TOKEN); | |
| if (manual) return manual; | |
| try { | |
| const res = await fetch('/api/auth/session', { | |
| credentials: 'include', | |
| headers: { accept: 'application/json' }, | |
| }); | |
| if (!res.ok) return ''; | |
| return findAccessToken(await res.json()); | |
| } catch { | |
| return ''; | |
| } | |
| } | |
| const accessToken = await getAccessToken(); | |
| const headers = { | |
| accept: 'application/json', | |
| }; | |
| if (accessToken) { | |
| headers.authorization = `Bearer ${accessToken}`; | |
| } | |
| async function apiGet(path) { | |
| const res = await fetch(path, { | |
| method: 'GET', | |
| credentials: 'include', | |
| headers, | |
| }); | |
| if (!res.ok) { | |
| const text = await res.text().catch(() => ''); | |
| if (res.status === 401) { | |
| throw new Error( | |
| [ | |
| `HTTP 401 Unauthorized: ${path}`, | |
| '', | |
| '没有拿到有效 Authorization。', | |
| '处理方式:', | |
| '1. 先确认你已经登录 chatgpt.com,并在同一个页面运行脚本。', | |
| '2. 刷新 Codex Usage / Analytics 页面后重试。', | |
| '3. 仍失败时,可在 Network 面板找到成功的 /backend-api/wham/usage 请求,', | |
| ' 复制 Authorization: Bearer 后面的 token,只在自己电脑临时填到 CONFIG.MANUAL_ACCESS_TOKEN。', | |
| '', | |
| '不要把 token、Cookie、填过 token 的脚本或截图发给任何人。', | |
| ].join('\n') | |
| ); | |
| } | |
| throw new Error( | |
| `HTTP ${res.status} ${res.statusText}: ${path}\n${text.slice(0, 800)}` | |
| ); | |
| } | |
| return res.json(); | |
| } | |
| // ============================================================ | |
| // /usage 解析:刷新周期、重置时间、已用百分比 | |
| // ============================================================ | |
| function parseWindow(label, w) { | |
| const usedPercent = n(w?.used_percent); | |
| const windowSeconds = n(w?.limit_window_seconds); | |
| const resetAfterSeconds = n(w?.reset_after_seconds); | |
| const resetAtSeconds = n(w?.reset_at); | |
| const resetAtMs = resetAtSeconds * 1000; | |
| const windowStartMs = resetAtMs - windowSeconds * 1000; | |
| const serverNowMs = resetAtMs - resetAfterSeconds * 1000; | |
| return { | |
| 名称: label, | |
| 已用百分比: usedPercent, | |
| 已用比例小数: round(usedPercent / 100, 4), | |
| 窗口秒数: windowSeconds, | |
| 窗口天数: round(windowSeconds / 86400, 4), | |
| 本轮开始_UTC: fmtUTC(windowStartMs), | |
| 本轮开始_本地: fmtLocal(windowStartMs), | |
| 下次重置_UTC: fmtUTC(resetAtMs), | |
| 下次重置_本地: fmtLocal(resetAtMs), | |
| 后端当前_UTC: fmtUTC(serverNowMs), | |
| 后端当前_本地: fmtLocal(serverNowMs), | |
| 距离重置小时: round(resetAfterSeconds / 3600, 2), | |
| _windowStartMs: windowStartMs, | |
| _resetAtMs: resetAtMs, | |
| _serverNowMs: serverNowMs, | |
| }; | |
| } | |
| function collectWindows(usage) { | |
| const windows = []; | |
| if (usage?.rate_limit?.primary_window) { | |
| windows.push( | |
| parseWindow('主限制 - 5小时窗口', usage.rate_limit.primary_window) | |
| ); | |
| } | |
| if (usage?.rate_limit?.secondary_window) { | |
| windows.push( | |
| parseWindow('主限制 - 7天窗口', usage.rate_limit.secondary_window) | |
| ); | |
| } | |
| for (const item of usage?.additional_rate_limits ?? []) { | |
| const name = item.limit_name || item.metered_feature || '额外限制'; | |
| if (item?.rate_limit?.primary_window) { | |
| windows.push( | |
| parseWindow(`${name} - 5小时窗口`, item.rate_limit.primary_window) | |
| ); | |
| } | |
| if (item?.rate_limit?.secondary_window) { | |
| windows.push( | |
| parseWindow(`${name} - 7天窗口`, item.rate_limit.secondary_window) | |
| ); | |
| } | |
| } | |
| return windows; | |
| } | |
| // ============================================================ | |
| // daily-workspace-usage-counts 解析 | |
| // ============================================================ | |
| function parseDailyRows(json) { | |
| return (json.data ?? []) | |
| .slice() | |
| .sort((a, b) => String(a.date).localeCompare(String(b.date))) | |
| .map((d) => { | |
| const t = d.totals ?? {}; | |
| const credits = n(t.credits); | |
| return { | |
| 日期桶: d.date, | |
| Credits: round(credits, 6), | |
| 折算USD: round(credits * CONFIG.USD_PER_CREDIT, 2), | |
| 用户数: n(t.users), | |
| 线程数: n(t.threads), | |
| 轮数: n(t.turns), | |
| Token总量: tokenTotal(t), | |
| 缓存输入Token: n(t.cached_text_input_tokens), | |
| 非缓存输入Token: n(t.uncached_text_input_tokens), | |
| 输出Token: n(t.text_output_tokens), | |
| 客户端数量: Array.isArray(d.clients) ? d.clients.length : 0, | |
| 客户端Credits: (d.clients ?? []) | |
| .map((c) => `${c.client_id ?? 'UNKNOWN'}:${round(n(c.credits), 2)}`) | |
| .join(' | '), | |
| }; | |
| }); | |
| } | |
| function summarizeClients(json) { | |
| const map = new Map(); | |
| for (const day of json.data ?? []) { | |
| for (const c of day.clients ?? []) { | |
| const id = c.client_id ?? 'UNKNOWN'; | |
| const row = map.get(id) ?? { | |
| 客户端: id, | |
| Credits: 0, | |
| 折算USD: 0, | |
| 线程数: 0, | |
| 轮数: 0, | |
| Token总量: 0, | |
| 缓存输入Token: 0, | |
| 非缓存输入Token: 0, | |
| 输出Token: 0, | |
| }; | |
| const credits = n(c.credits); | |
| row.Credits += credits; | |
| row.折算USD += credits * CONFIG.USD_PER_CREDIT; | |
| row.线程数 += n(c.threads); | |
| row.轮数 += n(c.turns); | |
| row.Token总量 += tokenTotal(c); | |
| row.缓存输入Token += n(c.cached_text_input_tokens); | |
| row.非缓存输入Token += n(c.uncached_text_input_tokens); | |
| row.输出Token += n(c.text_output_tokens); | |
| map.set(id, row); | |
| } | |
| } | |
| return [...map.values()] | |
| .map((r) => ({ | |
| ...r, | |
| Credits: round(r.Credits, 6), | |
| 折算USD: round(r.折算USD, 2), | |
| })) | |
| .sort((a, b) => b.Credits - a.Credits); | |
| } | |
| async function fetchDailyUsage(startDate, endExclusiveDate) { | |
| const qs = new URLSearchParams({ | |
| start_date: startDate, | |
| end_date: endExclusiveDate, | |
| group_by: 'day', | |
| }); | |
| const url = `${CONFIG.DAILY_USAGE_PATH}?${qs}`; | |
| const json = await apiGet(url); | |
| return { | |
| url: location.origin + url, | |
| rows: parseDailyRows(json), | |
| clients: summarizeClients(json), | |
| }; | |
| } | |
| function summarizeRows(rangeName, rows, startDate, endExclusiveDate) { | |
| const credits = rows.reduce((s, r) => s + n(r.Credits), 0); | |
| return { | |
| 范围: rangeName, | |
| 日期桶口径: | |
| CONFIG.DATE_BUCKET_MODE === 'utc' ? 'UTC日期桶' : '本地日期桶', | |
| API_start_date: startDate, | |
| API_end_date_排他: endExclusiveDate, | |
| 返回日期桶数: rows.length, | |
| 首个返回日期桶: rows[0]?.日期桶 ?? '', | |
| 最后返回日期桶: last(rows)?.日期桶 ?? '', | |
| 累计Credits: round(credits, 6), | |
| 累计折算USD: round(credits * CONFIG.USD_PER_CREDIT, 2), | |
| 累计Token: rows.reduce((s, r) => s + n(r.Token总量), 0), | |
| 累计线程数: rows.reduce((s, r) => s + n(r.线程数), 0), | |
| 累计轮数: rows.reduce((s, r) => s + n(r.轮数), 0), | |
| }; | |
| } | |
| function publicWindowRow(w) { | |
| const { _windowStartMs, _resetAtMs, _serverNowMs, ...visible } = w; | |
| return visible; | |
| } | |
| function printTimezoneDiagnostics({ | |
| apiNowMs, | |
| windowStartMs, | |
| resetAtMs, | |
| sinceResetStartDate, | |
| monthStartDate, | |
| rollingStartDate, | |
| endExclusiveDate, | |
| }) { | |
| const browserTimeZone = | |
| Intl.DateTimeFormat().resolvedOptions().timeZone || '未知'; | |
| console.log('0)时区诊断:刷新周期与用量日期桶'); | |
| console.table([ | |
| { 项目: '浏览器本地时区', 值: browserTimeZone }, | |
| { 项目: '浏览器UTC偏移', 值: utcOffsetLabel(apiNowMs) }, | |
| { | |
| 项目: '当前脚本日期桶模式', | |
| 值: CONFIG.DATE_BUCKET_MODE === 'utc' ? 'UTC日期桶' : '本地日期桶', | |
| }, | |
| { 项目: '浏览器当前时间_本地', 值: fmtLocal(Date.now()) }, | |
| { 项目: '浏览器当前时间_UTC', 值: fmtUTC(Date.now()) }, | |
| { 项目: '后端当前时间_本地', 值: fmtLocal(apiNowMs) }, | |
| { 项目: '后端当前时间_UTC', 值: fmtUTC(apiNowMs) }, | |
| { | |
| 项目: '浏览器时间与后端时间差_秒', | |
| 值: round((Date.now() - apiNowMs) / 1000, 2), | |
| }, | |
| { 项目: '7天窗口开始_本地', 值: fmtLocal(windowStartMs) }, | |
| { 项目: '7天窗口开始_UTC', 值: fmtUTC(windowStartMs) }, | |
| { 项目: '下次重置时间_本地', 值: fmtLocal(resetAtMs) }, | |
| { 项目: '下次重置时间_UTC', 值: fmtUTC(resetAtMs) }, | |
| { 项目: '7天窗口开始日期_本地口径', 值: ymdLocal(windowStartMs) }, | |
| { 项目: '7天窗口开始日期_UTC口径', 值: ymdUTC(windowStartMs) }, | |
| { 项目: '后端当前日期_本地口径', 值: ymdLocal(apiNowMs) }, | |
| { 项目: '后端当前日期_UTC口径', 值: ymdUTC(apiNowMs) }, | |
| { 项目: '本月月初_本地口径', 值: firstDayOfMonthLocal(apiNowMs) }, | |
| { 项目: '本月月初_UTC口径', 值: firstDayOfMonthUTC(apiNowMs) }, | |
| { 项目: 'API_start_date_上次重置至今', 值: sinceResetStartDate }, | |
| { 项目: 'API_start_date_本月初至今', 值: monthStartDate }, | |
| { 项目: `API_start_date_近${CONFIG.ROLLING_DAYS}天`, 值: rollingStartDate }, | |
| { 项目: 'API_end_date_排他', 值: endExclusiveDate }, | |
| ]); | |
| } | |
| function buildWeeklyEstimate({ | |
| mainSecondary, | |
| sinceResetRows, | |
| sinceResetSummary, | |
| sinceResetStartDate, | |
| }) { | |
| const usedPercent = n(mainSecondary.已用百分比); | |
| const usedRatio = usedPercent / 100; | |
| const includedCredits = n(sinceResetSummary.累计Credits); | |
| const resetDayRow = sinceResetRows.find( | |
| (r) => r.日期桶 === sinceResetStartDate | |
| ); | |
| const resetDayCredits = n(resetDayRow?.Credits); | |
| const excludedCredits = Math.max(0, includedCredits - resetDayCredits); | |
| if (usedRatio <= 0) { | |
| return { | |
| 依据: '主限制 - 7天窗口 secondary_window', | |
| 已用百分比: usedPercent, | |
| 说明: '已用比例为 0,无法反推总额度。', | |
| }; | |
| } | |
| const totalWithResetDay = includedCredits / usedRatio; | |
| const totalWithoutResetDay = excludedCredits / usedRatio; | |
| const remainingWithResetDay = Math.max( | |
| 0, | |
| totalWithResetDay - includedCredits | |
| ); | |
| const remainingWithoutResetDay = Math.max( | |
| 0, | |
| totalWithoutResetDay - excludedCredits | |
| ); | |
| return { | |
| 依据: '主限制 - 7天窗口 secondary_window', | |
| 已用百分比: usedPercent, | |
| 已用比例小数: round(usedRatio, 4), | |
| 剩余比例小数: round(1 - usedRatio, 4), | |
| 说明: | |
| 'used_percent 表示已经用掉的比例;例如 45 = 已用 45%,不是剩余 45%。', | |
| 日期桶口径: | |
| CONFIG.DATE_BUCKET_MODE === 'utc' ? 'UTC日期桶' : '本地日期桶', | |
| 包含重置日_已用Credits: round(includedCredits, 6), | |
| 包含重置日_已用折算USD: round( | |
| includedCredits * CONFIG.USD_PER_CREDIT, | |
| 2 | |
| ), | |
| 重置日整天Credits: round(resetDayCredits, 6), | |
| 重置日整天折算USD: round( | |
| resetDayCredits * CONFIG.USD_PER_CREDIT, | |
| 2 | |
| ), | |
| 排除重置日_已用Credits: round(excludedCredits, 6), | |
| 排除重置日_已用折算USD: round( | |
| excludedCredits * CONFIG.USD_PER_CREDIT, | |
| 2 | |
| ), | |
| 反推周总Credits_包含重置日: round(totalWithResetDay, 2), | |
| 反推周总USD_包含重置日: round( | |
| totalWithResetDay * CONFIG.USD_PER_CREDIT, | |
| 2 | |
| ), | |
| 反推周总Credits_排除重置日: round(totalWithoutResetDay, 2), | |
| 反推周总USD_排除重置日: round( | |
| totalWithoutResetDay * CONFIG.USD_PER_CREDIT, | |
| 2 | |
| ), | |
| 剩余Credits_包含重置日口径: round(remainingWithResetDay, 2), | |
| 剩余USD_包含重置日口径: round( | |
| remainingWithResetDay * CONFIG.USD_PER_CREDIT, | |
| 2 | |
| ), | |
| 剩余Credits_排除重置日口径: round(remainingWithoutResetDay, 2), | |
| 剩余USD_排除重置日口径: round( | |
| remainingWithoutResetDay * CONFIG.USD_PER_CREDIT, | |
| 2 | |
| ), | |
| 误差说明: | |
| 'daily analytics 只能按天聚合,不能切到具体小时分钟;实际值通常介于“排除重置日”和“包含重置日”之间。used_percent 也是整数,存在四舍五入或截断误差。', | |
| }; | |
| } | |
| // ============================================================ | |
| // 主流程 | |
| // ============================================================ | |
| const usage = await apiGet(CONFIG.USAGE_PATH); | |
| const windows = collectWindows(usage); | |
| if (!usage?.rate_limit?.secondary_window) { | |
| throw new Error( | |
| '没有找到 usage.rate_limit.secondary_window,无法反推主 7 天窗口。' | |
| ); | |
| } | |
| const mainSecondary = parseWindow( | |
| '主限制 - 7天窗口', | |
| usage.rate_limit.secondary_window | |
| ); | |
| const apiNowMs = mainSecondary._serverNowMs || Date.now(); | |
| const apiTodayDate = ymdForApi(apiNowMs); | |
| // daily-workspace-usage-counts 的 end_date 是排他边界。 | |
| // 要尽量包含当前日期桶,所以传“后端当前日期 + 1天”。 | |
| const endExclusiveDate = ymdForApi(addDaysForApi(apiNowMs, 1)); | |
| // 7天窗口:用 UTC 日期桶取 windowStart 所在日期。 | |
| // 注意:因为接口只能按天,windowStart 当天只能整天纳入。 | |
| const sinceResetStartDate = ymdForApi(mainSecondary._windowStartMs); | |
| // 本月初至今:按 DATE_BUCKET_MODE 取月初。 | |
| const monthStartDate = firstDayOfMonthForApi(apiNowMs); | |
| // 近 N 天:包含今天,往前 N-1 个日期桶。 | |
| const rollingStartDate = ymdForApi( | |
| addDaysForApi(apiNowMs, -(CONFIG.ROLLING_DAYS - 1)) | |
| ); | |
| printTimezoneDiagnostics({ | |
| apiNowMs, | |
| windowStartMs: mainSecondary._windowStartMs, | |
| resetAtMs: mainSecondary._resetAtMs, | |
| sinceResetStartDate, | |
| monthStartDate, | |
| rollingStartDate, | |
| endExclusiveDate, | |
| }); | |
| const sinceReset = await fetchDailyUsage( | |
| sinceResetStartDate, | |
| endExclusiveDate | |
| ); | |
| const sinceResetSummary = summarizeRows( | |
| `上次重置至今近似 ${sinceResetStartDate} ~ ${apiTodayDate}`, | |
| sinceReset.rows, | |
| sinceResetStartDate, | |
| endExclusiveDate | |
| ); | |
| const weeklyEstimate = buildWeeklyEstimate({ | |
| mainSecondary, | |
| sinceResetRows: sinceReset.rows, | |
| sinceResetSummary, | |
| sinceResetStartDate, | |
| }); | |
| const monthToDate = await fetchDailyUsage(monthStartDate, endExclusiveDate); | |
| const monthToDateSummary = summarizeRows( | |
| `本月初至今 ${monthStartDate} ~ ${apiTodayDate}`, | |
| monthToDate.rows, | |
| monthStartDate, | |
| endExclusiveDate | |
| ); | |
| const rolling = await fetchDailyUsage(rollingStartDate, endExclusiveDate); | |
| const rollingSummary = summarizeRows( | |
| `近${CONFIG.ROLLING_DAYS}天 ${rollingStartDate} ~ ${apiTodayDate}`, | |
| rolling.rows, | |
| rollingStartDate, | |
| endExclusiveDate | |
| ); | |
| // ============================================================ | |
| // 输出 | |
| // ============================================================ | |
| console.log('1)限制窗口概览:刷新周期 UTC / 本地对照'); | |
| console.table(windows.map(publicWindowRow)); | |
| console.log('2)主 7 天窗口:上次重置至今,按 daily analytics 近似'); | |
| console.log('GET', sinceReset.url); | |
| console.table([sinceResetSummary]); | |
| console.log('3)用 used_percent 反推周额度'); | |
| console.table([weeklyEstimate]); | |
| console.log('4)上次重置至今每日明细'); | |
| console.table(sinceReset.rows); | |
| console.log('4.1)上次重置至今客户端汇总'); | |
| console.table(sinceReset.clients); | |
| console.log('5)本月初至今汇总'); | |
| console.log('GET', monthToDate.url); | |
| console.table([monthToDateSummary]); | |
| console.log('6)本月初至今日明细'); | |
| console.table(monthToDate.rows); | |
| console.log('6.1)本月初至今客户端汇总'); | |
| console.table(monthToDate.clients); | |
| console.log(`7)近${CONFIG.ROLLING_DAYS}天汇总`); | |
| console.log('GET', rolling.url); | |
| console.table([rollingSummary]); | |
| console.log(`8)近${CONFIG.ROLLING_DAYS}天每日明细`); | |
| console.table(rolling.rows); | |
| console.log(`8.1)近${CONFIG.ROLLING_DAYS}天客户端汇总`); | |
| console.table(rolling.clients); | |
| console.log( | |
| '说明:end_date 是排他边界;如果今天没有返回,通常是 daily analytics 尚未刷新或当天暂无统计。' | |
| ); | |
| return { | |
| 配置: { | |
| 日期桶模式: CONFIG.DATE_BUCKET_MODE, | |
| USD_PER_CREDIT: CONFIG.USD_PER_CREDIT, | |
| ROLLING_DAYS: CONFIG.ROLLING_DAYS, | |
| }, | |
| 时区诊断: { | |
| 浏览器本地时区: | |
| Intl.DateTimeFormat().resolvedOptions().timeZone || '未知', | |
| 浏览器UTC偏移: utcOffsetLabel(apiNowMs), | |
| 后端当前_UTC: fmtUTC(apiNowMs), | |
| 后端当前_本地: fmtLocal(apiNowMs), | |
| 七天窗口开始_UTC: fmtUTC(mainSecondary._windowStartMs), | |
| 七天窗口开始_本地: fmtLocal(mainSecondary._windowStartMs), | |
| 下次重置_UTC: fmtUTC(mainSecondary._resetAtMs), | |
| 下次重置_本地: fmtLocal(mainSecondary._resetAtMs), | |
| API_start_date_上次重置至今: sinceResetStartDate, | |
| API_start_date_本月初至今: monthStartDate, | |
| [`API_start_date_近${CONFIG.ROLLING_DAYS}天`]: rollingStartDate, | |
| API_end_date_排他: endExclusiveDate, | |
| }, | |
| 限制窗口概览: windows.map(publicWindowRow), | |
| 主7天窗口_上次重置至今: { | |
| 汇总: sinceResetSummary, | |
| 反推周额度: weeklyEstimate, | |
| 每日明细: sinceReset.rows, | |
| 客户端汇总: sinceReset.clients, | |
| }, | |
| 本月初至今: { | |
| 汇总: monthToDateSummary, | |
| 每日明细: monthToDate.rows, | |
| 客户端汇总: monthToDate.clients, | |
| }, | |
| [`近${CONFIG.ROLLING_DAYS}天`]: { | |
| 汇总: rollingSummary, | |
| 每日明细: rolling.rows, | |
| 客户端汇总: rolling.clients, | |
| }, | |
| }; | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@zzulpc 是的, 我也获取不到了