WorkBuddy 自动签到实现原理
本文档说明如何在不打开界面、不依赖屏幕点击的前提下,让 WorkBuddy 桌面端每日自动完成「积分签到」。
核心结论:通过逆向桌面 App 的本地明文会话与内部 HTTP 接口,用 Node 脚本直接打签到 API 实现自动打卡,锁屏也能跑。
一、背景与目标
WorkBuddy 的积分签到入口只存在于桌面客户端内部(网页版 https://www.workbuddy.cn/app 没有签到入口),且需要「点击头像 → 进入 Buddy 加油站 → 点立即领取」三步交互。
用户的真实诉求是:人在外地 / 电脑锁屏 / 不在座位时,也能每天自动签到领积分。
这就要求自动签到方案必须满足:
- 不需要界面可见(锁屏、最小化都行)
- 不需要鼠标真的去点(窗口位置变化、样式更新都不能影响)
- 不依赖人工干预
二、方案演进(三步走)
为了达成上面的目标,先后尝试了三条技术路线,前两条最终被否,第三条(HTTP 直打)落地。
方案 A:AutoHotkey 图像匹配点击(GUI 模拟)
思路:用 AutoHotkey v2 的 ImageSearch 在屏幕上匹配三个按钮模板图,依次点击。
- 模板图:
step1_avatar.png(左下角头像)、step2_buddy_station.png(Buddy 加油站)、step3_claim.png(立即领取) - 窗口位置变化不影响定位,因为是基于图像匹配
- 样式更新时,只需替换模板图,不用改代码
致命局限:屏幕模拟点击必须 开机 + 未锁屏 + WorkBuddy 已打开。一旦锁屏,点击直接失败。不满足「锁屏也能签」。
方案 B:Chrome DevTools Protocol(CDP)注入
思路:让 WorkBuddy 以 --remote-debugging-port=9222 启动,用 Node + CDP 连上运行中的 app 进程,执行内部方法 window.wb.account.checkin()。
- 桌面 App 内部确实有
globalThis.wb = sdk全局对象,封装了checkin()方法(返回积分 / 连续天数) - CDP 走本地 socket(127.0.0.1),理论上看不到屏幕也能跑
失败原因:WorkBuddy 在检测到 --remote-debugging-port 后,出于安全加固故意不注册 IPC handler wb:invoke。所有 wb.* 方法都依赖该 handler 转发主进程,结果统一报 No handler registered for 'wb:invoke'。CDP 路线彻底堵死。
排查中还踩了一个坑:Electron 单实例锁导致带端口的新实例被旧实例「秒退」;且任务管理器「进程」页会折叠多进程,需在「详细信息」页才能看到全部
WorkBuddy.exe。
方案 C:HTTP 直打(最终落地方案)✅
思路:既然 wb.* 方法底层也是发 HTTP 请求,那就绕开 App 进程,直接打真实签到接口。
通过解包 D:\WorkBuddy\resources\app.asar 拿到两样关键东西:
- 明文会话文件 —— 登录态以明文 JSON 落盘,含访问令牌
- 真实签到接口 URL 与鉴权头构造方式
结果:脚本读本地明文 token → 拼请求头 → POST 签到接口,完全不依赖界面与调试端口。
三、最终方案原理(方案 C 详解)
3.1 会话从哪来:明文会话文件
WorkBuddy 桌面端使用 FileAuthenticationStorage,会把登录态以明文 JSON 写入磁盘:
C:\Users\A11495\AppData\Local\CodeBuddyExtension\Data\Public\auth\workbuddy-desktop.info
文件结构(关键字段):
{
"account": {
"uid": "3b01ad1d-9c5d-4565-9774-5b97a2db2512"
},
"auth": {
"accessToken": "<Bearer 令牌>",
"domain": "www.codebuddy.cn"
}
}
脚本从中读取三样东西:auth.accessToken、account.uid、auth.domain。Token 由 App 运行时写入,每日读取即为最新。
3.2 签到打哪个接口
逆向 app.asar 中的 initialize.js 与 workbuddy-auth-product-coordinator.js 双重确认:
POST https://www.codebuddy.cn/v2/billing/meter/daily-checkin
注意域名是
www.codebuddy.cn(正确)。codebuddy.cn/workbuddy.cn都会 302 跳转,不能直接用。
3.3 鉴权头怎么构造
对应源码里的 buildAuthHeaders,请求头为:
| Header | 值 | 来源 |
|---|---|---|
Authorization | Bearer <accessToken> | auth.accessToken |
X-User-Id | <uid> | account.uid |
X-Domain | <auth.domain> | auth.domain |
Content-Type | application/json | 固定 |
个人账号没有 enterpriseId,所以不加 X-Enterprise-Id。
3.4 状态查询接口(可选)
GET/POST https://www.codebuddy.cn/v2/billing/meter/checkin-activity-status
返回 today_checked_in(今日是否已签)、today_credit(今日积分)、streak_days(连续天数)。
3.5 成功 / 失败判定
签到接口返回 JSON:
- 成功:
{ "code": 0, "data": { "today_checked_in": true, "today_credit": 10, "streak_days": 5 } } - 重复签到:
{ "code": 10001, "msg": "今天已签到,请明天再来" }
实测脚本返回
code: 10001(当天已签过),证明整套 wiring 是通的——明天 08:30 定时任务触发时即为真实首次签到。
四、核心脚本:checkin_http.js
脚本职责很单纯:读会话 → 拼头 → POST → 写日志。完整源码如下(已脱敏 token):
// WorkBuddy daily checkin via direct HTTP API (no UI, no debug port, works locked-screen).
const fs = require('fs');
const https = require('https');
const path = require('path');
const SESSION_FILE = "C:/Users/A11495/AppData/Local/CodeBuddyExtension/Data/Public/auth/workbuddy-desktop.info";
const ENDPOINT = "https://www.codebuddy.cn";
const LOG_FILE = path.join(__dirname, "checkin_log.txt");
function log(msg) {
const line = "[" + new Date().toISOString() + "] " + msg;
try { fs.appendFileSync(LOG_FILE, line + "\n"); } catch (e) {}
console.log(line);
}
function loadSession() {
const s = JSON.parse(fs.readFileSync(SESSION_FILE, "utf-8"));
return {
token: s.auth && s.auth.accessToken,
uid: s.account && s.account.uid,
domain: s.auth && s.auth.domain
};
}
function postJson(url, headers, body) {
return new Promise((resolve, reject) => {
const u = new URL(url);
const req = https.request({
hostname: u.hostname,
path: u.pathname + u.search,
method: "POST",
headers: headers
}, (res) => {
let d = "";
res.on("data", (c) => d += c);
res.on("end", () => {
let json = null;
try { json = JSON.parse(d); } catch (e) {}
resolve({ status: res.statusCode, json: json, raw: d.slice(0, 1000) });
});
});
req.on("error", reject);
req.setTimeout(10000, () => { req.destroy(); reject(new Error("timeout")); });
req.write(JSON.stringify(body || {}));
req.end();
});
}
async function main() {
let sess;
try {
sess = loadSession();
} catch (e) {
log("ERROR load session: " + e.message);
process.exit(1);
}
if (!sess.token) {
log("ERROR: no accessToken in session file (not logged in?)");
process.exit(1);
}
const headers = {
"Authorization": "Bearer " + sess.token,
"X-User-Id": sess.uid || "",
"X-Domain": sess.domain || "",
"Content-Type": "application/json"
};
log("checkin start uid=" + sess.uid + " domain=" + sess.domain);
try {
const r = await postJson(ENDPOINT + "/v2/billing/meter/daily-checkin", headers, {});
if (r.json && r.json.code === 0) {
const d = r.json.data || {};
log("SUCCESS code=0 today_checked_in=" + d.today_checked_in +
" today_credit=" + d.today_credit +
" streak_days=" + d.streak_days +
" msg=" + (r.json.msg || ""));
} else {
log("UNEXPECTED status=" + r.status + " body=" + (r.raw || "").slice(0, 500));
}
} catch (e) {
log("ERROR request: " + e.message);
process.exit(1);
}
}
main();
日志输出到同目录 checkin_log.txt,格式:
[2026-08-14T05:06:52.905Z] checkin start uid=... domain=www.codebuddy.cn
[2026-08-14T05:06:53.072Z] UNEXPECTED status=400 body={"code":10001,"msg":"今天已签到,请明天再来",...}
五、实现步骤(复现指引)
步骤 1:定位会话文件
确认明文会话文件存在:
C:\Users\<你的用户名>\AppData\Local\CodeBuddyExtension\Data\Public\auth\workbuddy-desktop.info
确认里面有 auth.accessToken 字段(即已登录)。
步骤 2:确认接口与域名
- 接口:
POST https://www.codebuddy.cn/v2/billing/meter/daily-checkin - 域名必须用
www.codebuddy.cn,其余域名会 302
步骤 3:写入脚本
把上面的 checkin_http.js 放到任意目录(如本工作区),按需改 SESSION_FILE 路径里的用户名。
步骤 4:手动验证
node checkin_http.js
首次运行若返回 code: 10001 说明「今天已签」——这正是预期(当天手点过)。真正生效看次日日志。
步骤 5:挂定时任务
用 Windows 任务计划程序创建每日 08:30 任务,动作 = 启动 node,参数 = checkin_http.js 的完整路径。
也可在 PowerShell 中用 Register-ScheduledTask 创建(任务名示例:WorkBuddy Auto Signin)。
若 Node 不在 PATH 里,定时任务里要写 Node 的完整路径(如
C:\Program Files\nodejs\node.exe)。
六、优势与局限
优势
- ✅ 不依赖界面:无需打开 WorkBuddy 窗口,无需鼠标点击
- ✅ 锁屏也能跑:只要 PC 开机、联网、token 有效即可(走本地文件 + HTTPS)
- ✅ 满足「人在外地也自动签」:定时任务在后台静默执行
- ✅ 稳定:不受窗口位置、UI 样式更新影响(方案 A 的痛点)
局限与风险
- ⚠️ 会话明文落盘:token 以明文存在本机,本机自用可接受;多用户/公共机器不推荐
- ⚠️ token 过期:accessToken 失效后需用户重新在 App 内登录一次,脚本才会恢复
- ⚠️ 接口可能变动:App 大版本更新若改了接口路径或存储位置,脚本需重新适配(按本文「逆向思路」再走一遍即可)
- ⚠️ 仅个人账号验证:企业账号可能需额外
X-Enterprise-Id,未覆盖
七、安全说明
- 远程调试端口(
--remote-debugging-port)仅绑127.0.0.1,本机单用户可接受;但最终方案根本没用调试端口,攻击面更小。 - 签到接口为公网 HTTPS 接口,鉴权依赖 Bearer Token,与浏览器登录同源。
- 脚本只做「读本地 token + POST 签到」,不写、不改任何 App 文件,不影响账号安全。
- 所有文件均在用户本机工作区,无外部传输。
八、文件清单
| 文件 | 作用 |
|---|---|
checkin_http.js | 最终方案主脚本(HTTP 直打签到) |
checkin_log.txt | 签到运行日志 |
workbuddy_signin.ahk | 方案 A 兜底脚本(图像点击,已弃用主流程) |
signin_templates/ | 方案 A 的按钮模板图(头像 / 加油站 / 领取) |
launch_workbuddy_debug.bat | 方案 B 的带端口启动器(已弃用) |
checkin_cdp.js | 方案 B 的 CDP 脚本(已弃用) |
asar_out/ | 解包出的 app.asar 源码,含会话存储与接口定义 |
评论