Files
AgentCheckin 556e45d642 feat(qoder): 弄清国际版每日 100 Credits 的「按设备下发」规则 + 一键设备授权登录
排查结论(本机对真实接口做了 30+ 组对照实验)
- GET /sash/api/v1/me/campaigns 只返回一条常驻 VIEW_DETAILS 广告
  (act-20260901-493,BOGO 订阅推广,claimStatus=CLAIMED 与签到无关)
- 无论 Cosy-MachineToken/Code/Type 用 env 0/3/4/prod、裸调用/带 stdin、
  是否附 Cosy-MachineId/OS/Hostname/Origin/Referer/X-*、是否带 clientType,
  都拿不到 CLAIM_BEFIT 轮次;账号本人 balances/achievements 正常
- 三个社区实现(qoder2api-hub、dsh-connect-qoder、CreditDaddy)一致:
  每日轮次按「登录过官方客户端的那台设备」下发,NAS 侧另造的设备看不到它

修复
- _benefits() 改为只认 actionType=CLAIM_BEFIT(不再把 VIEW_DETAILS 当签到),
  窗口过期也排除;claimStatus 未知按可领处理(领取接口幂等)
- 两段式取活动:先裸取拿 uid → 按 uid 生成风控身份 → 再带设备身份重取
- 状态文案改为「未下发每日签到活动」而不是误导性的「今日已领取」

新功能
- Qoder 一键登录(OAuth 设备授权 / PKCE S256):应用内点按钮 → 浏览器确认一次,
  Token 直接绑定本机设备;同 uid 已存在则更新凭证而不是新增重复账号
- 手工导入桌面端设备身份接口 /api/qoder/import_device
- 诊断接口:/api/qoder/{probe,experiment,growth,matrix,desktop}

测试:39 后端 + 25 E2E + 9 前端全绿
2026-10-19 12:49:06 +08:00

