欢迎光临
我们一直在努力

【实战】WorkBuddy 每日自动签到:Python + GitHub Actions 云端免费部署教程

原创不易,觉得有用的话点个赞再走~
适用人群:想给 WorkBuddy 做每日签到、又不想每天开电脑的同学
技术栈:Python3 标准库 + GitHub Actions(免费额度足够)


前言

用 WorkBuddy 一段时间了,每天最稳的积分来源就是「每日签到」。问题也很真实:

  • 必须手动点客户端领取
  • 电脑没开机 / 人一忙就忘
  • 断签直接心疼

网上有人用 Gitee Go、云函数、甚至模拟鼠标点击。我这边最终落地的是:

Python 调官方签到接口 + GitHub Actions 每天定时跑

优点很直接:

  • 不依赖本地电脑是否开机
  • 纯标准库,零第三方依赖
  • GitHub Free 私有仓大约 每月 2000 分钟,这个任务每天 1~2 分钟,完全够用
  • Token 放 Secrets,不写进代码仓库

下面按「能直接复现」的方式写完整流程,包含我踩过的几个坑。


一、整体思路

流程可以概括成三步:

本地取出 accessToken

Python 脚本调用签到接口

GitHub Actions 每天定时执行脚本

核心接口(POST):

  • 查询状态:https://www.codebuddy.cn/v2/billing/meter/checkin-status
  • 领取签到:https://www.codebuddy.cn/v2/billing/meter/daily-checkin

注意:网上有文章写 copilot.tencent.com/billing/meter/…,实测会 404。
当前可用域名是 www.codebuddy.cn,路径带 /v2/。


二、准备工作:拿到 accessToken

WorkBuddy 登录后,本地会保存登录态文件。

Windows

路径:

%LOCALAPPDATA%\\CodeBuddyExtension\\Data\\Public\\auth\\workbuddy-desktop.info

也就是类似:

C:\\Users\\你的用户名\\AppData\\Local\\CodeBuddyExtension\\Data\\Public\\auth\\workbuddy-desktop.info

macOS

路径:

~/Library/Application Support/CodeBuddyExtension/Data/Public/auth/workbuddy-desktop.info

用记事本 / VS Code 打开,结构大概是:

{
"account": {
"uid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"nickname": "你的昵称"
},
"auth": {
"accessToken": "eyJhbGc…",
"domain": "www.codebuddy.cn"
}
}

我们后面要用到:

字段来源是否必须
accessToken auth.accessToken 必须
uid account.uid 建议带上
domain auth.domain 建议,默认 www.codebuddy.cn

⚠️ 安全提醒:accessToken 等同登录态,绝对不要提交到公开仓库,也不要发到群里/博客正文。


三、本地先跑通签到脚本

先保证本地能签到成功,再谈云端。项目结构建议这样:

workbuddy-checkin/
├── checkin.py
├── config.json # 本地用,不要提交
├── config.example.json
├── .gitignore
├── README.md
└── .github/workflows/checkin.yml

1).gitignore

# 含登录态,禁止提交
config.json

__pycache__/
*.py[cod]
.venv/
venv/
.idea/
.vscode/
.DS_Store
Thumbs.db
*.log

2)config.example.json

{
"access_token": "把你的_accessToken_粘贴到这里",
"account_name": "你的昵称",
"uid": "可选,本地 auth 里的 account.uid",
"domain": "www.codebuddy.cn",
"enterprise_id": ""
}

复制一份改名为 config.json,填上真实值。

3)完整脚本 checkin.py

纯 Python 标准库,复制即可用:

#!/usr/bin/env python3
"""
WorkBuddy 每日签到脚本
支持本地运行和 GitHub Actions 云端定时运行
"""

from __future__ import annotations

import json
import os
import sys
import urllib.error
import urllib.request
from datetime import datetime
from pathlib import Path

API_BASE = "https://www.codebuddy.cn"
CHECKIN_STATUS_URL = f"{API_BASE}/v2/billing/meter/checkin-status"
DAILY_CHECKIN_URL = f"{API_BASE}/v2/billing/meter/daily-checkin"
REQUEST_TIMEOUT = 20

