codex重置机会的具体过期时间是什么时候?

按获得时间计算 可以调接口查,脚本参考一下

#!/usr/bin/env python3
"""
Fetch Codex/WHAM usage-related ChatGPT backend endpoints with the local Codex auth.

The script reads ~/.codex/auth.json by default, extracts the bearer token, then calls:
  - https://chatgpt.com/backend-api/wham/usage
  - https://chatgpt.com/backend-api/wham/rate-limit-reset-credits
  - https://chatgpt.com/backend-api/wham/referrals/eligibility_rules?referral_key=codex_referral_persistent_invite

It never prints the token. By default it prints a Chinese human-readable summary.
Use --json to print raw data.
"""

from __future__ import annotations

import argparse
import json
import os
import sys
import urllib.error
import urllib.request
from dataclasses import dataclass
from pathlib import Path
from typing import Any


DEFAULT_ENDPOINTS = [
    "https://chatgpt.com/backend-api/wham/usage",
    "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits",
    "https://chatgpt.com/backend-api/wham/referrals/eligibility_rules?referral_key=codex_referral_persistent_invite",
]

ENDPOINT_TITLES = {
    "https://chatgpt.com/backend-api/wham/usage": "Codex 用量",
    "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits": "重置次数 / 额度",
    "https://chatgpt.com/backend-api/wham/referrals/eligibility_rules?referral_key=codex_referral_persistent_invite": "邀请资格规则",
}

FIELD_LABELS = {
    "account_id": "账户 ID",
    "available": "可用",
    "available_credits": "可用额度",
    "balance": "余额",
    "code": "错误代码",
    "count": "数量",
    "created_at": "创建时间",
    "credits": "额度",
    "daily_limit": "每日限制",
    "eligible": "是否符合资格",
    "eligibility": "资格",
    "enabled": "是否启用",
    "error": "错误",
    "expires_at": "过期时间",
    "granted": "已发放",
    "hard_limit": "硬限制",
    "is_eligible": "是否符合资格",
    "limit": "限制",
    "limit_remaining": "剩余额度",
    "message": "消息",
    "model": "模型",
    "next_reset_at": "下次重置时间",
    "period": "周期",
    "plan": "套餐",
    "quota": "配额",
    "rate_limit": "速率限制",
    "remaining": "剩余",
    "remaining_credits": "剩余次数",
    "reset": "重置",
    "reset_at": "重置时间",
    "reset_credits": "重置次数",
    "reset_time": "重置时间",
    "soft_limit": "软限制",
    "status": "状态",
    "total": "总计",
    "total_credits": "总次数",
    "type": "类型",
    "updated_at": "更新时间",
    "usage": "用量",
    "used": "已用",
    "used_credits": "已用次数",
}


@dataclass(frozen=True)
class CodexAuth:
    token: str
    token_source: str
    account_id: str | None = None


def load_json(path: Path) -> dict[str, Any]:
    try:
        with path.open("r", encoding="utf-8") as f:
            data = json.load(f)
    except FileNotFoundError:
        raise SystemExit(f"auth file not found: {path}") from None
    except json.JSONDecodeError as exc:
        raise SystemExit(f"auth file is not valid JSON: {path}: {exc}") from None

    if not isinstance(data, dict):
        raise SystemExit(f"auth file root must be a JSON object: {path}")
    return data


def first_string(data: dict[str, Any], paths: list[tuple[str, ...]]) -> tuple[str, str] | tuple[None, None]:
    for path in paths:
        current: Any = data
        for key in path:
            if not isinstance(current, dict) or key not in current:
                current = None
                break
            current = current[key]
        if isinstance(current, str) and current.strip():
            return current.strip(), ".".join(path)
    return None, None


def extract_auth(data: dict[str, Any], token_kind: str) -> CodexAuth:
    access_paths = [
        ("tokens", "access_token"),
        ("tokens", "accessToken"),
        ("access_token",),
        ("accessToken",),
    ]
    id_paths = [
        ("tokens", "id_token"),
        ("tokens", "idToken"),
        ("id_token",),
        ("idToken",),
    ]

    if token_kind == "access":
        token, source = first_string(data, access_paths)
    elif token_kind == "id":
        token, source = first_string(data, id_paths)
    else:
        token, source = first_string(data, access_paths + id_paths)

    if not token or not source:
        raise SystemExit(
            "could not find a bearer token in auth.json. "
            "Tried tokens.access_token/accessToken and tokens.id_token/idToken. "
            "Run `codex login` again, or pass a different file with --auth."
        )

    account_id, _ = first_string(
        data,
        [
            ("tokens", "account_id"),
            ("tokens", "accountId"),
            ("account_id",),
            ("accountId",),
        ],
    )
    return CodexAuth(token=token, token_source=source, account_id=account_id)


def decode_body(raw: bytes, content_type: str | None) -> Any:
    text = raw.decode("utf-8", errors="replace")
    if content_type and "json" in content_type.lower():
        try:
            return json.loads(text)
        except json.JSONDecodeError:
            return text

    stripped = text.strip()
    if stripped.startswith("{") or stripped.startswith("["):
        try:
            return json.loads(stripped)
        except json.JSONDecodeError:
            pass
    return text


