WorkBuddy 的「Buddy加油站」有个每日签到,每天领 100 通用积分。积少成多,对轻度使用的人基本够用。
问题是——它必须手动点。入口在客户端左下角头像 → 领取今日礼包。每天记得点的人不多,我反正经常忘。
于是花了半天,把它变成了每天早上自动执行的一条 HTTP 请求。过程比预想曲折,记录一下。
最自然的想法是 playwright 打开页面点按钮。
查了一圈资料,所有教程都指向同一个入口:客户端内,左下角头像 → 领取今日礼包。网页端 codebuddy.cn/profile/usage 只有一次性新人礼包,没有签到。
客户端是 Electron,界面由服务端下发。我试了几条路:
app.asar(174MB):grep「签到」「加油站」「checkin」,连 Unicode 转义形式都试了,零命中。签到逻辑压根不在本地包里。System Events 直接报 -10004 权限违例;而且每天定时运行时会把客户端窗口抢到前台,体验很差。结论:签到入口在客户端 UI 里,浏览器碰不到,这条路死了。
按钮点不到,但按钮背后一定有个 HTTP 请求。网页端虽然没有签到入口,但它和客户端大概率共用同一套后端和同一份登录态。
那就去网页端抓包。用已登录的浏览器打开 /profile/usage,把所有请求 URL 过滤一遍,看到一个干净的命名空间:
POST /billing/meter/get-user-resource-paid-packages
POST /billing/meter/get-user-resource-free-packages
POST /billing/meter/check-gift-claimed ← 这个有意思
POST /billing/meter/get-user-request-usagecheck-gift-claimed(查询礼包是否已领取)说明「礼包」是一等公民,那签到大概率也在同一个 namespace 下。
接下来就是批量试名字:
for (const n of ['claim-gift', 'claim-daily-gift', 'daily-checkin',
'check-in', 'sign-in', 'get-daily-gift', 'checkin']) {
console.log(n, await call('/billing/meter/' + n));
}结果:
claim-gift 400 每人限领一次,您已领取过无法重复领取
claim-daily-gift 404 page not found
daily-checkin 400 今天已签到,请明天再来 ← 命中
check-in 404 page not found
sign-in 404 page not founddaily-checkin 命中了。「今天已签到」这个报错本身就是最好的成功信号——接口存在、认我的登录态、逻辑正确。
接口通了,接下来自然是写脚本。第一次用 headless: true 跑,直接 401:
401 Authorization Required (openresty / APISIX)抓了下 cookie,登录相关的全没了,只剩统计和路由类。原因是 Chrome 132 之后移除了旧版 headless,playwright 的 headless 模式在 Chrome 155 上会让持久化 profile 的登录态失效。
改法:headless: false + --window-position=-32000,-32000 把窗口丢到屏幕外,既不丢登录态也不打扰人。(最后我用了更好的方案,见下个坑。)
既然客户端已经登录了,能不能直接把它的 cookie 拿来用?
客户端 cookie 存在 ~/.workbuddy/app/session/Cookies,里面确实有 www.codebuddy.cn 的记录。但它是 Electron 加密的,查了钥匙串没有对应的 Safe Storage 项,Chrome 解不开;那条记录本身也只是负载均衡用的 tgw_l7_route,不是登录凭证。localStorage 和 IndexedDB 也翻了一遍,没有可用 token。
所以:必须登录一次,把凭证存下来。
三步,之后全自动。
第一步:扫码登录,导出 cookie
登录页有个小坑——得先勾选同意协议、点「同意」,二维码才会出来。脚本里顺手处理掉:
await page.evaluate(() => {
const cb = document.querySelector('input[type="checkbox"]');
if (cb && !cb.checked) cb.click();
});
await page.evaluate(() => {
const b = [...document.querySelectorAll('button, div, span')]
.find(el => el.textContent.trim() === '同意' && el.getBoundingClientRect().width > 120);
if (b) b.click();
});
// 截图二维码给用户扫;登录成功后导出凭证
const cookies = await ctx.cookies();
fs.writeFileSync('wb-cookies.json', JSON.stringify(cookies, null, 2));第二步:签到脚本,纯 HTTP,不启动浏览器
const cookies = JSON.parse(fs.readFileSync('wb-cookies.json', 'utf8'));
const cookieHeader = cookies.map(c => `${c.name}=${c.value}`).join('; ');
const res = await fetch('https://www.codebuddy.cn/billing/meter/daily-checkin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Cookie: cookieHeader,
Origin: 'https://www.codebuddy.cn',
Referer: 'https://www.codebuddy.cn/profile/usage',
},
body: JSON.stringify({}),
});没有浏览器、没有窗口,一次请求几毫秒完成。
第三步:挂定时任务
每天 09:00 跑一次,结果追加到日志。凭证过期时提示重新扫码即可。
第一次跑通时返回的是「今天已签到,请明天再来」——因为当天早上已经手动领过了。从第二天开始,每天 9 点自动完成签到。
有一点要说明:签到成功的响应里 data 字段是空的,具体到账多少积分、连续多少天,脚本拿不到,只能去客户端里核对。日志里能确认的是「接口返回 200、签到成功」这个状态本身。
整个链路:接口是真实存在的、登录态是自己账号的、脚本跑在本地。没有绕过任何鉴权,只是把一次手工点击换成了一次 HTTP 请求。
方案跑起来不是一劳永逸,这两周的日志里记着三次真实故障:
日期 | 现象 | 原因 | 处理 |
|---|---|---|---|
第 8 天 | 签到返回 401 | 登录凭证过期(实测约 8 天有效期) | 重新扫码登录 |
某天补签 | 二维码生成失败 ERR_PROXY_CONNECTION_FAILED | Chrome 走了系统代理,而代理客户端没开;同一时刻 node 直连接口完全正常 | 修好本机代理再扫码 |
重登当天 | 扫码成功,导出 19 条 cookie,重跑签到返回 200 | — | 之后连续多天凭证复用正常 |
三个附带发现:
另外那个「丢屏外的窗口」方案后来也废弃了——纯 HTTP 请求根本不需要浏览器,第二步的 fetch 脚本才是最终形态。
自动化这类日常操作,本质是在跟产品的「手动才能领」设计较劲。我的原则很简单:只自动化自己账号上本来就有的权益,不碰任何鉴权绕过,不刷、不薅。
签到一天领 100 通用积分,值不了几个钱,但它提醒我一件事——很多「必须手动」的操作,背后其实只是一个接口。找到它,剩下的事就简单了。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。