_WIN_AUTH_FILE = (
Path(os.environ.get("LOCALAPPDATA", ""))
/ "CodeBuddyExtension"
/ "Data/Public/auth/workbuddy-desktop.info"
)
_MAC_AUTH_FILE = (
Path.home()
/ "Library/Application Support/CodeBuddyExtension"
/ "Data/Public/auth/workbuddy-desktop.info"
)
_CLOUD_CONFIG_FILE = Path(__file__).resolve().parent / "config.json"

def log(msg: str) > None:
line = f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}"
try:
print(line, flush=True)
except UnicodeEncodeError:
print(line.encode("utf-8", errors="replace").decode("utf-8", errors="replace"), flush=True)

def _read_local_auth(auth_file: Path) > dict | None:
if not auth_file.exists():
return None
try:
return json.loads(auth_file.read_text(encoding="utf-8"))
except Exception as e:
log(f"[!] 读取 auth 文件失败 ({auth_file}): {e}")
return None

def _creds_from_env() > dict | None:
token = os.environ.get("WORKBUDDY_ACCESS_TOKEN", "").strip()
if not token:
return None
return {
"access_token": token,
"account_name": os.environ.get("WORKBUDDY_ACCOUNT_NAME", "环境变量账号"),
"uid": os.environ.get("WORKBUDDY_UID", "").strip() or None,
"domain": os.environ.get("WORKBUDDY_DOMAIN", "www.codebuddy.cn").strip(),
"enterprise_id": os.environ.get("WORKBUDDY_ENTERPRISE_ID", "").strip() or None,
}

def _creds_from_config() > dict | None:
if not _CLOUD_CONFIG_FILE.exists():
return None
try:
data = json.loads(_CLOUD_CONFIG_FILE.read_text(encoding="utf-8"))
token = (data.get("access_token") or "").strip()
if not token:
return None
return {
"access_token": token,
"account_name": data.get("account_name") or "云端账号",
"uid": (data.get("uid") or "").strip() or None,
"domain": (data.get("domain") or "www.codebuddy.cn").strip(),
"enterprise_id": (data.get("enterprise_id") or "").strip() or None,
}
except Exception as e:
log(f"[!] 读取 config.json 失败: {e}")
return None

def load_credentials() > dict | None:
# CI 优先读 Secrets 注入的环境变量
if os.environ.get("GITHUB_ACTIONS") or os.environ.get("CI"):
creds = _creds_from_env()
if creds:
log(f" 使用环境变量 (账号: {creds['account_name']})")
return creds

creds = _creds_from_config()
if creds:
log(f" 使用 config.json (账号: {creds['account_name']})")
return creds

creds = _creds_from_env()
if creds:
log(f" 使用环境变量 (账号: {creds['account_name']})")
return creds

for auth_file, label in ((_WIN_AUTH_FILE, "Windows"), (_MAC_AUTH_FILE, "macOS")):
data = _read_local_auth(auth_file)
if not data:
continue
token = (data.get("auth", {}).get("accessToken") or "").strip()
if not token:
log(f"[x] {label} auth 中未找到 accessToken")
continue
account = data.get("account") or {}
creds = {
"access_token": token,
"account_name": account.get("nickname") or f"{label}账号",
"uid": (account.get("uid") or "").strip() or None,
"domain": (data.get("auth", {}).get("domain") or "www.codebuddy.cn").strip(),
"enterprise_id": (
(account.get("enterpriseId") or account.get("enterprise_id") or "").strip() or None
),
}
log(f" 使用 {label} 本地 auth (账号: {creds['account_name']})")
return creds

log("[x] 未找到任何 token 来源,请配置 config.json 或环境变量")
return None

def _build_headers(creds: dict) > dict[str, str]:
headers = {
"Authorization": f"Bearer {creds['access_token']}",
"Accept": "application/json",
"Content-Type": "application/json",
"User-Agent": "WorkBuddy-Checkin/1.1",
}
if creds.get("uid"):
headers["X-User-Id"] = creds["uid"]
if creds.get("domain"):
headers["X-Domain"] = creds["domain"]
eid = creds.get("enterprise_id")
if eid:
headers["X-Enterprise-Id"] = eid
headers["X-Tenant-Id"] = eid
return headers

