重构:Get* 命名统一 + 用户数据落盘 + use_ticket / reincarnate
1) 命名统一(服务端 API 名带 Get 的,脚本名与函数名一律带 get/Get)
- 26 个 api 脚本改名:userdata.py -> get_user_data.py 等
- 7 个 game_*.py -> get_game_*.py
- 38 个函数改名:UserData -> GetUserData 等
- upsert_snapshot.py -> get_need_user_data.py,
fetch_upsert_snapshot -> GetNeedUserData
- user_getter_common.fetch_all_pages -> get_all_pages
2) GetNeedUserData 抓到的数据自动落盘
- <工程根>/data/<userId>.json,新数据总是覆盖老数据
- 新增 save_user_data / load_user_data / user_data_path /
list_saved_user_ids / DATA_DIR / PROJECT_ROOT
- data/ 已加入 .gitignore
3) use_ticket.py(新)
- 读取账号当前持有的功能票,用 UpsertUserAll 消耗掉
- 依据:GetUserChargeApi 与 UpsertUserAllApi 的 userChargeList 格式相同;
真机实证 userChargeList.stock -1 + useTicketId + playCredit
- playCredit = 投币数 + 票的 creditNum(VOExtensions.cs:838-845)
4) reincarnate.py(新)
- 账号数据迁移(转生):A 的数据克隆到 B
- 每包最多 4 首(真机一局上限),包间用续关登录换新 loginId
续关依据:ContinueProcess.cs:403 -> PacketUserLogin(isContinue: true)
- playlog 从曲目记录反推合成(没有对应的 Get 接口)
自检 238 项全绿。
This commit is contained in:
@@ -25,20 +25,26 @@ WaHappy 是一个用 **Python 纯网络方式**与舞萌DX(SDGB 国服)游
|
||||
|
||||
**v0 目前只是一个占位版本,请以「能不能用」而不是「有多少代码」来判断它。**
|
||||
|
||||
| 模块 | 状态 |
|
||||
| 脚本 | 状态 |
|
||||
|---|---|
|
||||
| **`upsert.py`** | ✅ **可用** —— 扫码登录 → 上传一局成绩 → 落库回查 → 登出 |
|
||||
| `buy_ticket.py` | ⚠️ **不可用** —— 购买功能票接口疑似已被服务端拉黑,见下方警告 |
|
||||
| **`buy_ticket.py`** | ✅ **可用** —— 购买功能票(2026-10-06 真机验证通过) |
|
||||
| **`use_ticket.py`** | ✅ **可用** —— 读取当前持有的功能票并用 `UpsertUserAll` 消耗掉 |
|
||||
| **`reincarnate.py`** | 🔧 已实现(未实跑)—— 账号数据迁移(转生),每包 ≤4 首 + 续关登录 |
|
||||
| `api/` 下 40+ 个接口模块 | 🔧 已实现并通过零网络自检,但**未全部在真机上验证过** |
|
||||
| **文档** | 📄 在 **`docs` 分支**(`mai` 分支不含)—— 开发过程、踩坑记录、实跑验证,内容比代码本身还多 |
|
||||
|
||||
### ⚠️ 关于购买功能票
|
||||
### 关于购买功能票(已解决)
|
||||
|
||||
`buy_ticket.py`(`UpsertUserChargelogApi`)在**报文与真机逐字节一致**的情况下
|
||||
仍被服务端拒绝(`returnCode=0`),之后该账号**疑似被拉黑,暂时无法再购买功能票**。
|
||||
`UpsertUserChargelogApi` 曾经一直返回 `returnCode=0`。根因是 **请求头多发了
|
||||
`Accept` 与 `Connection`** —— 真机(Unity Mono 的 `HttpWebRequest`)**不发这两个头**:
|
||||
|
||||
**请勿轻易重试该接口。** 完整排查过程见
|
||||
[购买功能票流程](https://git.fragrance.moe/Txiaochen/wahappy/src/branch/docs/docs/购买功能票流程.md) 第六节。
|
||||
```
|
||||
number / Content-Type / User-Agent / charset / Mai-Encoding / Content-Encoding / Cookie
|
||||
```
|
||||
|
||||
删掉之后一次通过(`stock` 0 → 1,落库确认)。完整排查过程见
|
||||
[购买功能票流程](https://git.fragrance.moe/Txiaochen/wahappy/src/branch/docs/docs/购买功能票流程.md)。
|
||||
|
||||
---
|
||||
|
||||
@@ -66,19 +72,23 @@ v0 的开发过程中,我反复把同一个目标拆成小段丢给 AI,结
|
||||
|
||||
```
|
||||
WaHappy/
|
||||
├── config.py 运行时可配置项(密钥 / 机台 / 歌曲数据 / 日志级别)
|
||||
├── config.py 运行时可配置项(密钥 / 机台 / 歌曲数据 / 日志级别)★ 需自建
|
||||
├── config.py.example 配置模板(机台与店铺标识留空待填)
|
||||
├── upsert.py ★ 主脚本:扫码登录 → 上传成绩 → 登出
|
||||
├── buy_ticket.py 主脚本:购买功能票(★ 当前不可用)
|
||||
├── selftest.py 零网络自检(236 项,判据 = 源码 + 真机日志)
|
||||
├── buy_ticket.py 主脚本:购买功能票
|
||||
├── use_ticket.py 主脚本:消耗当前持有的功能票
|
||||
├── reincarnate.py 主脚本:账号数据迁移(转生)
|
||||
├── selftest.py 零网络自检(238 项,判据 = 源码 + 真机日志)
|
||||
│
|
||||
├── api/ ★ 所有与服务器交互的模块(一个接口一个脚本)
|
||||
│ ├── __init__.py 统一导出 + 日志自动跟踪
|
||||
│ ├── maimai_net.py 网络核心(AES / deflate / URL 混淆 / Cookie)
|
||||
│ ├── log_setup.py 日志(INFO / DEBUG 双模式)
|
||||
│ ├── upsert_user_all.py 上传成绩(36 节点 / 110 字段 / 差分)
|
||||
│ ├── upsert_snapshot.py 结算基线快照
|
||||
│ ├── get_need_user_data.py 结算快照编排 + 落盘 data/<userId>.json
|
||||
│ └── ... 40+ 个接口脚本
|
||||
│
|
||||
├── data/ 每账号一份用户数据快照(<userId>.json)★ 不入库
|
||||
├── logs/ 日志输出(latest.log + 日期.log)★ 不入库
|
||||
└── source/ 反编译源码 ★ 不入库,需自备
|
||||
```
|
||||
@@ -86,6 +96,9 @@ WaHappy/
|
||||
**设计约定**:`api/` 里每个脚本 = 一个接口,对外暴露一个**能传参的函数**。
|
||||
主脚本只负责编排,日志和协议细节都在 `api/` 内部。
|
||||
|
||||
**命名约定**:服务端 API 名里带 `Get` 的,**脚本名与函数名一律带 `get` / `Get`**
|
||||
(不要模棱两可)。所以取数脚本全是 `get_*.py`,函数是 `GetXxx`。
|
||||
|
||||
> **`docs/` 在 [`docs` 分支](https://git.fragrance.moe/Txiaochen/wahappy/src/branch/docs),`source/` 与 `logs/` 不入库。**
|
||||
>
|
||||
> `source/` 是反编译源码,**需要自备**。
|
||||
@@ -158,6 +171,63 @@ python upsert.py "<二维码>" --send --no-verify # 跳过上传后的落库
|
||||
|
||||
> 二维码从机台的「扫码登录」界面获取,**有效期 10 分钟**。
|
||||
|
||||
### buy_ticket.py 用法
|
||||
|
||||
```bash
|
||||
python buy_ticket.py "<二维码>" --list # 只列当前票券和可买的票
|
||||
python buy_ticket.py "<二维码>" --charge-id 2 # 预检:只看报文
|
||||
python buy_ticket.py "<二维码>" --charge-id 2 --send # ★ 真实购买
|
||||
```
|
||||
|
||||
| chargeId | 票 | 上报的 price |
|
||||
|---|---|---|
|
||||
| 2 | 2倍チケット | 1 |
|
||||
| 3 | 3倍チケット | 2 |
|
||||
| 4 | 4倍チケット | 3 |
|
||||
| 5 | 5倍チケット | 4 |
|
||||
| 6 | 6倍チケット | 5 |
|
||||
|
||||
> ⚠️ **账号已经持有功能票时,本脚本会直接拒绝购买**(重复购买有封号风险)。
|
||||
> 想清掉手里的票,用下面的 `use_ticket.py`。
|
||||
|
||||
### use_ticket.py 用法
|
||||
|
||||
读取账号当前**持有**的功能票,然后用 `UpsertUserAll` 表达「用这张票打了一局」把它消耗掉。
|
||||
|
||||
```bash
|
||||
python use_ticket.py "<二维码>" --list # 只列当前持有的票
|
||||
python use_ticket.py "<二维码>" --ticket-id 2 # 预检:只看报文
|
||||
python use_ticket.py "<二维码>" --ticket-id 2 --send # ★ 真实消耗
|
||||
```
|
||||
|
||||
原理:`GetUserChargeApi` 与 `UpsertUserAllApi` 里的 `userChargeList`
|
||||
**格式完全相同**,所以只要把该节点里目标票的 `stock` 减 1,
|
||||
再让 playlog 带上 `useTicketId` / `playCredit`,服务端就认为这张票被这一局用掉了。
|
||||
|
||||
`playCredit` 的算法(`VOExtensions.cs:838-845`):
|
||||
|
||||
```
|
||||
playCredit = 投币数(普通局 = 1) + 票的 creditNum
|
||||
```
|
||||
|
||||
### reincarnate.py 用法(账号数据迁移)
|
||||
|
||||
把一个账号的数据克隆到另一个账号。**每包最多 4 首**(真机一局最多 4 首),
|
||||
每包之间用**续关登录**(`UserLogin(is_continue=True)`)换新的 `loginId`。
|
||||
|
||||
```bash
|
||||
# 源数据来自二维码(会联网抓 A 的数据并落盘到 data/<A>.json)
|
||||
python reincarnate.py --dst-qr "<B的二维码>" --src-qr "<A的二维码>"
|
||||
|
||||
# 源数据来自已保存的 JSON(离线,推荐)
|
||||
python reincarnate.py --dst-qr "<B的二维码>" --src-json data/12345678.json --plan
|
||||
python reincarnate.py --dst-qr "<B的二维码>" --src-json data/12345678.json --send
|
||||
|
||||
# 试跑
|
||||
--limit 20 # 只迁前 20 首
|
||||
--clone-identity # 连 userName / accessCode 一起克隆(默认保留 B 自己的)
|
||||
```
|
||||
|
||||
### 额外塞节点:`--extra-data`
|
||||
|
||||
真机报文的 36 个节点绝大多数能由快照 + 本局演算自动推出来,
|
||||
|
||||
Reference in New Issue
Block a user