- 新增 unlockmusic.py:解锁(解禁)歌曲 + 解禁 Master / Re:Master 难度,整个流程复用 upsert.py 的 run() - 买票前置闸门 api/ChargeGuard.py:必须先跑过 GetUserCharge,且同进程只允许买一次 - buy_ticket.py 票源改为 GetUserCharge,不再调用 GetGameCharge - UpsertUserAll 与买票的冷却统一为 15 秒 - api/ 文件名统一 PascalCase(文件名 = 函数名) - 项目改名 Astral,README 加 Logo、鸣谢与开发者说明 - 自检 284 项
139 lines
5.7 KiB
Python
139 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
unlockmusic.py —— 主脚本:扫码登录 → 解锁(解禁)歌曲 → 登出
|
||
================================================================================
|
||
|
||
**它没有自己的协议实现** —— 整个流程直接复用 `upsert.py` 的 `run()`:
|
||
扫码 → 预览 → 登录 → UpsertUserAll → 落库回查 → 登出,一模一样。
|
||
唯一多出来的东西是"要解锁哪些歌"这个入参。
|
||
|
||
--------------------------------------------------------------------------------
|
||
为什么解锁不用单独的接口
|
||
--------------------------------------------------------------------------------
|
||
游戏里解锁不是一次独立的网络请求,而是**往 userItemList 里补几条 UserItem**:
|
||
|
||
itemKind = 5 歌曲本体解锁(MusicUnlockList)
|
||
itemKind = 6 Master 难度解锁(MusicUnlockMasterList)
|
||
itemKind = 7 Re:Master 难度解锁(MusicUnlockReMasterList)
|
||
|
||
依据 source/Assembly-CSharp/Net.VO.Mai2/VOExtensions.cs:981-1008
|
||
(UserData.cs:132-138 那四个解锁列表不是独立节点,是并进 userItemList 的)。
|
||
|
||
条目长这样:
|
||
|
||
{"itemKind": 5, "itemId": 11414, "stock": 1, "isValid": true}
|
||
|
||
★ 标准谱(musicId < 10000)的 Master / Re:Master **天生解锁**,不会写 kind6/7
|
||
(source/Assembly-CSharp/Manager/NotesListManager.cs:415/455 开头就是
|
||
`if (id < 10000) return true;`);只有 DX 谱(>= 10000)才需要额外解禁。
|
||
★ 服务器上已经有的条目**自动跳过** —— 重复插入会让服务端直接 HTTP 500。
|
||
★ Re:Master 还受活动开关约束(IsUnlockReMaster 里的 IsOpenEvent),
|
||
那是机台侧开关,脚本改不了;写了也可能进游戏仍是灰的。
|
||
★ 本功能**没有真机抓包可对照**(机台侧的解锁不上报),是按源码实现的。
|
||
|
||
--------------------------------------------------------------------------------
|
||
用法
|
||
--------------------------------------------------------------------------------
|
||
python unlockmusic.py <二维码> --music 11414 # 预检,不发包
|
||
python unlockmusic.py <二维码> --music 11414 --send # ★ 真实解锁
|
||
python unlockmusic.py <二维码> --music 1085,11414 --send # 多首,逗号分隔
|
||
python unlockmusic.py <二维码> --music 11414 --send --no-unlock-remaster
|
||
|
||
# 只要歌曲本体,不解禁任何难度
|
||
python unlockmusic.py <二维码> --music 11414 --send --no-unlock-master --no-unlock-remaster
|
||
|
||
# 和其它主脚本一样的通用开关
|
||
python unlockmusic.py <二维码> --music 11414 --send --debug
|
||
python unlockmusic.py <二维码> --music 11414 --send --cooldown 0
|
||
python unlockmusic.py <二维码> --music 11414 --send --no-verify
|
||
|
||
| 参数 | 说明 |
|
||
|---|---|
|
||
| `--music ID[,ID...]` | 要解锁的 musicId,可重复、可逗号分隔(`--unlock-music` 也认) |
|
||
| `--send` | 真正发包(不加就是预检,零副作用) |
|
||
| `--no-unlock-master` | 不解禁 Master |
|
||
| `--no-unlock-remaster` | 不解禁 Re:Master |
|
||
| `--cooldown N` | 发包前冷却秒数,默认取 config 的 15 秒 |
|
||
| `--no-verify` | 跳过上传后的落库回查 |
|
||
| `--debug` | 本次运行打印完整收发报文 |
|
||
|
||
日志同样是 logs/latest.log(覆盖)+ logs/YYYY-MM-DD.log(追加)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
|
||
import upsert
|
||
from api import log_error, setup_logging, teardown_logging
|
||
|
||
|
||
def parse_args(argv):
|
||
"""极简命令行解析:第一个非 -- 开头的参数是二维码。"""
|
||
opts = {"qr": "", "send": False, "debug": False, "no_verify": False,
|
||
"cooldown": None, "extra_data": [],
|
||
"unlock_music": [], "unlock_master": True, "unlock_remaster": True}
|
||
i = 0
|
||
while i < len(argv):
|
||
a = argv[i]
|
||
if a == "--send":
|
||
opts["send"] = True
|
||
elif a == "--debug":
|
||
opts["debug"] = True
|
||
elif a == "--no-verify":
|
||
opts["no_verify"] = True
|
||
elif a == "--extra-data" and i + 1 < len(argv):
|
||
opts["extra_data"].append(argv[i + 1])
|
||
i += 1
|
||
elif a == "--cooldown" and i + 1 < len(argv):
|
||
opts["cooldown"] = float(argv[i + 1])
|
||
i += 1
|
||
elif a in ("--music", "--unlock-music") and i + 1 < len(argv):
|
||
# 可重复;也支持逗号分隔(中英文逗号都认)
|
||
for part in str(argv[i + 1]).replace(",", ",").split(","):
|
||
part = part.strip()
|
||
if not part:
|
||
continue
|
||
try:
|
||
opts["unlock_music"].append(int(part))
|
||
except ValueError:
|
||
opts["unlock_bad"] = part
|
||
i += 1
|
||
elif a == "--no-unlock-master":
|
||
opts["unlock_master"] = False
|
||
elif a == "--no-unlock-remaster":
|
||
opts["unlock_remaster"] = False
|
||
elif not a.startswith("--") and not opts["qr"]:
|
||
opts["qr"] = a
|
||
i += 1
|
||
return opts
|
||
|
||
|
||
def main() -> int:
|
||
opts = parse_args(sys.argv[1:])
|
||
if not opts["qr"] or not opts["unlock_music"]:
|
||
# 没有二维码、或者没有指定要解锁哪首 —— 直接把用法打出来
|
||
print(__doc__)
|
||
return 1
|
||
|
||
if opts.get("unlock_bad"):
|
||
# 参数本身就不对,连扫码都不用做
|
||
setup_logging("unlockmusic")
|
||
log_error("--music 里有非法 musicId:" + str(opts["unlock_bad"]))
|
||
teardown_logging()
|
||
return 6
|
||
|
||
# ★ 整个流程复用 upsert.run(),这里只多传三个解锁参数
|
||
return upsert.run(
|
||
opts,
|
||
unlock_music=opts["unlock_music"],
|
||
unlock_master=opts["unlock_master"],
|
||
unlock_remaster=opts["unlock_remaster"],
|
||
log_name="unlockmusic",
|
||
)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|