def _request_json(url: str, creds: dict, method: str = "POST") > dict | None:
req = urllib.request.Request(
url,
data=b"{}",
method=method.upper(),
headers=_build_headers(creds),
)
try:
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp:
raw = resp.read().decode("utf-8")
return json.loads(raw) if raw else {}
except urllib.error.HTTPError as e:
err_body = ""
try:
err_body = e.read().decode("utf-8", errors="replace")
except Exception:
pass
log(f" HTTP {e.code}: {e.reason} {err_body[:300]}")
try:
return json.loads(err_body) if err_body else None
except Exception:
return None
except Exception as e:
log(f" 请求失败: {e}")
return None

def _msg_of(payload: dict | None) > str:
if not isinstance(payload, dict):
return ""
return str(payload.get("message") or payload.get("msg") or "")

def already_checked_in(payload: dict | None) > bool:
if not isinstance(payload, dict):
return False
code = payload.get("code")
msg = _msg_of(payload)
if code == 10001 or "已签到" in msg or "已经签到" in msg:
return True
data = payload.get("data")
if isinstance(data, dict) and (data.get("today_checked_in") or data.get("checked_in")):
return True
return bool(payload.get("today_checked_in") or payload.get("checked_in"))

def unwrap_data(payload: dict | None) > dict | None:
if not isinstance(payload, dict):
return None
code = payload.get("code")
if code is not None and code not in (0, 200):
msg = _msg_of(payload) or "unknown"
log(f" 业务错误 code={code}: {msg}")
return None
data = payload.get("data")
return data if isinstance(data, dict) else payload

def checkin() > bool:
log("=" * 56)
log(f" WorkBuddy 每日签到 — {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
log("=" * 56)

log("步骤 1/3: 获取凭证…")
creds = load_credentials()
if not creds:
return False

log("步骤 2/3: 查询签到状态…")
status_raw = _request_json(CHECKIN_STATUS_URL, creds, method="POST")
if already_checked_in(status_raw):
log("[ok] 今日已签到,无需重复领取。")
return True
status = unwrap_data(status_raw)
if status is None:
log("[!] 签到状态查询失败,继续尝试领取…")
else:
log(
f" active={status.get('active')}, "
f"today_checked_in={status.get('today_checked_in')}, "
f"streak_days={status.get('streak_days')}"
)

log("步骤 3/3: 领取签到积分…")
result_raw = _request_json(DAILY_CHECKIN_URL, creds, method="POST")
if already_checked_in(result_raw):
log("[ok] 今日已签到。")
return True

result = unwrap_data(result_raw)
if result is None:
log("[x] 签到失败,请检查 token 是否过期。")
return False

success = result.get("success", True)
credit = result.get("credit", result.get("today_credit", result.get("points")))
streak = result.get("streak_days")
message = result.get("message") or ""
if success is False:
log(f"[!] 领取未成功: {message or result}")
return False

log(f"[ok] 签到成功! credit={credit}, streak_days={streak} {message}")
return True

if __name__ == "__main__":
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
sys.exit(0 if checkin() else 1)

4)本地测试

python checkin.py

成功时大致会看到:

步骤 1/3: 获取凭证…
步骤 2/3: 查询签到状态…
步骤 3/3: 领取签到积分…
[ok] 签到成功! credit=xxx …

如果今天已经签过,接口可能返回类似:

HTTP 400 … {"code":10001,"msg":"今天已签到,请明天再来"}
[ok] 今日已签到。

脚本会把「已签到」也当成成功,避免 Actions 误报失败。


四、部署到 GitHub Actions(重点)

1)创建私有仓库

登录 GitHub,新建仓库:

  • 名称例如:workbuddy-checkin
  • 建议选 Private
  • 不要勾选自动生成 README(避免首次推送冲突)

2)写 Actions 工作流

创建文件:.github/workflows/checkin.yml

name: WorkBuddy Daily Checkin

on:
schedule:
# 每天北京时间 09:05 = UTC 01:05
cron: "5 1 * * *"
workflow_dispatch:

jobs:
checkin:
runs-on: ubuntulatest
timeout-minutes: 5
steps:
name: Checkout
uses: actions/checkout@v4

name: Setup Python
uses: actions/setuppython@v5
with:
python-version: "3.11"

name: Run checkin
env:
WORKBUDDY_ACCESS_TOKEN: ${{ secrets.WORKBUDDY_ACCESS_TOKEN }}
WORKBUDDY_ACCOUNT_NAME: ${{ secrets.WORKBUDDY_ACCOUNT_NAME }}
WORKBUDDY_UID: ${{ secrets.WORKBUDDY_UID }}
WORKBUDDY_DOMAIN: ${{ secrets.WORKBUDDY_DOMAIN }}
WORKBUDDY_ENTERPRISE_ID: ${{ secrets.WORKBUDDY_ENTERPRISE_ID }}
run: python checkin.py

