这是个什么东西
一个部署在阿里云函数计算上的 Python 脚本,每天早上 9 点自动执行一次,干三件事:
- 调用客户端的签到接口,替你完成当天的签到
- 查询签到结果(连续天数、累计积分、活动信息)
- 把结果推送到你的微信上
跑完之后的日常体验是这样的——每天早上微信收到一条消息:
活动: 每日签到 · 第 7 期
活动截止: 2026-08-24 11:00
获得积分: +100
连续签到: 3 天
累计积分: 300
到此为止。你不需要开电脑、不需要登录客户端、不需要记得任何事。
它解决什么问题
手动签到这件事,单次成本只有三秒,但它是个每天都必须执行的动作,于是真实成本变成了:
- 得记得:出差、休假、忙起来就会忘,而断一天就丢连续记录;
- 得有条件:得打开电脑或手机装着客户端,设备不在身边就没办法;
- 得自己确认:签了之后还得看一眼有没有真的成功。
这个脚本把这三件事全部自动化了:触发不依赖你的设备,执行不需要你的操作,结果主动推给你。而且整套方案的月成本是 0 元——每天 1 次调用,只占函数计算免费额度的 0.003%。
技术选型上为什么用函数计算而不是本地脚本或服务器?一句话:每天只跑 10 秒的任务,不值得为它维护一台机器。函数计算不跑不收费、自带定时触发器(秒级准点)、失败可以接云监控告警,正好卡住所有需求。
使用前你需要准备
三样东西,都是免费获取的:
| 准备项 | 获取方式 | 用途 |
|---|---|---|
| 阿里云账号 | fcnext.console.aliyun.com 注册并实名 | 承载函数计算 |
| Server酱 SendKey | sct.ftqq.com 微信扫码登录 | 微信推送通道 |
| 签到接口的 accessToken | 见下文「获取 accessToken」 | 接口鉴权 |
关于合规先说清楚:这个方案只对你自己的账号生效,用的是你自己客户端的凭据,本质是"让云函数替你点一下签到按钮"。请不要用它做多账号、代刷或其他超出自己账号范围的事,也请遵守你所使用平台的服务条款。
完整代码
整个项目就两个文件。第一个是核心脚本 checkin.py,纯 Python 标准库、零第三方依赖(这一点很重要:不用 pip install,不用 requirements.txt,部署包几 KB,不会有环境不一致的坑):
#!/usr/bin/env python3
"""每日自动签到:查询状态 → 执行签到 → 复查 → 微信通知"""
import json
import os
import sys
import base64
import urllib.request
import urllib.error
import urllib.parse
from pathlib import Path
from datetime import datetime
API_BASE = "https://copilot.tencent.com"
STATUS_URL = f"{API_BASE}/v2/billing/meter/checkin-activity-status" # 查询接口
CLAIM_URL = f"{API_BASE}/v2/billing/meter/daily-checkin" # 签到接口
REQUEST_TIMEOUT = 15
CONFIG_FILE = Path(__file__).resolve().parent / "config.json"
def log(msg):
# 函数计算会自动采集 stdout,print 就是日志
print(f"[{datetime.now():%Y-%m-%d %H:%M:%S}] {msg}", flush=True)
def load_token():
"""读凭证:环境变量(云端推荐)优先,其次 config.json(本地调试用)"""
token = os.environ.get("ACCESS_TOKEN", "").strip()
if token:
return token
if CONFIG_FILE.exists():
cfg = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
token = cfg.get("access_token", "").strip()
return token
def token_remaining_days(token):
"""解析 JWT 的 exp 字段,返回剩余天数(用于到期预警)"""
try:
payload = token.split(".")[1]
payload += "=" * (4 - len(payload) % 4)
exp = json.loads(base64.urlsafe_b64decode(payload)).get("exp", 0)
return int((exp - datetime.now().timestamp()) / 86400) if exp else -1
except Exception:
return -1
def api_post(url, token, body=None):
"""POST JSON。注意:4xx 时业务码在响应体里,必须读出来,否则只剩 Bad Request"""
data = json.dumps(body or {}).encode("utf-8")
req = urllib.request.Request(url, data=data, method="POST", headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
})
try:
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp:
return json.loads(resp.read().decode("utf-8"))
except urllib.error.HTTPError as e:
try:
return json.loads(e.read().decode("utf-8")) # 错误体里往往有业务码
except Exception:
log(f"❌ HTTP {e.code}: {e.reason}")
return None
except Exception as e:
log(f"❌ 请求异常: {e}")
return None
def notify(title, desp):
"""Server酱 微信推送。desp 按 markdown 渲染,字段间必须用空行分隔"""
key = os.environ.get("SERVERCHAN_SENDKEY", "").strip()
if CONFIG_FILE.exists() and not key:
cfg = json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
key = cfg.get("serverchan_sendkey", "").strip()
if not key:
log("ℹ️ 未配置 SendKey,跳过通知")
return
url = f"https://sctapi.ftqq.com/{key}.send"
data = urllib.parse.urlencode({"title": title, "desp": desp}).encode("utf-8")
try:
with urllib.request.urlopen(urllib.request.Request(url, data=data),
timeout=10) as resp:
ok = json.loads(resp.read().decode("utf-8")).get("code") == 0
log("📨 微信通知已发送" if ok else "⚠️ 微信通知失败")
except Exception as e:
log(f"⚠️ 微信通知异常: {e}")
def checkin():
log("=" * 50)
token = load_token()
if not token:
notify("❌ 自动签到失败", "无法获取 accessToken,请检查环境变量或 config.json")
return False
remaining = token_remaining_days(token)
# ① 查状态:今天已签过 → 静默退出(幂等,绝不重复通知)
status = api_post(STATUS_URL, token)
data = status.get("data", {}) if status else {}
if data.get("today_checked_in"):
log("今日已签到,静默退出")
return True
if not data.get("active"):
log("⚠️ 当前无进行中的活动(可能处于活动空窗期)")
# ② 执行签到
result = api_post(CLAIM_URL, token)
if not result or result.get("code") != 0:
code = (result or {}).get("code", "network")
msg = (result or {}).get("msg", "")
log(f"❌ 签到失败: code={code} {msg}")
notify("❌ 自动签到失败", f"错误码: {code}\n\n{msg}\n\n请到 FC 控制台查看日志")
return False
# ③ 复查:签到接口只返回本次结果,累计数据要重新查
credit = result.get("data", {}).get("credit", 0)
streak = result.get("data", {}).get("streak_days", 0)
latest = api_post(STATUS_URL, token)
total = latest.get("data", {}).get("total_credits", 0) if latest else 0
log(f"✅ 签到成功 +{credit},连续 {streak} 天,累计 {total}")
# ④ 通知(字段间用空行分隔,markdown 才会分行)
lines = []
if data.get("theme_name"):
lines.append(f"**活动:** {data['theme_name']} · 第{data.get('season', '')}期")
if data.get("end_time"):
lines.append(f"**活动截止:** {str(data['end_time'])[:16]}")
lines.append(f"**获得积分:** +{credit}")
lines.append(f"**连续签到:** {streak} 天")
lines.append(f"**累计积分:** {total}")
notify("✅ 自动签到成功", "\n\n".join(lines))
# ⑤ 凭证到期预警(提前 7 天)
if 0 < remaining <= 7:
notify("⚠️ 签到凭证即将过期",
f"凭证仅剩 **{remaining} 天**,请到 FC 控制台更新 ACCESS_TOKEN 环境变量")
return True
if __name__ == "__main__":
sys.exit(0 if checkin() else 1)
第二个文件 index.py 是函数计算的入口,只有几行——这样设计的好处是业务脚本在本地可以直接 python3 checkin.py 调试,不用每次部署到云上试错:
# -*- coding: utf-8 -*-
import json
from checkin import checkin
def handler(event, context):
return json.dumps({"success": bool(checkin())}, ensure_ascii=False)
代码里有三处设计值得注意,都是实测踩出来的:
- 幂等三段式(查→做→查):先查状态,签过就静默退出。这样就算定时器重复触发(或者你手动跑了一次又赶上定时触发),微信也只收到一条通知;
- HTTP 4xx 时读响应体:
urllib遇到 4xx 会抛异常,而服务端的业务错误码就在异常携带的响应体里,不读出来你永远只看到 "Bad Request"; - 通知字段用空行分隔:推送详情页按 markdown 渲染,单个
\n会被当成空格,字段会挤成一行。
获取 accessToken
脚本需要客户端自己的 accessToken(一个 JWT,有效期约 60 天)。
macOS 客户端的登录态存在本地文件里,路径是:
~/Library/Application Support/CodeBuddyExtension/Data/Public/auth/workbuddy-desktop.info
打开这个 JSON,取 auth.accessToken 字段的值即可。Windows 用户在对应的 %APPDATA% 目录下找同名文件。
提示:这个文件是客户端的登录凭据,不要提交到任何仓库、不要发给别人。脚本里也做了防护——云端运行时 token 通过函数计算的环境变量注入,不进代码包。
部署到阿里云函数计算(约 10 分钟)
第 1 步:打 ZIP 包
把两个文件打成 ZIP:
zip fc-checkin.zip index.py checkin.py
不需要 requirements.txt——纯标准库,这是这套方案部署简单的原因。
第 2 步:创建函数
进入 FC 控制台 → 左侧「函数」→「函数列表」→「创建函数」:
| 配置项 | 填写值 |
|---|---|
| 函数类型 | 事件函数(⚠️ 不是 Web 函数) |
| 函数名称 | auto-checkin |
| 运行时 | Python 3.10 |
| 代码上传 | ZIP 包 → 选 fc-checkin.zip |
| 处理程序 | index.handler |
| 内存 | 512 MB |
| 执行超时 | 120 秒(默认 60s 偏紧) |
第 3 步:配置环境变量
函数详情 →「配置」→「环境变量」:
| 变量 | 值 |
|---|---|
ACCESS_TOKEN | 上一步拿到的 accessToken |
SERVERCHAN_SENDKEY | Server酱的 SendKey |
TZ | Asia/Shanghai(让日志时间和北京时间一致) |
环境变量的好处是改完保存立即生效——之后 token 续期时,只需要在控制台粘贴新值,不用重新部署。
第 4 步:配定时触发器
函数详情 →「触发器」→「创建触发器」:
| 配置项 | 填写值 |
|---|---|
| 类型 | 定时触发器 |
| Cron | 0 0 9 * * * |
| 时区 | Asia/Shanghai ⚠️ 务必手动改,默认 UTC 会差 8 小时 |
两个新手坑提前打预防针:FC 的 cron 是六位式(秒 分 时 日 月 周,秒在最前面),别按 Linux 的五位习惯写;代码包本身不包含触发器,只上传代码函数不会自己跑。
第 5 步:测试
函数详情 →「测试函数」→ 事件模板选空事件 {} → 点「测试」。日志里看到签到结果、微信收到推送,就是部署成功了。
日常维护:只有一件事
token 有效期约 60 天,到期需要续期。整个流程已经被优化到最短:
- 脚本会自动解析 JWT 有效期,剩 7 天时微信会收到预警——你不会忘;
- 打开一次客户端(它会刷新本地登录态文件),取新的 accessToken;
- FC 控制台 → 环境变量 → 粘贴新 token → 保存。
10 秒,不用重新部署,不用改任何代码。
除此之外没有其他维护项。函数计算每月免费额度对这个任务来说约等于无限,不会产生账单。
一些可选的增强
加一层兜底:如果担心函数计算偶发故障,可以在 GitHub Actions 上留一个错开时间的兜底任务(比如 09:47)。因为脚本有幂等设计,兜底触发时发现"今日已签"会静默退出,不会造成重复通知——两套调度可以安全共存。
通知失败告警:如果希望"推送失败"也能被发现,可以让函数在推送失败时返回 500,然后配置 FC 的云监控告警(函数执行失败 → 通知)。这样即使推送通道本身挂了,也还有一条不依赖它的告警路径。
怎么确认它真的在跑:不用天天翻日志,看通知就行——如果通知连续多天在固定时刻到达,那就是定时器在正常工作。人手点不出这种整齐度,只有定时器能。
最后
这套东西的本质是:把一件"每天都要做但毫无技术含量"的事,交给一个不睡觉、不忘记、不要钱的系统,并让它主动向你汇报。
签到只是这个模式最简单的应用。把 checkin.py 里的两个接口换掉,同样的骨架可以直接套到其他"每日一做"的场景:定时同步数据、每日巡检、定时备份、日报推送。三件套不变——定时触发、幂等执行、结果通知——这三件事想清楚了,任何无人值守任务都能这么搭。
完整代码就在上文,拿去就能用。有问题欢迎评论区交流。