自动完成 塔吉多App "异环" 社区的四项每日任务领取金币以及"云异环"时长。
pip3 install --user pycryptodome # 一次性依赖
# 1. 登录: 输入手机号 + 密码 → 自动写好 tjd_config.json (含 token+设备指纹+账密)
python3 tjd_login.py --password
# 2. 跑任务. 之后每天直接跑这一行就行;
# token 几天后过期, daily 会自动用 config 里的账密重登, 你不用管
python3 tjd_daily.py之后想自动化定时,选你顺手的:本地 cron / Docker 常驻 / 青龙面板。
.
├── tjd_daily.py # 日常任务脚本, 纯 stdlib
├── tjd_login.py # 登录脚本 (账密 / 短信), 仅它依赖 pycryptodome
├── tjd_cloud.py # 云异环每日 15 分钟领取 (附加, 被 daily 调用也可独立跑)
├── tjd_config.example.json # 配置模板
├── .env.example # Docker 环境变量模板
├── Dockerfile / docker-compose.yml / .dockerignore
├── .gitignore
└── README.md
真实凭据走
tjd_config.json/.env,不进版本库(.gitignore已拦)。仓库里只有*.example.*模板。 Docker / 青龙用环境变量传凭据,见各自章节。
30 秒上手默认用账号密码登录(tjd_login.py --password),它会一并把账密持久化进 config,后续 token 失效自动续命。这是推荐路径。
下面是三种登录方式的细节,以及没密码 / 不愿存密码时的备选:
| 方式 | 命令 | 自动重登? | 备注 |
|---|---|---|---|
| 账号密码(推荐) | python3 tjd_login.py --password |
✓ | 默认存账密;--no-save-password 关闭 |
| 短信验证码 | python3 tjd_login.py |
✗ | 只存 phone,不存密码 |
| 抓包复制 | Stream / Charles 抓 Authorization 头粘到 tjd_config.json |
✗ | 不需要装 pycryptodome |
前两种共用 tjd_login.py,需要装 pycryptodome(密码 / 手机号 / 验证码都用 AES-128-ECB 加密上传):
pip3 install --user pycryptodomepython3 tjd_login.py --password
请输入手机号 (11 位): 180xxxxx100
请输入登录密码 (输入时不显示):
[1/2] 用账号密码登录 laohu (180****100) ... ✓ 登陆成功
[2/2] 用 laohu token 换 BBS accessToken ... ✓ accessToken=...
✓ tjd_config.json: updated (single account)密码用 getpass 隐式输入,不会进 shell history,也不会出现在日志里。底层走 POST /openApi/secureLogin,跟短信登录共用同一套 sign + AES。
python3 tjd_login.py
请输入手机号 (11 位): 180xxxxx100
[1/3] 发送验证码到 180*****100 ...
✓ 手机短信发送成功
请输入收到的 6 位短信验证码: 499911
[2/3] 用验证码登录 laohu ... ✓ 登陆成功
[3/3] 用 laohu token 换 BBS accessToken ... ✓ accessToken=...
✓ tjd_config.json: updated (single account)跑完直接接 python3 tjd_daily.py 即可。
| 开关 | 用途 |
|---|---|
| 默认 | 短信验证码模式;复用 tjd_config.json 里已有的设备指纹 |
--password |
切到账号密码模式(密码 getpass 隐式输入);默认会把账密一并写入 config,供 tjd_daily.py 自动重登使用 |
--no-save-password |
密码模式下,不把密码写入 config(只存 token) |
--new-device |
强制生成全新设备指纹再登录(注册/登录小号时用,老账号慎用,可能触发风控) |
--print-device |
只打印一份新指纹,不登录 |
--name 小号 |
多账号配置,把这次登录的结果写到指定 name 条目(不存在则追加) |
--phone 180... |
跳过手机号交互输入 |
--no-write |
只打印 token,不写入 config |
把账号密码持久化到 tjd_config.json 之后(tjd_login.py --password 默认会做),tjd_daily.py 跑任务时遇到 HTTP 401 会自动:
- 用配置里的
phone+password重新登录拿新 token - 把新 token 写回
tjd_config.json - 用新 token 重试当前请求
也就是说,只要密码持久化了,Daily/Docker/青龙以后都不需要手工管 token——失效就自重登,任务继续跑。这是密码登录相对短信登录的核心好处。
[relogin] [主号] token 已失效, 用账密重登 180****100 ...
[relogin] ✓ 拿到新 token, 已写回 config
多账号:每号独立 phone/password,失效时只重登失效那个,不影响其他。
⚠️ 安全提醒:password是明文存的。tjd_login.py写入时会自动chmod 600 tjd_config.json缩小本机权限,但这不能替代.gitignore。.gitignore已经把tjd_config.json拦下,永远别-f强加。如果不想留密码,用--no-save-password关闭(代价:每次失效都要手动跑一次tjd_login.py)。
服务端不校验 deviceid 内部结构(抓包显示同一个 32-hex 同时被塞进 deviceId/mac/adid/openudid 都被接受),所以任何 16 字节随机 hex 都能用。tjd_login.py 首次登录时若 config 里没有 deviceid / openudid / vendorid,会自动生成一份并持久化,之后所有请求都用这一份——避免每次登录都被服务端记一台"新设备"。要彻底换设备/做小号时加 --new-device。
30 秒上手是手动跑的形态。无人值守用下面三种:
| 方式 | 适合 | 凭据从哪来 | 跳转 |
|---|---|---|---|
| 本地 cron / launchd | 主机一直开机 | tjd_config.json |
本地定时执行 |
| Docker compose | NAS / 自家服务器 | .env |
Docker 部署 |
| 青龙面板 | 已有面板的人 | 面板的「环境变量」UI | 青龙面板部署 |
三种方式都共用同一个 tjd_daily.py + 同一份算法。强烈建议先在本地用 tjd_login.py --password 登一次拿到 token 和指纹,然后:
- 本地 cron:直接跑就行,
tjd_config.json已就位 - Docker / 青龙:把
tjd_config.json里的 token / deviceid / openudid / vendorid / phone / password 抄到对应的环境变量 /.env里
跑完整流程之外的常用开关,三种部署方式通用:
| 子命令 | 作用 |
|---|---|
--only status |
只显示进度,不动任务 |
--only signin|gamesign|browse|like|share|cloud |
只跑某一项(cloud=云异环领时长) |
--account 主号 |
多账号配置时,只跑指定 name |
--config alt.json |
用其它配置文件 |
--no-notify |
不发青龙通知(本地调试) |
--loop 86400 |
跑完睡 N 秒再循环(Docker 常驻模式默认带) |
| 任务 | 接口 | 备注 |
|---|---|---|
| 签到 (BBS) | POST /apihub/api/signin body communityId=2 |
+40 金币 |
| 游戏签到 | GET /apihub/api/getGameRecordCard?uid=<account_uid> 拿到绑定角色 → GET /apihub/awapi/signin/state?gameId=... 查状态 → POST /apihub/awapi/sign body gameId=...&roleId=... |
自动遍历所有绑定游戏 (异环 等),每月签满拿日历奖励 |
| 浏览 | GET /bbs/api/getRecommendPostList 拉一页 → GET /bbs/api/getPostFull × 5 |
+5 金币,拟人化 0.7-1.5s 间隔 |
| 点赞 | POST /bbs/api/post/like body postId=... × 5 |
+5 金币,自动跳过 selfOperation.liked=true 的帖子,避免取消赞 |
| 分享 | POST /bbs/api/post/share body platform=qq&postId=... |
+20 金币,服务端只看上报,不真发到 QQ |
BBS 任务用原生壳协议(带
ds/deviceid/appversion头),游戏签到走WebView 协议(只用Authorization+Origin: https://webstatic.tajiduo.com,不用ds)。脚本自动选用正确的请求模式。
《异环》还有个云游戏版(云异环,同一个完美世界账号),按"时长(分钟)"计费,每日首次登录自动赠送 15 分钟(一年 ≈ 91 小时)。
| 项 | 说明 |
|---|---|
| 触发机制 | 登录拿 laohu token → 调 POST user.laohu.com/cloud/game/getUserInfo,服务端自动入账 15 分钟(无独立"领取"接口) |
| 零短信 | 云异环自己的登录有首登短信风控,但 laohu token 全局唯一跨 appId——复用塔吉多(已信任设备)的免短信密码登录拿同一个 token,直接调云异环接口绕过 |
| 凭据 | 复用 tjd_config.json 的 phone/password,无需任何新配置 |
| 开关 | 默认开;tjd_config.json 里设 "cloud_duration": false 可关 |
| 跑法 | python3 tjd_daily.py 顺手领;python3 tjd_daily.py --only cloud 只领;python3 tjd_cloud.py 独立领 |
| 云异环每日凌晨 5 点重置,定时必须设在 5 点后(现有 9:30 没问题) |
简报会多一段:云游戏⏱+15 剩659(今日已领则只显示 云游戏⏱剩644)。
密钥(
appId=10597/appKey/channelId=9)从云异环 APK 静态提取并闭环验证;cloud_get_user_info的 sign 同老虎主站算法MD5(参数按 key 排序拼 value + appKey)。
{
"token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=",
"deviceid": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"openudid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"vendorid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"uid": "0",
"phone": "180xxxxx100",
"password": "your-password",
"bark_push": ""
}最小可用配置只需要
token+deviceid。其余字段:openudid/vendorid给自动重登用(没有就空 UUID,可能触发陌生设备风控);phone+password启用 token 失效时自动重登;bark_push启用通知。
{
"bark_push": "https://api.day.app/xxxxxxxxxxxxxxxxxx",
"accounts": [
{
"name": "主号",
"token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=",
"deviceid": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"openudid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"vendorid": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
"uid": "0",
"phone": "180xxxxx100",
"password": "main-account-password"
},
{
"name": "小号",
"token": "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy=",
"deviceid": "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
"openudid": "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY",
"vendorid": "YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY",
"uid": "0",
"phone": "180xxxxx200",
"password": "alt-account-password"
}
]
}脚本会依次跑每个账号,账号之间用 ╔══ 账号: 主号 (1/2) ══ 分隔。--account 主号 可单跑某一个,name 字段未填会自动用 #1 / #2 兜底。bark_push 是所有账号共用的通知出口,放顶层即可;phone + password 每号独立,某号失效只重登该号。
| 字段 | 说明 | 失效特征 |
|---|---|---|
token |
Authorization 头明文,44 字节 base64 |
长有效期但会过期,失效后接口返回 401 / 登录过期 |
deviceid |
32-hex 设备指纹,真机由 LaohuSDK 写入 keychain。没有时 tjd_login.py 自动随机生成并持久化 |
永不变;同账号每次登录建议复用,以免触发陌生设备风控 |
openudid |
UUID,与 deviceid 同源同生命周期 |
同上 |
vendorid |
iOS IDFV;脚本里也用随机 UUID 替代 | 同上 |
uid |
账号 uid,默认 "0" 也接受 |
一般不用改 |
phone |
手机号(11 位),用于自动重登 + 云异环领时长 | 一般不用改 |
password |
登录密码明文,用于自动重登 + 云异环领时长。空 / 不存 = 关闭自动重登和云异环 | 改密码后失效,需要重新跑 tjd_login.py --password |
cloud_duration |
是否领云异环每日 15 分钟,默认 true。设 false 关闭 |
见 云异环每日 15 分钟 |
name |
多账号时的标签,纯输出用 | 任意字符串,可省略 |
bark_push |
Bark 推送地址 / device key,空字符串 = 不推送 | 同名环境变量 BARK_PUSH 优先级更高,适合本地纯 JSON 部署的人 |
设备指纹生成机制详见 登录(获取 token)细节 → 设备指纹机制。
API = 'https://bbs-api.tajiduo.com'
APP_VER = '1.2.2'
SECRET = '<10-char secret>' # 从 Hermes jsbundle LoadConstString 取出
UA = 'HTAssistant/106 CFNetwork/3860.200.71 Darwin/25.1.0'SECRET 与签名拼装方式从 Hermes jsbundle 反编还原,与抓包样本 8/8 完全一致。
每次请求都带一个反重放头:
ds = "<ts>,<nonce8>,<md5(ts + nonce + APP_VER + SECRET)>"
ts:Unix 秒时间戳nonce:8 字符[A-Za-z0-9]随机串APP_VER:'1.2.2'SECRET:JS bundle 中以LoadConstString硬编码的一个 10 字符串
签名输入只有 ts+nonce+ver+secret,不绑定 method / path / body。
Authorization: <token>
appversion: 1.2.2
platform: ios
uid: 0
deviceid: <deviceid>
ds: <ts>,<nonce>,<md5>
User-Agent: HTAssistant/106 CFNetwork/3860.200.71 Darwin/25.1.0
任何任务返回 {"code":401, ...} 或包含 登录过期 字样时:
- 最快:
python3 tjd_login.py --password,详见 登录(获取 token)细节。账号密码两步换新 token,15 秒搞定。 - 没密码:
python3 tjd_login.py(默认短信模式)。等一条 SMS,30 秒搞定。 - 抓包替换:Stream / Charles 抓一次塔吉多 App,把
bbs-api.tajiduo.com任意请求的Authorization头复制到tjd_config.json的token字段;deviceid/openudid/vendorid继续用原值。
文件顶部已带:
"""
new Env('塔吉多每日任务')
cron: 30 9 * * *
"""拉库后会被青龙自动识别,定时与显示名都不用手填。
| 变量 | 必填 | 说明 |
|---|---|---|
TJD_TOKEN |
✓ | Authorization 头明文,多账号用 & 分隔(t1&t2&t3) |
TJD_DEVICEID |
✓ | 32-hex 设备 ID,顺序与 TJD_TOKEN 一一对齐 |
TJD_UID |
默认 0,多账号 & 分隔 |
|
TJD_NAME |
通知/日志里显示的名字,多账号 & 分隔 |
|
TJD_PHONE |
手机号,配合 TJD_PASSWORD 启用 token 失效自动重登 |
|
TJD_PASSWORD |
登录密码,明文 | |
TJD_OPENUDID |
UUID,自动重登需要;不填则空 UUID(可能触发风控) | |
TJD_VENDORID |
UUID,自动重登需要 |
或一次性塞一段 JSON(适合复杂场景):
TJD_ACCOUNTS='[{"name":"主号","token":"...","deviceid":"...","phone":"...","password":"..."},{"name":"小号",...}]'环境变量任一存在即走 env 路径,完全跳过配置文件;两者皆无才报错。
env 模式下的自动重登语义:服务端回 401 时,daily 会用
TJD_PHONE+TJD_PASSWORD现场登录拿新 token,但新 token 不会写盘(env 模式下没 config 可写)——它只缓存在容器进程内存里,跨--loop周期复用。容器重启或一次性docker compose run会丢掉缓存,下次启动用环境变量里的旧 token 触发一次重登,然后照常跑。对常驻容器无感;对一次性容器额外多花几秒登录。
跑完会逐个尝试可用的通知渠道:
| 渠道 | 触发条件 | 备注 |
|---|---|---|
| Bark(iOS 推送) | 设了 BARK_PUSH 环境变量 |
任何环境都能用,脚本内置直推,不依赖青龙 |
| 青龙 notify | 在青龙容器里运行(可 import notify) |
自动覆盖 TG / 微信 / 邮件 / Server 酱等渠道,按青龙的标准变量名配置 |
BARK_PUSH 接两种写法:
BARK_PUSH=xxxxxxxxxxxxxxxxxx # 仅 device key, 默认 server = api.day.app
BARK_PUSH=https://api.day.app/xxxxxxxxxxxxxxxxxx # 完整 URL (自建 Bark server 用这个)简报样例:
[主号] 签到✓1/1 浏览✓5/5 点赞✓5/5 分享✓1/1 异环✓ 金币110/150
[小号] 签到✓1/1 浏览✓5/5 点赞✓5/5 分享✓1/1 异环✓ 金币110/150
加 --no-notify 可关闭所有推送(本地调试用)。
最小映像基于 python:3.12-slim,无三方依赖,构建后约 130 MB(含 tzdata)。
# 1. 复制模板填值
cp .env.example .env
# 编辑 .env, 至少把 TJD_TOKEN / TJD_DEVICEID 替换为真实值
# 2. 起容器
docker compose up -d --build
# 3. 看实时日志 / 简报
docker compose logs -f tjddocker-compose.yml 默认带 command: ["--loop", "86400"],容器启动会立即跑一次,之后每 24 小时一轮,容器一直存活。修复异常后 docker compose restart 即可。
多账号:把 TJD_TOKEN / TJD_DEVICEID 改成 & 分隔多个值即可,无需重建镜像。
TJD_TOKEN='token1&token2'
TJD_DEVICEID='did1&did2'
TJD_NAME='主号&小号'注释掉 restart: unless-stopped 和 command: [...],然后:
30 9 * * * cd /path/to/tjd && /usr/local/bin/docker compose run --rm tjd >> tjd.log 2>&1docker build -t tjd-daily .
docker run --rm \
-e TJD_TOKEN='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=' \
-e TJD_DEVICEID='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' \
-e TJD_NAME='主号' \
-e TJD_PHONE='180xxxxx100' \
-e TJD_PASSWORD='xxxxxxx' \
-e TJD_OPENUDID='XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' \
-e TJD_VENDORID='XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' \
-e TZ=Asia/Shanghai \
tjd-dailydocker run tjd-daily --only status、tjd-daily --only signin 等子命令直接附加在 image 名后面(ENTRYPOINT 已绑定到脚本)。
docker run --rm \
-v /path/to/tjd_config.json:/app/tjd_config.json:ro \
-e TZ=Asia/Shanghai \
tjd-daily30 9 * * * cd /path/to/tjd && /usr/bin/python3 tjd_daily.py >> tjd.log 2>&1把以下内容存到 ~/Library/LaunchAgents/com.tjd.daily.plist,然后 launchctl load 即可。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key> <string>com.tjd.daily</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/python3</string>
<string>/path/to/tjd/tjd_daily.py</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key> <integer>9</integer>
<key>Minute</key> <integer>30</integer>
</dict>
<key>StandardOutPath</key> <string>/path/to/tjd/tjd.log</string>
<key>StandardErrorPath</key><string>/path/to/tjd/tjd.log</string>
</dict>
</plist>COMMUNITY_ID = '2' # 异环社区 id
SHARE_PLATFORM = 'qq' # qq | wechat | timeline | wb
BROWSE_TARGET = 5 # 浏览目标篇数
LIKE_TARGET = 5 # 点赞目标篇数