通知样例换成新的分组格式(📈 任务中心 / 🎁 会员福利站 / 💰 金币中心 / 🎫 VIP 体验券),并加分组规则表与「新增任务要补 step() 且用两段式名」的提醒。 新增「执行日志」章节:四段分隔标题的样例输出,说明末尾两行为什么要有第二行 (光看成长值合计看不出金币跑没跑),以及金币段开头打 ActID 的用途。 本地测试清单补 test_coin.js / test_render.js。
461 lines
20 KiB
Markdown
461 lines
20 KiB
Markdown
# 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 在用)
|
||
|