Files
ql-scripts/qqmusic/README.md
zhuhjay 21e4a7a3ae docs(qqmusic): README 补通知分组与执行日志章节
通知样例换成新的分组格式(📈 任务中心 / 🎁 会员福利站 / 💰 金币中心 /
🎫 VIP 体验券),并加分组规则表与「新增任务要补 step() 且用两段式名」的提醒。

新增「执行日志」章节:四段分隔标题的样例输出,说明末尾两行为什么要有第二行
(光看成长值合计看不出金币跑没跑),以及金币段开头打 ActID 的用途。

本地测试清单补 test_coin.js / test_render.js。
2026-11-07 18:59:00 +08:00

461 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# QQ音乐自动签到(scrip_qqmusic_checkin.js)
QQ音乐会员成长值任务 + 会员福利站 自动签到,青龙面板 Node 零依赖。
协议底座与 [LDP924/auto-checkin](https://github.com/LDP924/auto-checkin)(Python)逐字节同源:
`u6.y.qq.com/cgi-bin/musics.fcg` 网关 + `zzc` 签名(sha1 重组 + 置乱表 + b64 去尾)+ `qm_keyst` cookie。
> **文件名必须带 `scrip_` 前缀**:本仓库的青龙订阅规则白名单是 `scrip_`,
> 不带前缀的文件不会被订阅拉取(脚本、测试、文档同一约定)。
## 环境变量
| 变量 | 必填 | 说明 |
|---|---|---|
| `SH_QQMUSIC_KEY` | ✅ | 多账号:青龙同名 env 加多条(注入自动 `&` 连接)。**单条格式 `<uin>#<qm_keyst>`**,如 `123456789#Q_H_L_xxxx`。也接受整串 cookie(含 `qm_keyst=` 与 `uin=o...`) |
| `QM_KEY` / `QM_UIN` | 备选 | 单账号兼容形态(`SH_QQMUSIC_KEY` 优先) |
| `QM_CACHE_DIR` | 可选 | key 缓存目录,默认 `/ql/data/qqmusic_cache`(青龙)/ `qqmusic/.cache`(本地) |
| `QM_PLAY_REPORT` | 可选 | `on` 才启用任务20播放上报(本版未实现模板,挂了开关占位) |
| `QM_SHARE_CARD` | 可选 | `on` 才让**定时任务**自动建卡。默认 off = 只读报数。手动取卡走 `-- card`,不受此开关限制 |
| `QM_CARD` | 可选 | `1`/`on` 等价于 `-- card`(不方便传参时用) |
| `QM_COIN_SIGN` | 可选 | 金币中心签到开关,**默认 on**。`off` 整体跳过(纯签到+领奖,不花金币、不伪造行为) |
| `QM_COIN_ACT_ID` | 可选 | 金币签到 ActID,默认 `Z25hHGi`。**活动换季会变**,失效时抓包改这里 |
| `QM_COIN_SCENE_ID` | 可选 | 金币签到 ScenesID,默认 `2` |
| `QM_COIN_LOTTERY_ACT_ID` | 可选 | 金币抽奖签到 ActID,默认 `Z156KEu`,同样会换季 |
| `WEBHOOK_URL` | 可选 | 自定义 webhook(如平台 `/api/hooks/ql`)。与 `PUSH_PLUS_TOKEN` 至少配一个 |
| `WEBHOOK_METHOD` | 可选 | 默认 `POST` |
| `WEBHOOK_CONTENT_TYPE` | 可选 | 默认 `application/json` |
| `WEBHOOK_BODY` | 可选 | 支持 `$title` / `$content` 占位;不填则按 Content-Type 给默认形状 |
| `WEBHOOK_HEADERS` | 可选 | 一行一个 `Key: Value`,**必须是真换行** |
| `PUSH_PLUS_TOKEN` | 可选 | pushplus 直发 |
| `PUSH_PLUS_TOPIC` | 可选 | pushplus 一对多群组编码(对应 `PUSH_PLUS_USER`) |
| `PUSH_PLUS_TEMPLATE` | 可选 | 默认 `txt`(不转义 html、不折叠);`html` 会把换行变 `<br>` |
> 通知由本脚本**自己实现**(`notify()`),不依赖青龙的 `sendNotify.js` ——
> 那份文件每轮都会被 `/ql/data/deps/*` 覆盖,改它不生效。详见「通知发不出去怎么查 · 坑 0」。
## VIP 体验券(分享卡)
`music.vip.VipFreeCardSvr`,2026-11-04 从体验券落地页
`y.qq.com/m/vip/experience_card/dist/index.html` 的 `index.036b2e716.js` 逆出。
接口在 u6 网关实测路由通(`GetCanNumConf` 用假 key 也返回真实配额表,说明该 method 不校验登录;
`CreatCard` / `CheckCurrentCardList` 返回 `1000`=未登录)。
### 这卡是什么
有效期内的豪华绿钻会员,每 31 天可按等级**赠送给不同的非会员好友** 3 天体验券:
| 等级 | LV1 | LV2 | LV3 | LV4 | LV5 | LV6 | LV7 | LV8 |
|---|---|---|---|---|---|---|---|---|
| 张数 | 1 | 1 | 2 | 3 | 3 | 5 | 5 | 7 |
(实测配额表还有 LV9=10、LV10=15)
**卡只能送人,自己领不了自己的**(`CheckCard` 的 `ownerUin` 判定)。
好友领取限制:非会员自然月内限领 1 张,体验券仅周期内有效、不可累计。
### 三个容易混淆的数
落地页上这三个数不是一回事,脚本输出里也分开报:
| 字段 | 含义 |
|---|---|
| `sum` | **本月配额**(你这个等级这个月一共能送几张) |
| `reciveUinList.length` | **本月已被好友领走的张数** |
| `cardInfo.left` | **单张卡还能被领几次**(一张卡可以被多个人领,每次 -1) |
「还能送几张」= `sum - reciveUinList.length`。
`left` 是另一维度:它只说明这张卡还剩几次可被领,**建卡本身就已经占掉一张配额**。
### 两种模式
**① 手动取卡(你要的场景)**——青龙里点一次「运行」就吐链接:
```
task qqmusic/scrip_qqmusic_checkin.js -- card
```
或者环境变量 `QM_CARD=1`。
行为:**先扫本月已有的卡,有 `cardLeft>0` 的直接复用**(照抄落地页的
`createCard(d ? cardId : "")` 逻辑),只有卡都领完了才 `CreatCard` 建新的。
不会为了取一张卡把整个签到流程再跑一遍。
输出:
```
===== 取卡 账号 123456789 =====
[分享卡] 本月配额 5 张,已被领 1 张,还能送 4 张
[分享卡] 复用本月未领完的卡:还能被领 3 次(不新建、不占新配额)
[分享卡] 分享链接:https://y.qq.com/m/vip/experience_card/dist/index.html?cardid=<cardID>
👉 复制这条链接发给对方:
https://y.qq.com/m/vip/experience_card/dist/index.html?cardid=<cardID>
```
**② 定时任务路径(默认只读)**——每天签到顺带报个数,不建卡:
```
[分享卡] 本月配额 5 张,已被领 2 张,还能送 3 张
[分享卡] 只读模式(QM_SHARE_CARD 未置 on),不创建新卡
```
要让定时任务也自动建卡(不建议,会堆废卡)才设 `QM_SHARE_CARD=on`。
### 接口清单
| method | 作用 | 需要登录 |
|---|---|---|
| `GetCanNumConf` | 全等级配额表 `{等级: 张数}` | ❌ |
| `CheckCurrentCardList` | 本月卡列表 + 已被领 uin 列表 | ✅ |
| `CheckCard` | 查某张卡详情 `{cardID}` → `cardInfo.left` / `ownerUin` | ✅ |
| `CreatCard` | 建卡 `{source: 0}` → `cardID` + `cardInfo.left` | ✅ |
| `GetCard` | **好友领取**用 `{cardID, source}` | ✅ |
`source` 是落地页 ADTAG 映射:`xqy`=1 / `cdy`=2 / `privilege`=3 / 其他=0。
错误码:`100205`=当月已领取过 · `100210`=体验券已被领取 · `100211`=领取失败。
## 凭证获取(web 抓一次,之后无限续)
1. 浏览器打开 [y.qq.com](https://y.qq.com) 登录(QQ/微信均可)
2. F12 → Application → Cookies → `https://y.qq.com` → 复制 `qm_keyst` 与 `uin`(`o123456789` 去掉 `o`)
3. 青龙新建环境变量 `SH_QQMUSIC_KEY`,值 `uin#qm_keyst`
**为什么抓一次就够**:签名/`g_tk` 全是本地算法(无盐、无设备绑定、无动态 token);
`qm_keyst` 名义 72h,但续期接口 `QQConnectLogin.LoginServer.QQLogin` 旧 key 不吊销——
脚本每次运行先续期,新 key 写缓存滚动,环境变量永远不用再动。
只有脚本断跑超 72h 且缓存丢失(推送出现 🔴 2000)才需要重新抓。
## 缓存与订阅安全
续期产物存 `/ql/data/qqmusic_cache/qm_<uin>.json`(**订阅仓库之外**)。
git 的 reset/clean 只动 tracked 文件,untracked 目录在 `git pull` 下天然安全;
且脚本读缓存优先、env 兜底,缓存丢失仅退化为"72h 内重新抓一次"。
## 定时
```
cron: 1 0,6 * * *
```
每天 00:01 + 06:02 双轮:任务20(收听节目)跨天滚动翻账本 + 福利站 0 点翻新兜底。
### 青龙自动建任务的元数据约定(踩坑记录 2026-11-04)
订阅勾了「自动添加定时任务」时,青龙从**脚本内容**里正则提取任务名与 cron,两个坑:
| 项 | 青龙的提取规则 | 必须怎么写 |
|---|---|---|
| **任务名** | `/name:\s*(.+)/`(**不锚定行首**),取**全文第一个**命中 | 脚本第 3 行写 `* name: QQ音乐自动签到`。**全文任何位置都不能再出现「name+冒号」的连写**——`https.request({ hostname: u.hostname })` 会被抓成任务名 `u.hostname,`(真机已复现) |
| **cron** | `/\bcron:\s*([^\r\n]+)/`(**必须带冒号**) | 写 `* cron: 1 0,6 * * *`,且**独占一行、后面不跟注释**(同一行剩余文字会被一起吞进 schedule) |
两个都抓不到时的兜底:任务名 = 文件名 `scrip_qqmusic_checkin.js`;cron = 随机 `M H * * *`。
所以脚本里:
- `https.request` 的选项用 `host:`(Node 的 `hostname` 别名)而不是 `hostname:`
- cron 注解独占一行
## 通知内容
每轮跑完推送一条。**按体系分组**,每条都带子项名,一眼看出是什么签到完成了:
```
✅ QQ音乐签到 · 全部完成
🟢 123456789:成长值 +45
📈 任务中心(成长值)
✓ 签到 +15
✓ 挂件 已佩戴
✓ 节目 计数 3
· 无需处理 2 项:装扮、听书
🎁 会员福利站(成长值 + 权益)
✓ 签到 15成长值(本月 8 天)
✓ 礼券 +10 心愿值
· 无需处理 4 项:旅程激励、月里程碑、月礼、臻享抽奖
💰 金币中心(金币,不计成长值)
✓ 签到 已签 + 30 金币
✓ 抽奖签到 已签 + 已领
🎫 VIP 体验券
✓ 分享卡 新建 1 张,还能被领 3 次
🔗 领卡链接 https://y.qq.com/m/vip/experience_card/dist/index.html?cardid=xxx
———
轮次: 00:01 / 06:02 双轮(任务20跨天滚动 + 福利站兜底)
节目任务(20)依赖 App 内正常收听计入(播放上报未启用)
时间: 2026/11/7 00:01:24
```
**标题就是结论**:`✅ 全部完成` / `⚠️ 有未完成项`,不用点开就知道要不要管。
### 分组规则
| 组 | emoji | 说明 |
|---|---|---|
| 任务中心 | 📈 | 成长值,`music.lvz.*` |
| 会员福利站 | 🎁 | 成长值 + 权益,`music.vipcgi.*` |
| 金币中心 | 💰 | **金币,不计成长值**,`music.actCenter.*` |
| VIP 体验券 | 🎫 | 分享卡 |
| 其他 | 📌 | 兜底,未知前缀落这里 |
- 组内每条都是「**子项名 + detail**」,不会出现只有 detail 的行
- **整组全 skip 时收成一行**:`💰 金币中心(金币,不计成长值):签到、抽奖签到 今日无需处理`
- 金币组标题固定带「不计成长值」,避免 `+30 金币` 被读成成长值
### 符号
| 符号 | 含义 |
|---|---|
| `✓` | 做到了(带结果,如 `+15`、`已佩戴`) |
| `△` | 部分完成 / 留待下次(如任务20未计次、写锁、ActID 过期) |
| `✗` | 失败(接口报错、code 非预期) |
| `·` | 无需处理(今日已完成 / 未开放),**折叠成一行**,不占版面 |
判定规则:有任何 `✗` → 标题变 `⚠️`;只有 `△` → 仍算完成(有遗留但不影响);
全 `✓`/`·` → `✅`。登录态失效单独用 🔴 并直接给处理指引,不进明细列表。
明细来源是 `QMClient.steps`:每个动作调一次 `step(name, status, detail)`,
`verdict()` 汇总。**新增任务时记得补一句 `step()`,且 name 用「体系·子项」两段式**,
否则它在通知里会被归到「其他」组。
## 执行日志
日志前缀同样是「体系·子项」两段,`run()` 里还有四段分隔标题:
```
───── ① 任务中心(成长值)─────
[任务中心·签到] 签到成功 成长值+15
[任务中心·挂件] UsePendant retCode:0
[任务中心·节目] 开始标记已挂(播放上报未启用,App 内正常收听计入)
───── ② 会员福利站(成长值 + 权益)─────
[福利站·签到] 签到成功:15成长值(本月 9 天)
[福利站·礼券] 某券 领取成功 +10心愿值
───── ③ 金币中心(金币,不计入成长值)─────
[金币·签到] 开始:ActID=Z25hHGi ScenesID=2
[金币·签到] 签到并领取成功:30 金币
[金币·抽奖签到] 开始:ActID=Z156KEu
[金币·抽奖签到] 今日已签
───── ④ VIP 体验券(分享卡)─────
[分享卡] 只读模式(QM_SHARE_CARD 未置 on),不创建新卡
[QQ音乐] 本次新增成长值合计: 45
[QQ音乐] 金币中心为独立体系,不产成长值(明细见上方 ③ 段)
```
**最后两行是关键**:光看「成长值合计: 45」不知道金币那部分跑没跑,所以补了第二行说明。
金币段开头会打 `ActID=...`,换季排查时直接能看到脚本用的是哪个 ID。
## 通知发不出去怎么查
### 坑 0(最狠的一个):仓库的 `sendNotify.js` 每轮都被覆盖
**症状**:改了 `sendNotify.js`,青龙跑起来行为不变;日志里永远是旧版那句话。
**原因**:青龙 `shell/update.sh` 的 `gen_list_repo` 里,文件复制是**三步**,后一步覆盖前一步:
```bash
407: cp -f $file_notify_js "${dir_scripts}/${uniq_path}" # ① 面板自带的 sendNotify.js
408: cp -f $file_notify_py "${dir_scripts}/${uniq_path}"
410: if [[ $dependence ]]; then
411: results=$(find . -name "*.js" | egrep "$dependence")
415: cp -f $_file "${dir_scripts}/${uniq_path}/${file_path}" # ② 仓库的 sendNotify.js(覆盖①)
417: fi
420: if [[ -d $dir_dep ]]; then
421: cp -rf $dir_dep/* "${dir_scripts}/${uniq_path}" &>/dev/null # ③ /ql/data/deps/*(再覆盖②)
422: fi
```
`$dir_dep` 是 `/ql/data/deps`,青龙初始化时往里面拷了一份面板自带的 `notify.js`:
```bash
# shell/share.sh:228
if [[ ! -s $dep_notify_js ]]; then
cp -f "$file_notify_js_sample" "$dep_notify_js" # sample/notify.js → /ql/data/deps/sendNotify.js
fi
```
**③ 排在最后,所以最终文件永远是面板自带的那份。** 仓库里的 `sendNotify.js` 白改。
**验证**:
```bash
docker exec -it qinglong sh -c '
md5sum /ql/data/deps/sendNotify.js /ql/data/scripts/zhuhjay_ql-scripts_master/sendNotify.js
'
```
两个 md5 相同 = 被覆盖了。
**结论:通知实现放在仓库根的 `common.js`,脚本 `require('../common')` 取用。**
```js
const { notify } = require('../common')
await notify('QQ音乐签到', desp) // → { ok, channels: [{name, ok, detail}] }
```
`common.js` 不在 `deps` 里(`deps` 只有 `notify.py` 和 `sendNotify.js`),所以安全。
后续任何脚本想发通知,同样一行 `require('../common')` 即可,不用再碰 `sendNotify.js`。
### 坑 1:`sendNotify` 的三个静默 `resolve()`
如果哪天又接回 `sendNotify`,注意 `webhookNotify` 开头逐行 return,**不发请求也不报错**:
1. `WEBHOOK_URL` 或 `WEBHOOK_BODY` **至少一个**含 `$title`
2. `WEBHOOK_METHOD` **必须显式设**(空则走 else 分支 resolve)
3. body 能被 `parseString` 正确解析(见坑 2)
### 坑 2:`WEBHOOK_BODY` 写成单行 JSON 会被解析成空
`sendNotify` 的 `parseString` 用 `/(\w+):\s*((?:(?!\n\w+:).)*)/g`,**靠换行 + `\w+:` 断值**。
单行 JSON 没有换行,整个 body 被吞掉,实测解析结果 `{}`(0 个 key):
```js
'{"title":"$title","content":"$content"}' → {} ✗
'title:$title\ncontent:$content' → 两个 key ✓
```
body 变成 `{}` → 平台 `JSON.parse` 得到空对象 → 400「title 与 content 不能同时为空」。
**本脚本的 `buildHookBody` 不受这个坑影响** —— 它对 `application/json` 会先试
`JSON.parse`,不合法就直接 `JSON.stringify({title, content})` 重建,单行多行都行。
### 坑 3:pushplus 通知栏只显示标题 + 首行
微信服务号模板消息**默认不展示具体内容**,点「查看详情」才展开全文。
pushplus 官方给了三种解法([文档](https://pushplus.plus/doc/help/showmessage.html)):
| 方案 | 代价 |
|---|---|
| 在服务号发「激活消息」 | 24 小时内 5 条走客服消息,能显示全文 |
| 开通 pushplus 会员 | 付费,模板能多显示一部分 |
| 绑定自己的服务号 | 企业/个体工商户认证,每年 300 元 |
**不是 bug,是通道限制。** 要通知栏直接看到明细,换 Bark / Telegram / 企业微信机器人。
### 自检
```bash
# common.js 的 notify:69 条断言(纯函数 + 八种状态码 + Env 兼容性)
node test_common_notify.js
```
```
RESULT: PASS (69 passed, 0 failed)
```
### 指向平台的完整配置
```bash
WEBHOOK_URL=https://<平台域名>/api/hooks/ql
WEBHOOK_METHOD=POST
WEBHOOK_CONTENT_TYPE=application/json
WEBHOOK_BODY=title:$title
content:$content
WEBHOOK_HEADERS=Content-Type: application/json
x-sh-hook-token: <平台 QL_HOOK_TOKEN>
```
平台侧还有两道硬校验(`hookGuard`):
- **来源 IP 必须是 127.0.0.1 或 172.16/12**(docker 内网)。青龙与平台不同容器时,
走公网域名会让源 IP 变成反代地址 → 403。要用**容器内网地址**(如 `http://script-helper:3000`)。
- 回环地址带 `X-Forwarded-For` 一律 403(说明穿过了反代)。
## 任务覆盖
| 层 | 内容 | 风险 |
|---|---|---|
| 任务中心 | 签到(15) / 挂件(5) / 装扮(5) / 听书(5) / 节目StartTask | 低(等价 App 点击) |
| 会员福利站 | 每日签到(15) / 礼券(+10心愿值) / 旅程激励 / 月里程碑 / 月度好礼 / 臻享抽奖 | 低(先探后做,无硬编码 ID) |
| **金币中心** | 金币签到 + 领当日奖励 + 金币抽奖签到(`QM_COIN_SIGN`,默认 on) | 低(纯签到领奖,不花金币、不伪造行为) |
| VIP 体验券 | 分享卡配额查询 / 建卡(`QM_SHARE_CARD=on`) | 低(只读默认,不消耗配额) |
| 播放上报 | 任务20 完整 +10 需 App 正常收听(伪造统计默认关) | 中(默认关闭) |
| 支付流水任务 | 4/5/6/12/14/18/21 | 不可伪造,不碰 |
状态三态机:`Y`=已完成零请求(防风控)/ `W`=进行中直接领 / `N`=执行动作。
错误码处置:`20020` 冷却3s重领 · `30005` 只读探测后补领 · `10006` 写锁即停次日恢复 · `2000` 登录态失效推送提示。
## 金币中心(第三套体系)
**与任务中心、福利站完全独立**:金币体系走 `musicu.fcg` + 小程序 comm,另外两套走 `u6.y.qq.com` + zzc 签名。
给的是**金币**,不是成长值,所以不计入通知里的「成长值 +N」。
| 项目 | 值 |
|---|---|
| 网关 | `https://u.y.qq.com/cgi-bin/musicu.fcg`(**不签名**,裸 JSON) |
| 模块 | `music.actCenter.ActCenterSignNewSvr` |
| comm | `{ uin, authst, mina:1, appid:'wxada7aab80ba27074', ct:29, cv:0, format:'json' }` |
| 方法 | `GetSignInSummary` / `GetSignInTaskList` / `SignIn` / `AwardPrize` |
`authst` 就是 `qm_keyst`,放在 body 里(不走 Cookie)。**没有 `g_tk`,也不签名。**
### 流程
```
① GetSignInSummary + GetSignInTaskList → info.IsSignIn / ContinueTaskList
② 未签 → SignIn { ActID, ScenesID }
③ 找 State===2 的 TaskList 项 → AwardPrize { ActID, TaskID }
```
`retCode` 口径:`0` 成功 · `100004` 已领过(幂等,也算成功)· `1000` 未登录 · 其他失败。
### 不做的部分
| 功能 | 为什么不做 |
|---|---|
| **金币抽奖**(`drawCoinLottery`) | 消耗抽奖券,属于「花金币」不是「赚金币」 |
| **红包雨** | 要卡 6 个时段各一次,青龙 cron 得加 6 条 |
| **每日任务列表**(浏览页 +3 豆那类) | 完成上报发生在跳转页内部埋点,要逆向各 H5 协议 |
| **定时金币** | 服务端按各自间隔下发,要维护下次查询时间,收益低 |
### ActID 换季
`ActID` 是活动实例 ID,**活动换季就变**。当前硬编码值:
```
COIN_ACT_ID = Z25hHGi (金币中心签到)
COIN_SCENE_ID = 2
LOTTERY_ACT_ID = Z156KEu (金币抽奖签到)
```
失效时脚本会打:
```
[金币] 状态查询失败 code=... ret=...
[金币] 若持续失败,多半是活动换季导致 ActID 失效 —— 抓包看 ActID,改 QM_COIN_ACT_ID
```
并记 `warn`(不是 `fail` —— 不是登录态问题,也不是脚本 bug)。
**修法**:App 抓包看 `ActCenterSignNewSvr` 请求里的 `ActID` 参数,改环境变量 `QM_COIN_ACT_ID`,
不用改代码。
## 本地测试
```bash
node --check scrip_qqmusic_checkin.js # 语法
node scrip_qqmusic_checkin.js # 无 env 时优雅跳过
SH_QQMUSIC_KEY='123#Q_H_L_x' node -e "console.log(require('./scrip_qqmusic_checkin').parseAccounts())"
node test_sign_run.js # 签名对拍(需先 python test_sign_vector.py 生成期望)
node test_coin.js # 金币中心 19 条断言(mock https,不联网)
node test_render.js # 通知分组 24 条断言(纯函数,不联网)
node ../test_common_notify.js # 通知实现(69 条断言,见仓库根 README)
node ../_probe_meta.js # 青龙元数据注解
```
通知相关的测试在仓库根 `test_common_notify.js`(因为实现搬到了 `common.js`),
覆盖:
- `parseHookHeaders`:多行 / 空行 / 无冒号 / 值含冒号 / 重复 key / 字面量 `\n` 不切开
- `buildHookBody`:内容含双引号、反斜杠、制表符、多行时仍产出合法 JSON
- `notify`:mock 服务测 **200 / 204 / 201 / 401 / 403 / 400 / 429 / 连不上** 八种
- 无配置、双通道部分失败、`Env` 兼容性(jd / xiequ 在用)