feat(qoder): 适配国际版每日签到 — UMID 设备风控身份 + 国际版活动 schema

核心发现(实测):
- Qoder 国际版服务端只对带 Cosy-MachineToken/Code/Type 的请求下发签到活动,
  否则 campaigns 返回空数组(国内版无此限制)
- 国际版活动 actionType=VIEW_DETAILS(国内版 CLAIM_BENEFIT),claimStatus 一致
- 活动窗口 2026-09-01~10-31,每日 10:00 (UTC+8) 刷新,领取后 30 天有效

实现:
- 新增 umid.py:从官方 npm 包 @qoder-ai/qodercli 下载并提取内嵌 UMID 二进制
  (x86_64/arm64 ELF),试跑校验后缓存到 @appdata/AgentCheckin/umid/
- qoder.py:签到前自动确保风控身份,支持国际版/国内版双 schema 活动解析
- API:/api/umid/status、/api/umid/install、/api/accounts/refresh_risk
- 前端:设置页风控身份面板(下载/状态)、账号列表「刷新风控」按钮
- 诊断:测连接返回 risk_identity / campaigns_debug(原始字段)

验证:NAS 实测 Qoder 国际版从「活动列表为空」变为「今日已领取(75 积分)」
测试:37 后端 + 25 E2E + 9 前端,全部通过
This commit is contained in:
cbb
2026-10-19 12:04:44 +08:00
parent a3b84d5ce2
commit c263edc3e7
10 changed files with 484 additions and 17 deletions
+52 -1
View File
@@ -209,7 +209,8 @@
"<td>" + (a.enabled ? "启用" : "禁用") + "</td>" +
'<td class="actions">' +
'<button class="btn btn-sm" data-a2="run" data-id="' + esc(a.id) + '">签到</button>' +
(a.platform === "qoder" ? '<button class="btn btn-sm" data-a2="fixdomain" data-id="' + esc(a.id) + '" title="自动探测 Token 所属站点并切换">修复站点</button>' : "") +
(a.platform === "qoder" ? '<button class="btn btn-sm" data-a2="fixdomain" data-id="' + esc(a.id) + '" title="自动探测 Token 所属站点并切换">修复站点</button>' +
'<button class="btn btn-sm" data-a2="risk" data-id="' + esc(a.id) + '" title="重新生成设备风控身份(活动列表为空时使用)">刷新风控</button>' : "") +
'<button class="btn btn-sm" data-a2="toggle" data-id="' + esc(a.id) + '">' + (a.enabled ? "禁用" : "启用") + "</button>" +
'<button class="btn btn-sm btn-danger" data-a2="del" data-id="' + esc(a.id) + '">删除</button>' +
"</td></tr>";
@@ -219,6 +220,16 @@
btn.addEventListener("click", function () {
var id = btn.dataset.id, act = btn.dataset.a2;
if (act === "run") runCheckin(id, btn);
else if (act === "risk") {
btn.disabled = true; btn.textContent = "生成中…";
api("POST", "/accounts/refresh_risk", { id: id })
.then(function (d) {
if (d.ok) { toast("风控身份已更新", "ok"); refreshState(); }
else { toast(d.error || "生成失败", "err"); }
})
.catch(function (e) { toast(e.message, "err"); })
.finally(function () { btn.disabled = false; btn.textContent = "刷新风控"; });
}
else if (act === "fixdomain") {
btn.disabled = true; btn.textContent = "探测中…";
api("POST", "/accounts/fix_domain", { id: id })
@@ -398,12 +409,52 @@
var settings = null;
var settingsDirty = false;
function loadUmidStatus() {
api("GET", "/umid/status").then(function (d) {
var el = $("umid-status");
if (!el) return;
if (!d.supported) {
el.textContent = "当前 CPU 架构 " + d.cpu_arch + " 暂不支持自动生成(需 x86_64 / arm64)";
} else if (d.installed) {
el.textContent = "✅ 已安装 v" + d.version + "(" + d.arch + ",安装于 " + (d.installedAt || "-") + ")";
} else {
el.textContent = "⚠️ 未安装 —— 点击下方按钮下载(约 30MB)";
}
}).catch(function () {});
}
var umidBtn = $("btn-umid-install");
if (umidBtn) {
umidBtn.addEventListener("click", function () {
umidBtn.disabled = true;
umidBtn.textContent = "下载中(约 1-3 分钟)…";
$("umid-msg").textContent = "";
$("umid-msg").className = "msg";
api("POST", "/umid/install", { force: true })
.then(function (d) {
$("umid-msg").textContent = d.message || (d.ok ? "已安装" : "失败");
$("umid-msg").className = "msg " + (d.ok ? "ok" : "err");
toast(d.ok ? "风控组件已就绪" : (d.message || "下载失败"), d.ok ? "ok" : "err");
loadUmidStatus();
})
.catch(function (e) {
$("umid-msg").textContent = e.message;
$("umid-msg").className = "msg err";
})
.finally(function () {
umidBtn.disabled = false;
umidBtn.textContent = "下载 / 重新获取风控组件";
});
});
}
function openSettingsTab() {
if (!settingsDirty) {
settings = JSON.parse(JSON.stringify(
(state && state.settings) || { schedule_times: [], webhook_url: "", notify_on: [] }));
}
renderSettings();
loadUmidStatus();
}
function renderSettings() {
+12
View File
@@ -81,6 +81,15 @@
<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>
@@ -188,6 +197,9 @@
<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">