def request_json(url: str, auth: CodexAuth, timeout: float) -> dict[str, Any]:
    headers = {
        "Authorization": f"Bearer {auth.token}",
        "Accept": "application/json",
        "User-Agent": "codex-wham-usage-fetcher/1.0",
        "Origin": "https://chatgpt.com",
        "Referer": "https://chatgpt.com/codex",
    }
    if auth.account_id:
        headers["ChatGPT-Account-ID"] = auth.account_id

    req = urllib.request.Request(url=url, headers=headers, method="GET")
    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            raw = resp.read()
            return {
                "ok": 200 <= resp.status < 300,
                "status": resp.status,
                "url": url,
                "body": decode_body(raw, resp.headers.get("Content-Type")),
            }
    except urllib.error.HTTPError as exc:
        raw = exc.read()
        return {
            "ok": False,
            "status": exc.code,
            "url": url,
            "reason": exc.reason,
            "body": decode_body(raw, exc.headers.get("Content-Type")),
        }
    except urllib.error.URLError as exc:
        return {
            "ok": False,
            "status": None,
            "url": url,
            "reason": str(exc.reason),
            "body": None,
        }


def label_for_key(key: str) -> str:
    if key in FIELD_LABELS:
        return FIELD_LABELS[key]
    return key.replace("_", " ")


def format_scalar(value: Any) -> str:
    if value is True:
        return "是"
    if value is False:
        return "否"
    if value is None:
        return "无"
    if isinstance(value, (int, float)):
        return str(value)
    if isinstance(value, str):
        return value if value else "空字符串"
    return str(value)


def format_body(body: Any, indent: int = 0) -> list[str]:
    prefix = "  " * indent
    lines: list[str] = []

    if isinstance(body, dict):
        if not body:
            return [f"{prefix}- 无返回字段"]
        for key, value in body.items():
            label = label_for_key(str(key))
            if isinstance(value, dict):
                lines.append(f"{prefix}- {label}:")
                lines.extend(format_body(value, indent + 1))
            elif isinstance(value, list):
                lines.append(f"{prefix}- {label}:")
                lines.extend(format_body(value, indent + 1))
            else:
                lines.append(f"{prefix}- {label}: {format_scalar(value)}")
        return lines

    if isinstance(body, list):
        if not body:
            return [f"{prefix}- 空列表"]
        for index, item in enumerate(body, start=1):
            if isinstance(item, (dict, list)):
                lines.append(f"{prefix}- 第 {index} 项:")
                lines.extend(format_body(item, indent + 1))
            else:
                lines.append(f"{prefix}- 第 {index} 项: {format_scalar(item)}")
        return lines

    return [f"{prefix}- 内容: {format_scalar(body)}"]


def format_text_report(results: dict[str, Any]) -> str:
    lines = [
        "Codex 用量与额度查询结果",
        "",
        f"认证文件: {results['auth_path']}",
        f"Token 来源: {results['token_source']}",
        f"是否包含账户 ID: {'是' if results['account_id_present'] else '否'}",
        "",
    ]

    for index, item in enumerate(results["results"], start=1):
        title = ENDPOINT_TITLES.get(item["url"], "自定义接口")
        ok_text = "成功" if item["ok"] else "失败"
        status = item["status"] if item["status"] is not None else "无状态码"
        lines.extend(
            [
                f"{index}. {title}",
                f"接口: {item['url']}",
                f"请求结果: {ok_text}",
                f"HTTP 状态: {status}",
            ]
        )
        if item.get("reason"):
            lines.append(f"失败原因: {item['reason']}")
        lines.append("返回信息:")
        lines.extend(format_body(item.get("body"), indent=1))
        lines.append("")

    return "\n".join(lines).rstrip() + "\n"


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Fetch Codex WHAM usage/rate-limit/referral endpoint data using ~/.codex/auth.json."
    )
    parser.add_argument(
        "--auth",
        type=Path,
        default=Path.home() / ".codex" / "auth.json",
        help="Path to Codex auth.json. Default: ~/.codex/auth.json",
    )
    parser.add_argument(
        "--token-kind",
        choices=["auto", "access", "id"],
        default="auto",
        help="Which bearer token to use from auth.json. Default: auto",
    )
    parser.add_argument(
        "--timeout",
        type=float,
        default=30.0,
        help="HTTP timeout in seconds. Default: 30",
    )
    parser.add_argument(
        "--endpoint",
        action="append",
        dest="endpoints",
        help="Additional endpoint to fetch. Can be provided multiple times.",
    )
    parser.add_argument(
        "--only-extra-endpoints",
        action="store_true",
        help="Fetch only endpoints passed with --endpoint, skipping the built-in list.",
    )
    parser.add_argument(
        "--json",
        action="store_true",
        help="Print raw JSON instead of the default Chinese text report.",
    )
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    auth_path = args.auth.expanduser()
    auth_data = load_json(auth_path)
    auth = extract_auth(auth_data, args.token_kind)

    endpoints: list[str] = []
    if not args.only_extra_endpoints:
        endpoints.extend(DEFAULT_ENDPOINTS)
    if args.endpoints:
        endpoints.extend(args.endpoints)
    if not endpoints:
        raise SystemExit("no endpoints to fetch")

    results = {
        "auth_path": os.fspath(auth_path),
        "token_source": auth.token_source,
        "account_id_present": bool(auth.account_id),
        "results": [request_json(url, auth, args.timeout) for url in endpoints],
    }
    if args.json:
        json.dump(results, sys.stdout, ensure_ascii=False, indent=2)
        sys.stdout.write("\n")
    else:
        sys.stdout.write(format_text_report(results))
    return 0


if __name__ == "__main__":
    raise SystemExit(main())