#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 推送端(本机运行)—— 把本机的用量原始快照 POST 到 server.py,服务端暂存、渲染时现算 pace。 推什么: - Claude:~/.claude/usage-snapshot.json 原文(statusline 生产,含 five_hour / seven_day / seven_day_fable / updated_at)→ POST /push/usage - ChatGPT:暂无本机数据源;将来有了按 data.GPT_WINDOWS 的键(five_hour / seven_day / seven_day_codex,各含 utilization + resets_at) 组一个 dict → POST /push/usage_gpt。目前不推,服务端用 mock 并标「示例数据」。 鉴权:请求头 X-Push-Token = PUSH_TOKEN。网络:直连(不走代理),连接类异常重试 2 次。 调度:与喵喵设备推送同节律,由 launchd 每 5 分钟跑一次 `main.py --target push`(或加进 run.sh)。 """ import json import time import urllib.error import urllib.request import config import usage_local RETRY_TIMES, RETRY_DELAY, TIMEOUT = 2, 2, 10 def _post_json(url, payload): body = json.dumps(payload, ensure_ascii=False).encode("utf-8") req = urllib.request.Request(url, data=body, method="POST", headers={ "Content-Type": "application/json; charset=utf-8", "X-Push-Token": config.PUSH_TOKEN, "User-Agent": "eink-push/push_client", }) last = None for i in range(RETRY_TIMES + 1): try: with urllib.request.urlopen(req, timeout=TIMEOUT) as resp: return json.loads(resp.read().decode("utf-8")) except urllib.error.HTTPError as e: # 4xx/5xx 不重试,直接报出服务端的说明 raise RuntimeError(f"HTTP {e.code}: {e.read().decode('utf-8', 'replace')[:200]}") from e except urllib.error.URLError as e: # 连接类:重试 last = e if i < RETRY_TIMES: time.sleep(RETRY_DELAY) raise RuntimeError(f"连接失败:{last}") def push_usage(): if not config.PUSH_URL or not config.PUSH_TOKEN: raise SystemExit("[推送] 需在 .env 配置 PUSH_URL 与 PUSH_TOKEN") raw = usage_local.get_usage() # 缺文件直接抛错——没有快照就没什么可推 resp = _post_json(config.PUSH_URL.rstrip("/") + "/push/usage", raw) print(f"[推送] Claude 用量快照已推送到 {config.PUSH_URL}:{resp}") return resp if __name__ == "__main__": push_usage()