Post

关于work buddy的自动签到方案研究

WorkBuddy 自动签到实现原理 本文档说明如何在不打开界面、不依赖屏幕点击的前提下,让 WorkBuddy 桌面端每日自动完成「积分签到」。 核心结论:通过逆向桌面 App 的本地明文会话与内部 HTTP 接口,用 Node 脚本直接打签到 API 实现自动打卡,锁屏也能跑。 一、背景与目标

阅读 31 点赞 0 评论 0

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 拿到两样关键东西:

  1. 明文会话文件 —— 登录态以明文 JSON 落盘,含访问令牌
  2. 真实签到接口 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.accessTokenaccount.uidauth.domain。Token 由 App 运行时写入,每日读取即为最新。

3.2 签到打哪个接口

逆向 app.asar 中的 initialize.jsworkbuddy-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来源
AuthorizationBearer <accessToken>auth.accessToken
X-User-Id<uid>account.uid
X-Domain<auth.domain>auth.domain
Content-Typeapplication/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 源码,含会话存储与接口定义

继续阅读

全部归档

评论