说明两点:

  • GitHub Actions 的 cron 用的是 UTC
    北京时间 09:05 → 5 1 * * *
  • workflow_dispatch 方便你在网页上点「手动执行」调试
  • 3)推送代码(不要推 config.json)

    git init
    git add .gitignore README.md checkin.py config.example.json .github/workflows/checkin.yml
    git commit -m "init: WorkBuddy GitHub Actions 每日签到"
    git branch -M main
    git remote add origin https://github.com/你的用户名/workbuddy-checkin.git
    git push -u origin main

    推送包含 .github/workflows/*.yml 时,Classic PAT 需要同时勾选:

    • repo
    • workflow
      少勾 workflow 会报:refusing to allow a Personal Access Token to create or update workflow

    4)配置 Secrets(别把 token 写进代码)

    仓库页面路径:

    Settings → Secrets and variables → Actions → New repository secret

    建议配置:

    Secret 名称必填值从哪来
    WORKBUDDY_ACCESS_TOKEN auth.accessToken
    WORKBUDDY_UID 建议 account.uid
    WORKBUDDY_ACCOUNT_NAME 昵称,仅日志展示
    WORKBUDDY_DOMAIN 默认 www.codebuddy.cn
    WORKBUDDY_ENTERPRISE_ID 企业账号才需要

    5)手动跑一次验证

  • 打开仓库 Actions
  • 左侧选 WorkBuddy Daily Checkin
  • 点 Run workflow
  • 等跑完看日志
  • 成功标志:

    • Job 显示绿色 success
    • 日志里有 [ok] 签到成功 或 [ok] 今日已签到

    之后就会每天北京时间 09:05 自动跑,电脑关着也没关系。


    五、方案对比(为什么不选 Gitee Go)

    方案优点缺点
    本地计划任务 / 模拟点击 配置快 依赖电脑开机,分辨率/窗口还容易翻车
    Gitee Go 国内访问稳 免费时长偏紧,体验额度不够长期用
    云函数 定时准 配置相对重,有的还有费用门槛
    GitHub Actions 免费额度够、文档全、Secrets 好用 偶发网络波动,私有仓吃月度分钟数

    按「每天跑一次短脚本」这个场景,GitHub Actions 性价比最高。


    六、踩坑记录(建议收藏)

    坑 1:接口地址写错

    旧文常见地址:

    https://copilot.tencent.com/billing/meter/daily-checkin

    实测 404。请用:

    https://www.codebuddy.cn/v2/billing/meter/daily-checkin

    并且是 POST,不是 GET。

    坑 2:状态字段和领取结果不一致

    有时 checkin-status 里 today_checked_in=false,但领取接口返回:

    {"code":10001,"msg":"今天已签到,请明天再来"}

    所以脚本里要把 code=10001 / 文案含「已签到」也判定为成功,否则 Actions 会红叉。

    坑 3:Token 权限不够推 workflow

    Fine-grained token 很容易漏权限。个人自动化更建议用 Classic PAT:

    • 勾选 repo
    • 勾选 workflow

    坑 4:把 config.json 推到仓库

    私有仓也别养成这个习惯。正确做法是:

    • 本地用 config.json
    • 云端用 GitHub Secrets

    坑 5:Token 过期

    accessToken 会过期。过期后:

  • 重新打开本地 auth 文件
  • 复制新的 accessToken
  • 更新 GitHub Secret:WORKBUDDY_ACCESS_TOKEN
  • 不用改代码,不用重新部署。


    七、维护成本总结

    跑通以后,日常几乎不用管:

    • 每天自动签到
    • 想看结果就去 Actions 日志
    • Token 过期改一个 Secret

    适合那种「功能很小、但忘了就烦」的自动化需求。同思路也可以迁移到别的「每日打卡 / 每日领取」类接口脚本上。


    有问题欢迎评论区留言,我看到会回。


    赞(0)
    未经允许不得转载:171主机测评 » 【实战】WorkBuddy 每日自动签到:Python + GitHub Actions 云端免费部署教程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址