235 lines
13 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Agent 签到助手</title>
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<header class="topbar">
<div class="brand">
<img src="assets/logo.svg" alt="" class="brand-logo">
<div>
<h1>Agent 签到助手</h1>
<p class="sub">TRAE · WorkBuddy 每日积分自动签到</p>
</div>
</div>
<div class="topbar-right">
<span id="daemon-chip" class="chip chip-gray">守护进程 —</span>
<span id="nextrun-chip" class="chip chip-blue">下次签到 —</span>
<button id="btn-run-all" class="btn btn-primary">立即签到</button>
</div>
</header>
<nav class="tabs">
<button class="tab active" data-tab="dashboard">仪表盘</button>
<button class="tab" data-tab="accounts">账号管理</button>
<button class="tab" data-tab="logs">签到记录</button>
<button class="tab" data-tab="settings">设置</button>
</nav>
<main>
<!-- ======================= 仪表盘 ======================= -->
<section id="tab-dashboard" class="tabpage active">
<div id="dash-cards" class="card-grid"></div>
<div class="panel">
<h2>使用说明</h2>
<ul class="hint-list">
<li><b>TRAE</b>(字节跳动·国内版):每日自动领取 <b>200</b> Work 专属积分。</li>
<li><b>WorkBuddy / CodeBuddy</b>(腾讯):7 天循环签到,第 1-6 天每天 100 分,第 7 天 1000 分。</li>
<li><b>Qoder</b>(国内版):每日自动领取 <b>100</b> Credits(活动非零点准时下发,多时段自动补领)。</li>
<li>签到由 NAS 本地守护进程自动执行,失败会在下一个时段自动补签; Token 过期会标记需重新授权。</li>
<li>所有凭据仅保存在本机 <code>@appdata/AgentCheckin</code>,不会上传到任何第三方。</li>
</ul>
</div>
</section>
<!-- ======================= 账号管理 ======================= -->
<section id="tab-accounts" class="tabpage">
<div class="panel">
<div class="panel-head">
<h2>账号列表</h2>
<button class="btn btn-primary" id="btn-add-account">+ 添加账号</button>
</div>
<div id="account-table-wrap"></div>
</div>
</section>
<!-- ======================= 签到记录 ======================= -->
<section id="tab-logs" class="tabpage">
<div class="panel">
<div class="panel-head">
<h2>运行日志</h2>
<div>
<label class="switch-label"><input type="checkbox" id="log-autorefresh" checked> 自动刷新</label>
<button class="btn" id="btn-refresh-logs">刷新</button>
</div>
</div>
<pre id="log-view" class="log-view">加载中…</pre>
</div>
</section>
<!-- ======================= 设置 ======================= -->
<section id="tab-settings" class="tabpage">
<div class="panel">
<h2>签到时间</h2>
<p class="muted">每天在这些时间点自动检查并签到(已签会自动跳过)。错过时段后在守护进程启动时会自动补跑。</p>
<div id="time-chips" class="chips"></div>
<div class="row">
<input type="time" id="new-time" class="input input-time">
<button class="btn" id="btn-add-time">添加时间点</button>
</div>
</div>
<div class="panel">
<h2>Qoder 风控身份(UMID)</h2>
<p class="muted">Qoder 国际版只对带设备风控身份的请求下发签到活动。首次使用需下载官方组件(约 30MB,仅一次)。</p>
<div id="umid-status" class="muted">检测中…</div>
<div class="row">
<button class="btn" id="btn-umid-install">下载 / 重新获取风控组件</button>
</div>
<div id="umid-msg" class="msg"></div>
</div>
<div class="panel">
<h2>消息通知(可选)</h2>
<p class="muted">签到后向 Webhook 发送 POST JSON(<code>{"title","content","text","msgtype"}</code>),兼容企业微信/钉钉/飞书/Bark 等机器人。留空则不通知。</p>
<input type="url" id="webhook-url" class="input input-wide" placeholder="https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=...">
<label style="margin-top:12px">通知平台
<select id="webhook-type" class="input">
<option value="auto">自动识别(推荐)</option>
<option value="wecom">企业微信机器人</option>
<option value="dingtalk">钉钉机器人</option>
<option value="feishu">飞书机器人</option>
<option value="bark">Bark(iOS 推送)</option>
<option value="generic">通用 JSON(Server酱 / PushPlus 等)</option>
</select>
</label>
<div class="row">
<label class="switch-label"><input type="checkbox" id="notify-fail" checked> 失败时通知</label>
<label class="switch-label"><input type="checkbox" id="notify-success"> 成功时也通知</label>
</div>
<div class="row">
<button class="btn btn-primary" id="btn-save-settings">保存设置</button>
<button class="btn" id="btn-test-notify">发送测试通知</button>
</div>
<div id="settings-msg" class="msg"></div>
</div>
</section>
</main>
<!-- ======================= 添加账号弹窗 ======================= -->
<div id="modal-add" class="modal-mask hidden">
<div class="modal">
<div class="modal-head">
<h3>添加账号</h3>
<button class="modal-close" data-close="modal-add">×</button>
</div>
<div class="modal-tabs">
<button class="mtab active" data-mtab="wb-oauth">WorkBuddy 网页授权</button>
<button class="mtab" data-mtab="wb-manual">WorkBuddy 手动</button>
<button class="mtab" data-mtab="trae">TRAE</button>
<button class="mtab" data-mtab="qoder">Qoder</button>
</div>
<div id="mtab-wb-oauth" class="mtabpage active">
<p>点击下方按钮生成授权链接,在浏览器中打开并登录腾讯 CodeBuddy / WorkBuddy 账号,本页面会自动完成绑定。</p>
<button class="btn btn-primary" id="btn-oauth-begin">生成授权链接</button>
<div id="oauth-box" class="hidden">
<p>请在浏览器中打开以下链接完成登录(10 分钟内有效):</p>
<div class="row">
<input id="oauth-url" class="input input-wide" readonly>
<button class="btn" id="btn-oauth-copy">复制</button>
</div>
<p id="oauth-status" class="muted">等待登录…</p>
</div>
</div>
<div id="mtab-wb-manual" class="mtabpage">
<label>名称(可选)<input id="wb-name" class="input input-wide" placeholder="我的 CodeBuddy"></label>
<label>Access Token <span class="req">*</span><input id="wb-token" class="input input-wide" placeholder="accessToken"></label>
<label>Refresh Token(可选,用于自动续期)<input id="wb-refresh" class="input input-wide" placeholder="refreshToken"></label>
<label>区域
<select id="wb-domain" class="input">
<option value="codebuddy.cn">国内版 codebuddy.cn(支持签到)</option>
<option value="workbuddy.ai">国际版 workbuddy.ai(无签到)</option>
</select>
</label>
<button class="btn btn-primary" id="btn-wb-manual-add">添加</button>
</div>
<div id="mtab-trae" class="mtabpage">
<label>名称(可选)<input id="trae-name" class="input input-wide" placeholder="TRAE 主账号"></label>
<label>Access Token <span class="req">*</span><input id="trae-token" class="input input-wide" placeholder="eyJ 开头的 JWT"></label>
<label>Refresh Token(可选)<input id="trae-refresh" class="input input-wide" placeholder="可选"></label>
<details class="help">
<summary>如何获取 TRAE Token?</summary>
<div class="help-body">
<p>在登录了 TRAE 国内版客户端的电脑上,凭据保存在:</p>
<p><code>Windows: %APPDATA%\Trae CN\User\globalStorage\storage.json</code></p>
<p>其中键 <code>iCubeAuthInfo://icube.cloudide</code> 的值为加密串,解密后可得 <code>token</code>(即 Access Token,JWT 格式)。</p>
<p>也可使用社区工具 <code>trae_get_token.py</code>(Trae-AutoCheckin 项目)一键提取。</p>
<p class="muted">Token 约 60 天有效;过期后在本页重新粘贴即可。</p>
</div>
</details>
<button class="btn btn-primary" id="btn-trae-add">添加</button>
</div>
<div id="mtab-qoder" class="mtabpage">
<div class="panel qoder-oauth">
<div class="row">
<button class="btn" id="btn-qoder-login">一键登录(设备授权,推荐)</button>
<span id="qoder-login-status" class="muted"></span>
</div>
<div id="qoder-login-link" class="msg"></div>
<p class="muted">点按钮 → 打开弹出的授权链接 → 在浏览器里确认一次即可自动加入账号。<b>不建议</b>再手工粘贴别处提取的 Token:国际版的「每天领 100 Credits」活动只下发给<b>这次登录的设备</b>,用别的机器提取的 Token 会看不到活动。</p>
</div>
<label>名称(可选)<input id="qoder-name" class="input input-wide" placeholder="Qoder 主账号"></label>
<label>Access Token <span class="req">*</span><input id="qoder-token" class="input input-wide" placeholder="dt- 开头的 JWT"></label>
<label>Refresh Token(可选,用于自动续期)<input id="qoder-refresh" class="input input-wide" placeholder="drt- 开头,可选"></label>
<label>站点区域(重要:两个站点 Token 不互通,选错会 401)
<select id="qoder-domain" class="input">
<option value="openapi.qoder.com.cn">国内版 openapi.qoder.com.cn(qoder.cn 客户端)</option>
<option value="openapi.qoder.sh">国际版 openapi.qoder.sh(qoder.com 客户端)</option>
</select>
</label>
<details class="help" open>
<summary>如何获取 Qoder Token?(三种方式)</summary>
<div class="help-body">
<p><b>方式 A · 一键登录(推荐,无需抓包)</b></p>
<p>直接点上方「一键登录(设备授权)」,在浏览器里确认一次即可。这种方式拿到的 Token 与<b>本机设备绑定</b>,国际版才能看到「每天领 100 Credits」活动。</p>
<p><b>方式 B · 浏览器抓包(最快)</b></p>
<p>浏览器登录 Qoder 网页版 → F12 开发者工具 → 网络 → 点页面里任意功能,任选一个请求 → 在请求头里找到 <code>authorization: Bearer dt-...</code></p>
<p><b>复制技巧</b>:右键该值 →「Copy value」复制完整内容,粘贴后删掉开头的 <code>Bearer </code>。</p>
<p><b>注意</b>:Qoder 的 <code>dt-</code> Token 原生就只有 <b>27/28 个字符</b>(不是截断,这是它的原生格式),<code>drt-</code> 刷新串同理。</p>
<p><b>关键</b>:国内版(qoder.cn)与国际版(qoder.com)的 Token <b>不互通</b>——用哪个客户端登录提的 Token,上方「站点区域」就要选对应的那个,选错会直接 401。拿不准可以先用「测连接」,它会自动探测 Token 属于哪个站点。</p>
<p><b>方式 C · 本机客户端提取</b></p>
<p>在装了并登录 Qoder 客户端的 Windows 上运行社区工具 <code>01_extract.py</code>(GitHub: wallechfox/qoder-checkin),自动解密 <code>auth.v1.dat</code> 取出 Token 和设备标识。</p>
<p class="muted">Token 有效期较短,过期后重新获取即可;填了 Refresh Token 的应用会自动续期。</p>
<p><b>关于风控身份(重要)</b></p>
<p>Qoder 服务端只对携带设备风控身份(<code>Cosy-MachineToken/Code/Type</code>)的请求下发「每天领 100 Credits」活动,否则活动列表为空。</p>
<p>本应用会自动从官方 npm 包 <code>@qoder-ai/qodercli</code> 提取 UMID 组件并生成身份(首次约需下载 30MB,之后缓存复用)。可在「设置 → Qoder 风控身份」查看/重新获取。</p>
</div>
</details>
<details class="help">
<summary>设备标识(可选,领取提示风控时填写)</summary>
<div class="help-body">
<p>Qoder 签到需要 Cosy-* 设备标识。一般情况留空即可;若提示风控/领取失败,用方式 C 的工具提取后填入:</p>
<div class="row"><input id="qoder-machine-id" class="input input-wide" placeholder="Cosy-MachineId"></div>
<div class="row"><input id="qoder-machine-token" class="input input-wide" placeholder="Cosy-MachineToken"></div>
<div class="row"><input id="qoder-machine-code" class="input input-wide" placeholder="Cosy-MachineCode"></div>
<div class="row"><input id="qoder-machine-type" class="input input-wide" placeholder="Cosy-MachineType"></div>
<div class="row"><input id="qoder-version" class="input input-wide" placeholder="Cosy-Version(客户端版本号)"></div>
</div>
</details>
<button class="btn btn-primary" id="btn-qoder-add">添加</button>
</div>
<div id="add-msg" class="msg"></div>
</div>
</div>
<div id="toast" class="toast hidden"></div>
<script src="assets/app.js"></script>
</body>
</html>