接上篇 《把 WorkBuddy 每日签到搬上云端》 (2026-08-28)
快进到 操作步骤
仓库地址
源码(建议直接 Fork):cnb.cool/holzwege.co…
Fork 之后就得到了你自己的「签到仓库」,下面所有操作都在你自己的仓库里进行。
一、为什么需要这个
1. 为什么不能只在本机跑
签到要挂在「每天某个时刻」上,本机方案有个绕不过去的坑:机器得开着。 笔记本合盖、下班关机、客户端没启动,签到就断了。断了还得手动补, 补的时候又常常已经错过活动重置时间。
搬到云端定时跑,这些都不再是问题。代价是凭据得从本机挪到云端。
2. 为什么必须有「密钥仓库」这个独立仓库
因为凭据不能写在代码仓库里。
.cnb.yml 里没有任何 env 硬编码的 token —— 凭据只能经由 CNB 的 imports
从另一个私有仓库注入。这样带来三个好处:
- 签到仓库可以放心开源、随手分享,里面一个密钥都没有
- 换 token 只动密钥仓库一个文件,不用改代码、不用重跑流水线
- 泄漏面收敛:即使代码仓库被看到,也拿不到任何可用凭据
3. 为什么凭据只能在本机取一次
几个平台的登录态都没法在云端解密:
| 平台 | 本机登录态形态 | 为什么云端解不了 |
|---|---|---|
| WorkBuddy | AES-256-GCM 信封 | 解密密钥只驻留客户端进程内存,不落盘 |
| TRAE Work | AES-256-CBC 加密块 | 密钥由客户端代码派生,且设备ID只出现在客户端日志里 |
| Qoder CN | DPAPI 保护的 AES 密钥 | DPAPI 与本机 Windows 账户强绑定,不可导出 |
| MiniMax Code | 浏览器 localStorage 的 _token | 就是个 JWT,浏览器里复制一下就行 —— 唯一不需要客户端的 |
所以架构只能是三段式:
本机(Windows,一次性) CNB 密钥仓库(私有) CNB 云端(每天定时)
auth/<平台>_auth.py → <平台>_secrets.yml → runner.py + providers/
解出明文凭据 只读、只被 imports 引用 不碰加密,直接用明文
4. 为什么只要一个仓库
最初每个工具各占一个仓库,结果是同一套「取凭据 → 存密钥 → 定时签到」 被抄了三遍,三份代码慢慢长得不一样,命名也各不相同。合并成一个仓库之后:
- 一套调度:几点跑、跑哪些、跑挂了怎么办,只有一个地方要改
- 一套命名:见下面「命名约定」,脚本、产物、变量名全部同构
- 加工具只加文件:不碰
.cnb.yml - 工具自己的「附加动作」也收在 provider 里:比如 WorkBuddy 签完要派遣
Buddy(喵喵)旅行,就放在
providers/travel.py,由 workbuddy provider 调用, 不往 runner 里塞平台判断(见「四、WorkBuddy 的附加动作」)
二、命名约定
这套约定是强制的 —— tests/selftest.py 会检查,写歪了自测直接红。
| 项 | 约定 | 示例 |
|---|---|---|
| 取凭据脚本 | auth/<平台>_auth.py | auth/qoder_auth.py |
| 一键导出(汇总器) | export_auth.py | tools/ai-checkin/export_auth.py |
| 取凭据产物 | <平台>_secrets.yml | qoder_secrets.yml |
| 密钥仓库内文件 | .cnb/<平台>-secrets.yml | .cnb/qoder-secrets.yml |
| 云端实现 | providers/<平台>.py | providers/qoder.py |
| 环境变量前缀 | <平台大写>_ | QODER_TOKEN、QODER_MACHINE_OS |
| 工具名 | 全小写平台名 | workbuddy / trae / qoder / minimax / baidu |
一句话:平台名是唯一的命名基准,脚本、文件、变量都从它派生。
取凭据脚本一律叫 *_auth.py,作用一律是「拿本机登录态,生成密钥文件」;
产物一律以平台名打头。
汇总器只有一个:export_auth.py 调各平台的 collect(),把它们合成一份成品。
它是「汇总」不是「取凭据」,所以放在 tools/ai-checkin/ 而不是 auth/ ——
selftest.py 会盯着这条边界,别把平台无关的脚本混进 auth/。
三、操作步骤
① 准备密钥仓库
有一个私有仓库专门放凭据(本仓库默认指向 holzwege.coz/check-in/secrets)。
没有就新建一个,把 .cnb.yml 里 imports 的地址换成你的。
② 在本机一键生成密钥文件(推荐:一条命令)
在仓库根目录执行:
python tools/ai-checkin/export_auth.py
脚本在
tools/ai-checkin/下,必须带上这个相对路径; 在仓库根目录直接敲python export_auth.py会报can't open file '...\export_auth.py': [Errno 2] No such file or directory。 也可以先cd tools/ai-checkin再python export_auth.py,脚本自己会处理路径。
它会逐个平台取本机登录态,然后合成一份 ai-checkin-secrets.yml ——
文件与密钥仓库里那份同名同构,全选复制、整份覆盖粘贴即可,不用再手工拼。
# ── AI 工具签到 · CNB 密钥仓库文件(一键生成,整份复制即可)────────
# 已包含工具:workbuddy、trae
# 未包含工具(补上凭据后重新跑一次 export_auth.py 即可):
# - qoder:未找到 Qoder CN 登录态文件,请先在客户端登录。
allow_slugs:
- "<你的组织>/<签到仓库>"
allow_events:
- "crontab: 2 0 * * *"
- "crontab: 2 10 * * *"
- web_trigger
# ── workbuddy 凭据 ─────────────────────────────────────
WORKBUDDY_TOKEN: "<明文凭据>"
WORKBUDDY_DOMAIN: "www.codebuddy.cn"
# ── trae 凭据 ──────────────────────────────────────────
TRAE_CLOUDIDE_TOKEN: "<明文凭据>"
TRAE_DEVICE_ID: "<纯数字设备ID>"
# ── minimax 凭据 ───────────────────────────────────────
MINIMAX_TOKEN: "<浏览器 localStorage 的 _token>"
# ── baidu 凭据 ─────────────────────────────────────────
BAIDU_COOKIE: "<客户端登录态 Cookie,含 BDUSS>"
授权范围(allow_slugs / allow_events)只出现一次,从 .cnb.yml
逐字推导 —— 这正是手工合并最容易写坏的地方(三份产物各带一份,同名 key 互相覆盖)。
前提:对应客户端已启动并登录。各平台的数据来源不同,所以一台机器上通常 只能导出装了客户端的那几个;取不到的平台自动跳过(文件头会注明原因), 不当失败 —— runner 对缺凭据的工具本来就是 skip。
常用选项:
python tools/ai-checkin/export_auth.py --stdout # 打到终端,不落盘
python tools/ai-checkin/export_auth.py --copy # 生成后直接进剪贴板
python tools/ai-checkin/export_auth.py --only qoder # 只导出指定平台
python tools/ai-checkin/export_auth.py --slug <组织>/<仓库> # 覆盖 allow_slugs
三个安全约定:授权范围占位符会在粘贴前提醒;覆盖已有产物时若平台变少会
拒绝(加 --force 才放行),免得一次误跑把凭据越写越少;一个都没取到
就不落盘。
只想单独取某一个平台也可以,生成的是单平台文件:
python tools/ai-checkin/auth/workbuddy_auth.py # → workbuddy_secrets.yml python tools/ai-checkin/auth/trae_auth.py # → trae_secrets.yml python tools/ai-checkin/auth/qoder_auth.py --full # → qoder_secrets.yml MINIMAX_TOKEN="eyJ..." python tools/ai-checkin/auth/minimax_auth.py # → minimax_secrets.yml python tools/ai-checkin/auth/baidu_auth.py # → baidu_secrets.yml不想落盘就加
--verify(只校验能不能解出来)或--stdout。 这条路要自己把几份产物拼成一份,注意allow_slugs/allow_events只能留一份。
③ 粘贴进密钥仓库
把 ai-checkin-secrets.yml 整份复制,覆盖密钥仓库里的
.cnb/ai-checkin-secrets.yml,提交。
.cnb.yml 里只有一行 imports,指向的正是这个路径:
imports:
- https://cnb.cool/<密钥仓库>/-/blob/main/.cnb/ai-checkin-secrets.yml
所以文件只有一份,变量按工具分几组 —— 加工具只是多一组变量,不用动 imports。
手写也行:
tools/ai-checkin/secrets.example.yml是完整的变量清单模板。 但要小心allow_events必须与.cnb.yml的事件键逐字一致 (含 cron 表达式与空格),只写crontab会在 Prepare 阶段报does not conform to allow_events。用脚本生成就没这个问题。
④ 跑一次验证
在「代码构建」页点「执行」(或推一次代码触发 push),看日志:
>>> [workbuddy] WorkBuddy(签到 + 派遣 Buddy 旅行)
结果: OK · 签到成功(12.3s)
(detail 里多一段 travel:派猫猫旅行:空闲(可派遣)……)
>>> [trae] TRAE Work(每日签到领积分)
结果: SKIP · 今日已签到(3.1s)
>>> [qoder] Qoder CN(每日签到领 Credits)
结果: SKIP · 当前无可领活动(2.4s)
>>> [minimax] MiniMax Code(每日签到领积分)
结果: OK · 签到成功(0.6s)
>>> [baidu] 百度搭子(客户端签到领积分)
结果: OK · 签到成功(1.1s)
汇总:成功 3 / 跳过 2 / 失败 0
日志末尾会打整段 JSON 摘要(含每个工具的 detail),要看全就往下翻。
WorkBuddy 的 detail.travel 就是派遣结果:available: false 说明旅行接口
没通(签到本身没坏),auto_log 里是「已派出 Buddy 前往【XX】」之类的原话。
没配的工具显示 SKIP 是正常的,不算失败 —— 想启用就把它的凭据补进密钥仓库。
用「代码构建」页手动跑时,密钥仓库的
allow_events要加上web_trigger, 否则 Prepare 阶段失败。脚本生成的产物会自动带上(如果.cnb.yml里有这个入口)。
⑤ 之后就自动跑了
| 时间(北京) | 跑什么 | 为什么是这个点 |
|---|---|---|
| 每天 00:02 | workbuddy + trae + minimax + baidu | 按自然日重置,零点后 2 分钟领最稳 |
| 每天 10:02 | qoder | Qoder 活动每日 10:00 重置,早跑必是空的 |
四、WorkBuddy 的附加动作:派遣 Buddy(喵喵)
「签到」只是 WorkBuddy 这条线的一半。旧仓库 holzwege.coz/workbuddy-checkin
里还有两件本仓库合并时漏掉的事,这次一并接回来了:
| 动作 | 在哪 | 说明 |
|---|---|---|
| 派遣 Buddy 旅行 | providers/travel.py | 签到之后「先领后派」闭环 |
| 多渠道消息通知 | notifier.py | runner 跑完每个工具发一条,配置从密钥仓库注入 |
旅行为什么单独一个文件
因为旅行接口和签到接口不是同一个域名,路径规则也不一样:
签到 https://www.codebuddy.cn/v2/billing/meter/... ← 带 /v2
旅行 https://www.workbuddy.cn/activity/growth/buddy/... ← 不带 /v2
写错任一项直接 404。把两者塞进一个 provider 里,改签到路由时很容易连累旅行,
所以独立成 travel.py,默认域名写死,只有服务端路由变了才用
WORKBUDDY_TRAVEL_DOMAIN 覆盖。
闭环逻辑(与旧仓库一致):已到达就先领积分,空闲且未达每日上限才派出。 三条安全约束:
- 达每日上限绝不发派遣请求 —— 上限外的请求服务端会白记一次
- 只读接口失败(500 / 返回结构不对)一律降级,一个写请求都不发
- 派遣任何异常都就地吞掉,不改变签到结论、不影响退出码
想只签到不派遣:密钥仓库里加 WORKBUDDY_TRAVEL: "off"。
通知怎么配
通知配置和凭据一样只能从密钥仓库来(容器里没有本机配置文件):
# 值本身是一份 JSON(与旧仓库本机的 notify_config.json 同构),不是文件路径
AI_CHECKIN_PUSH_CONFIG: '{"channels": {"wecom": {"webhook": "https://..."}}}'
AI_CHECKIN_PUSH_CHANNELS: "wecom" # 选填,缺省发配置里全部已就绪渠道
AI_CHECKIN_PUSH_ON: "error" # 选填:error(默认,只报失败)/ ok / always
支持钉钉、飞书、企业微信、PushPlus、邮件、Bark、Slack、Telegram、通用 webhook…
(渠道实现从旧仓库 push_message.py 逐字平移,字段见 notifier.py 的 _SAMPLE_CONFIG)。
默认只在签到失败时发(AI_CHECKIN_PUSH_ON=error):每天四条「签到成功」迟早被练成
免打扰,真该看的失败反而淹在里面 —— 失败必须响,成功静默即可。想成/败都发就写 always。
不注入就完全不发通知,这属预期,不算失败。通知本身坏了也只吞掉自己 —— 它和签到是同一条流水线里的两件事,通知挂掉不该让签到结果变红。
本机那份
~/.workbuddy/scripts/notify_config.json仍然会被读(方便本机调试), 但云端容器里没有它,所以线上只有注入这一条路。
启动时会打印通知配置(脱敏)
每次 runner 起来,先打一遍通知配置,省得「到底配没配、发去哪」只能靠猜:
通知:已配置(来源 AI_CHECKIN_PUSH_CONFIG,模式 error,enabled=True)
· wecom:webhook=https://qyapi.weixin.qq.com/***/***/***?key=***
· 未配置:dingtalk, feishu, wechat, bark, ...
没注入时是 通知:未配置 —— 未注入 AI_CHECKIN_PUSH_CONFIG,本次不发通知(属预期,不算失败)。
配置写坏了也会打出原因而不是把签到带崩。凭据一律脱敏:URL 的 path 整段遮掉、query 只留键名,
token 类字段只留长度 —— host 足够判断「配到哪个平台」,凭据一个字符都不落日志。
单独看这份摘要(不跑签到):
cd tools/ai-checkin
python3 notifier.py --report # 与流水线启动时打印的是同一份
python3 notifier.py --ready # 机器可读版(JSON)
五、MiniMax Code:不用客户端的网页签到
四个平台里唯一「不需要装客户端」的一个 —— 因为它有个网页版的每日签到,
凭据就是浏览器 localStorage 里的 _token(一个 JWT),抠出来即可。
在哪拿 token
- 浏览器打开 agent.minimax.cn/ 并登录
F12→ Application → Local Storage →https://agent.minimax.cn→ 复制_token- 本机执行:
cd tools/ai-checkin
MINIMAX_TOKEN="eyJ..." python auth/minimax_auth.py # 生成 minimax_secrets.yml
MINIMAX_TOKEN="eyJ..." python auth/minimax_auth.py --verify # 先在线校验一次再导出(推荐)
user_id(= user_detail_agent.realUserID)是必填项,不是可选优化:
签到 query 里缺了它、或写成 0,网关一律回 401,看起来和「token 过期」一模一样。
好消息是脚本会自动去 user/info 把它补上,所以通常你只要给 _token 就够;
想显式指定就用 --user-id,或直接把 DevTools 里复制的整段 JSON 丢给 --token。
想省事可以 --save,token 存到 ~/.minimax/token.json,之后重跑不用再粘
(明文,只在本机;已在 .gitignore 之外,别提交)。
签到口径
接口就两个:GET /signin/status(7 天进度面板)、POST /signin/claim(领取)。
- 只有 status 明确说「今天可领」才发 claim —— 不可领时一个写请求都不打
- 「今日已签到」是 skip 不是失败:面板今天那格已是
Claimed,或 claim 返回AlreadyClaimed,都按 skip 收口,重跑不会刷红流水线 401= token 失效,按skip · Token 失效收口(见下节);400= 签名被拒, 才是真error(网页端公共参数变了,看providers/minimax.py)
六、「skip」与「error」的边界
四个平台的凭据都会过期,过期是常态不是事故。所以口径定死:
| 结果 | 什么时候 | 流水线 |
|---|---|---|
skip | 今日已签到、当前无可领、凭据失效、未配置 | 绿 |
error | 网络/5xx、响应结构变了、签到被拒但原因不明 | 红 |
凭据失效的判定收在 providers/authfail.py 一处(HTTP 401/403,或业务码 1001),
各 provider 只管调它。这样做的理由:从前的实现里每家自己 return error,
任何一家 token 过期就把整条流水线刷红 —— 其它三家明明签得好好的,日报却只有
一片红,分不清是「签到坏了」还是「该换 token 了」。
现在过期只留一行 skip · Token 失效,请重新…,流水线照常绿,看到 reason 就知道
该去密钥仓库换凭据了。注意 skip 不等于签到成功,该换的凭据还是得换。
真出错仍是
error。这个口径只放宽「凭据失效」这一种,不兜底 5xx 和响应异常。
七、加一个新工具
四步,不用改 .cnb.yml:
providers/下加文件,导出PROVIDER = YourProvider()providers/__init__.py的_MODULES加一行auth/下加your_tool_auth.py,从_common拿现成骨架, 并导出collect(full=False) -> (values, summary)—— 一键导出就是靠它自动带上新工具- 密钥仓库加一组
YOUR_TOOL_*变量
第 4 步的变量名要登记到 tests/selftest.py 的 test_real_required_env_names ——
写错变量名线上会永远 skip 且没人发现,这条断言就是防这个的。
工具自己的附加动作(如 WorkBuddy 的派遣)放在它自己的文件或同级模块里,
不要往 runner.py 里加平台判断 —— runner 的职责只有「逐个跑、逐个兜错、统一收口」。
八、本地自测
不联网,验证装配是否正确(含命名约定、变量对齐、AES 后端对拍):
cd tools/ai-checkin && python3 tests/selftest.py
手动跑一次真实签到(会消耗当天签到机会,注意幂等):
cd tools/ai-checkin
python3 runner.py --list # 看哪些工具已配置
python3 runner.py workbuddy # 只跑一个
python3 runner.py # 跑全部
九、目录结构
.
├── .cnb.yml # 流水线:00:02 主跑 / 10:02 Qoder / 手动 / 知识库
├── README.md # 本文档
└── tools/ai-checkin/
├── runner.py # 汇总调度器:逐个跑、逐个兜错、统一摘要、发通知
├── notifier.py # 多渠道通知(标准库,配置由环境变量注入)
├── export_auth.py # 一键导出:各平台凭据合成一份密钥文件
├── secrets.example.yml # 密钥仓库文件模板(手写时的变量清单)
├── auth/ # 取凭据:本机跑,生成密钥文件
│ ├── _common.py # 公共骨架:授权推导 + YAML 生成/合并 + 脱敏输出
│ ├── _workbuddy_atrest.py # WorkBuddy 登录态解密器
│ ├── workbuddy_auth.py
│ ├── trae_auth.py
│ ├── qoder_auth.py
│ ├── minimax_auth.py # 网页凭据:--from-browser 直读 / --renew 续期
│ └── baidu_auth.py # 百度搭子:读客户端 auth.json + .cookie-key 解密(L1/L2/L3 分层)
├── providers/ # 云端签到:零第三方依赖
│ ├── __init__.py # 工具注册表
│ ├── base.py # Provider 基类与环境变量取值
│ ├── authfail.py # 凭据失效判定:401/403、业务码 1001/10001 → skip
│ ├── workbuddy.py # 签到,签完调用 travel
│ ├── travel.py # 派遣 Buddy 旅行(域名与签到不同,故独立)
│ ├── trae.py
│ ├── qoder.py
│ ├── minimax.py # MiniMax Code 网页签到(网关签名 + user_id 自愈 + 401 自动续期)
│ └── baidu.py # 百度搭子(DuMate)签到领积分(Cookie 鉴权)
└── tests/selftest.py # 离线回归自检
十、常见问题
| 现象 | 原因与处理 |
|---|---|
skip · 缺少凭据 X | 该工具未配置,正常。补密钥仓库变量即可 |
skip · Token 失效 | 凭据过期(401/403 或业务码 1001)。不刷红流水线,但也没签到 —— 重跑 export_auth.py 更新密钥仓库 |
error + 9074(TRAE) | 设备ID 不对,必须是客户端 AHA SDK 的纯数字 did,不是 telemetry.devDeviceId |
Prepare 阶段 allow_events 报错 | 密钥里的 allow_events 与 .cnb.yml 事件键不一致,用脚本重新生成 |
| Qoder 永远「无可领活动」 | 活动每日 10:00 重置;确认跑在 10:02 那条 cron |
| 构建整体失败但只有一家挂了 | runner 口径是「有 error 就非 0」,看摘要定位具体工具;若是 skip · Token 失效 则不会失败,属提醒 |
| 取凭据报「未找到登录态」 | 对应客户端没登录,或没和脚本在同一用户下运行 |
export_auth.py 只导出了一部分 | 正常:没装客户端的平台取不到。文件头会写明缺席的是谁 |
| 覆盖产物时被拒(平台变少) | 防「越跑越秃」:确认要删就加 --force |
| WorkBuddy 有签到但没派遣 | 看 detail.travel.available:false 是旅行接口没通(签到没坏);WORKBUDDY_TRAVEL=off 则是自己关掉的 |
| 一次通知都没收到 | 先看启动那几行 通知:...:显示「未配置」= 没注入(属预期);显示已配置但模式是 error 且这次全成功,那本来就不发 |
通知里出现 notify_error | 通知模块自己抛了(签名/配置/网络)。签到结论不受影响,看这行定位 |
| 派遣多打了一个请求 / 404 | 旅行路径不带 /v2、域名默认 www.workbuddy.cn,两者都别跟签到混 |
skip · 鉴权失败(MiniMax) | token 与 user_id 有一个不对(401 只看「token 过期」会误判)。用 auth/minimax_auth.py --from-browser 重新导出 |
error + 400 签名无效(MiniMax) | 网页端公共参数/签名算法变了,看 providers/minimax.py 的签名段 |
error + 401 缺少凭据(MiniMax) | token 压根没注入,不是「过期」。见下面「刚配置完就 401」 |
| MiniMax 签到总在 401 但 token 刚抄 | 多半是 user_id 缺失/为 0,不是 token 过期。换 --from-browser(见「401 的三种成因」) |
百度搭子 skip · 登录态失效 | Cookie 过期(常态):开一次 DuMate 客户端重登,再跑 auth/baidu_auth.py 重导。客户端不重登,云端换不了这份 cookie |
| 百度搭子导出时报「读不出 Cookie」 | 先跑 python auth/baidu_auth.py --check:它会把探测过的候选路径列全(哪个目录不存在 / 有 auth.json 但没 .cookie-key / 有文件但解不开)。多数是跨了 Windows 账户,或客户端没真正登录过。--cookie 可手给兜底 |
| 百度搭子「未找到登录态」(新版已修) | 早期按 Electron safeStorage 解、而客户端其实是 .cookie-key + AES-256-GCM(issue #31),密文永远解不开。现按实测契约解,并会打印 解密密钥: ….cookie-key 供确认 |
凭据多久换一次
| 平台 | 到期信号 | 日志里能提前看到吗 |
|---|---|---|
| WorkBuddy | HTTP 401/403 或业务码 1001 | 能:WORKBUDDY_EXPIRES_AT(客户端 expiresAt)→ JWT exp |
| TRAE | 返回 code=1001 | 能:TRAE_EXPIRES_AT(客户端 expiredAt)→ JWT exp |
| Qoder | 活动列表返回 401 | 能:QODER_EXPIRES_AT(登录态 expiresAt)→ JWT exp |
| MiniMax Code | 签到接口 401(user_id 缺失也会 401;脚本先补 user_id、再自动续期) | 不能:_token 不是 JWT,也没有到期字段,不打 |
| 百度搭子 | HTTP 401/403 或业务码 10001/10006(按 skip 收口) | Cookie 里真带 expires 才打(BAIDU_EXPIRES_AT),会话 Cookie 不打 |
每个平台各自打自己登录态里真实的到期时间,几家的天数本来就不一样 —— 这正常。
providers/expiry.py 只做一件事:把凭据里真实存在的那个时刻打出来。
取值的顺序是「客户端自己写的最先,其次才是解 token」,因为客户端那份更贴真实失效点:
- WorkBuddy:
WORKBUDDY_EXPIRES_AT(客户端expiresAt)→ JWTexp;签发时间WORKBUDDY_ISSUED_AT→ JWTiat - TRAE:
TRAE_EXPIRES_AT(客户端expiredAt)→ JWTexp - Qoder:
QODER_EXPIRES_AT(登录态expiresAt)→ JWTexp - 百度搭子:
BAIDU_EXPIRES_AT(Cookie 自己的expires,取最早那个)→ 没有就不打 - MiniMax:
_token不是 JWT、也没到期字段 → 静默(属预期)
auth/*_auth.py 导出凭据时会把这些真实到期时刻一并带走(读不到就不带),
所以云端拿到的就是本机登录态那一刻的真实值,不经过任何推算。
各平台那行由 providers/expiry.py 在每次签到前打印,例如:
WorkBuddy 登录态有效期至 2027-01-16 10:10(签发于 2026-10-13 10:10,已过 0.0 天 / 剩余 95.0 天)
TRAE 登录态有效期至 2026-10-25 10:10(剩余 12.0 天)
Qoder 登录态有效期至 2026-11-12 10:10(剩余 30.0 天)
百度搭子 登录态有效期至 2026-11-02 10:10(剩余 20.0 天)
(上面四行的天数各不相同 —— 这正是「按真实的打印」:有几家就几家,天数互不相干。 数字仅示意形态,实际以当次凭据为准。)
只打真实数据,不推算。有效期与签发时间都来自凭据自身携带的事实; 拿不到任何线索时一行都不打 —— 绝不编数字。
为什么要打「签发于 …,已过 X 天」:只有「剩余 X 天」时,刚换完凭据却看到 剩 27 天会让人怀疑「是不是没换上」;有签发时间就一眼分明 —— 刚换是「签发于 今天」,没换则签发时间不动。
早先版本为了算「已过」,拿一个假设的寿命(
已过 = 55 - 剩余)去反推。 那是编的:寿命是猜的、剩余是算的,两个不确定量一相减,得到的数只对 「寿命恰好 55 天」成立。现在改成认iat;JWT 里没有iat就省掉这半句, 只报真实剩余天数,绝不拿假设凑数(LIFETIME_DAYS这个口子已删除,selftest 盯着)。
不足 7 天时追加一行 ::warning::,日志页会高亮:
::warning::<平台> 登录态剩余不足 7 天(5.2 天),请在电脑上打开客户端刷新后重新导出凭据更新密钥仓库
(哪家不足 7 天就报哪家 —— WorkBuddy / TRAE / Qoder / 百度搭子 只要打得出真实 到期时刻,都会走这条门槛;MiniMax 打不出有效期,自然不会报。)
毫秒戳(13 位)会先归一化成秒,否则会被当成公元 5 万年。这个提示纯属旁路: 字段畸形、缺失都只是不打印,绝不影响签到结果与退出码。
换完凭据想确认「到底换没换」:看日志那行的「签发于」是不是变成了今天。 只重传了密钥文件却忘了客户端重新登录,token 不变,签发时间也不会变。
客户端大版本升级后建议重新取一次 —— 设备指纹和登录态格式都可能变。