feat(upsert): 新增 --extra-data,可手工覆盖/补写 upsertUserAll 节点
- 支持 JSON 字符串与 JSON 文件路径,可重复指定(后者覆盖前者) - 节点名与 JSON 都在**扫码之前**校验,写错直接退出,不浪费二维码 - README:补 --extra-data 用法;标题居中;简化 source 说明
This commit is contained in:
@@ -1,8 +1,11 @@
|
|||||||
# WaHappy
|
<h1 align="center">WaHappy</h1>
|
||||||
|
|
||||||
> **舞萌DX 的游戏助手**
|
<p align="center">
|
||||||
>
|
<b>舞萌DX 的游戏助手</b><br>
|
||||||
> 宗旨:让舞萌DX的玩家能够更好地游玩游戏,获得快乐。
|
宗旨:让舞萌DX的玩家能够更好地游玩游戏,获得快乐。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">AI真的很好用你知道吗</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -13,7 +16,7 @@ WaHappy 是一个用 **Python 纯网络方式**与舞萌DX(SDGB 国服)游
|
|||||||
它不修改游戏、不注入进程,只是把游戏客户端做的事用脚本重做一遍:
|
它不修改游戏、不注入进程,只是把游戏客户端做的事用脚本重做一遍:
|
||||||
扫码登录 → 取数据 → 上传成绩 → 登出。
|
扫码登录 → 取数据 → 上传成绩 → 登出。
|
||||||
|
|
||||||
> 协议实现全部来自反编译源码 `source/Assembly-CSharp/` 与真机网络日志的双向验证,
|
> 协议实现全部来自反编译源码与真机网络日志的双向验证,
|
||||||
> 每个关键常量在代码注释里都标了**源码行号**。
|
> 每个关键常量在代码注释里都标了**源码行号**。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -77,7 +80,7 @@ WaHappy/
|
|||||||
│ └── ... 40+ 个接口脚本
|
│ └── ... 40+ 个接口脚本
|
||||||
│
|
│
|
||||||
├── logs/ 日志输出(latest.log + 日期.log)★ 不入库
|
├── logs/ 日志输出(latest.log + 日期.log)★ 不入库
|
||||||
└── source/ 反编译源码 + 真机日志 ★ 不入库(版权 + 隐私)
|
└── source/ 反编译源码 ★ 不入库,需自备
|
||||||
```
|
```
|
||||||
|
|
||||||
**设计约定**:`api/` 里每个脚本 = 一个接口,对外暴露一个**能传参的函数**。
|
**设计约定**:`api/` 里每个脚本 = 一个接口,对外暴露一个**能传参的函数**。
|
||||||
@@ -85,9 +88,7 @@ WaHappy/
|
|||||||
|
|
||||||
> **`docs/` 在 [`docs` 分支](https://git.fragrance.moe/Txiaochen/wahappy/src/branch/docs),`source/` 与 `logs/` 不入库。**
|
> **`docs/` 在 [`docs` 分支](https://git.fragrance.moe/Txiaochen/wahappy/src/branch/docs),`source/` 与 `logs/` 不入库。**
|
||||||
>
|
>
|
||||||
> `source/` 是本地查证用的反编译源码(含 SEGA 二进制与真机日志),
|
> `source/` 是反编译源码,**需要自备**。
|
||||||
> 代码注释里写的 `source/Assembly-CSharp/xxx.cs:123` 都是**引用出处**,
|
|
||||||
> 需要自己准备一份才能逐行核对。
|
|
||||||
|
|
||||||
### 分支说明
|
### 分支说明
|
||||||
|
|
||||||
@@ -128,6 +129,48 @@ python upsert.py "<二维码>" --send --no-verify # 跳过上传后的落库
|
|||||||
|
|
||||||
> 二维码从机台的「扫码登录」界面获取,**有效期 10 分钟**。
|
> 二维码从机台的「扫码登录」界面获取,**有效期 10 分钟**。
|
||||||
|
|
||||||
|
### 额外塞节点:`--extra-data`
|
||||||
|
|
||||||
|
真机报文的 36 个节点绝大多数能由快照 + 本局演算自动推出来,
|
||||||
|
但偶尔想手工覆盖某几个(比如本局拿到的新 KaleidxScope 门),就用 `--extra-data`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ① 从文件读(★ 推荐 —— 不用跟 shell 引号搏斗)
|
||||||
|
python upsert.py "<二维码>" --send --extra-data extra.json
|
||||||
|
|
||||||
|
# ② 直接写 JSON 字符串
|
||||||
|
python upsert.py "<二维码>" --send --extra-data '{"isNewMapList":"1"}'
|
||||||
|
|
||||||
|
# ③ 多个来源,后给的覆盖先给的
|
||||||
|
python upsert.py "<二维码>" --send --extra-data base.json --extra-data patch.json
|
||||||
|
```
|
||||||
|
|
||||||
|
`extra.json` 长这样:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"userKaleidxScopeList": [
|
||||||
|
{"gateId": 1, "isGateFound": true, "isKeyFound": true, "isClear": false,
|
||||||
|
"totalRestLife": 0, "totalAchievement": 0, "totalDeluxscore": 0,
|
||||||
|
"bestAchievement": 0, "bestDeluxscore": 0,
|
||||||
|
"bestAchievementDate": "", "bestDeluxscoreDate": "",
|
||||||
|
"playCount": 0, "clearDate": "", "lastPlayDate": "", "isInfoWatched": false}
|
||||||
|
],
|
||||||
|
"isNewKaleidxScopeList": "1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 规则 | 说明 |
|
||||||
|
|---|---|
|
||||||
|
| 键 | 必须是 `upsertUserAll` 的 **36 个节点**之一(含 11 条 `isNew*` 位串),写错会报错并列出可用节点名 |
|
||||||
|
| 值形态 | 按节点形态强制转换,传错立刻报错 |
|
||||||
|
| 位串 | 给了数组没给位串 → 按长度补 `"0"` 并提示。**新增条目请显式传 `"1"`**(`"0"` = 存量更新) |
|
||||||
|
| 优先级 | 最高,覆盖快照演算结果 |
|
||||||
|
| 校验时机 | ★ **扫码之前**就校验 —— JSON 写错直接退出,不浪费二维码 |
|
||||||
|
|
||||||
|
> ⚠ 在 PowerShell / CMD 里写内联 JSON 很容易被吞掉双引号,
|
||||||
|
> **建议一律用文件**(`--extra-data extra.json`)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 主脚本怎么写
|
## 主脚本怎么写
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ upsert.py —— 主脚本:扫码登录 → 上传成绩 → 登出
|
|||||||
python upsert.py <二维码> --send --cooldown 0 # 跳过发包前的 60 秒冷却
|
python upsert.py <二维码> --send --cooldown 0 # 跳过发包前的 60 秒冷却
|
||||||
python upsert.py <二维码> --send --no-verify # 跳过上传后的落库回查
|
python upsert.py <二维码> --send --no-verify # 跳过上传后的落库回查
|
||||||
|
|
||||||
|
# ★ 额外塞节点(--extra-data,可重复,后给的覆盖先给的)
|
||||||
|
python upsert.py <二维码> --send --extra-data extra.json
|
||||||
|
python upsert.py <二维码> --send --extra-data '{"isNewMapList":"1"}'
|
||||||
|
python upsert.py <二维码> --send --extra-data a.json --extra-data b.json
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
★ UpsertUserAll 一个函数就把整条结算链路跑完了
|
★ UpsertUserAll 一个函数就把整条结算链路跑完了
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
@@ -45,22 +50,49 @@ upsert.py —— 主脚本:扫码登录 → 上传成绩 → 登出
|
|||||||
8. 冷却 -> 真实发包
|
8. 冷却 -> 真实发包
|
||||||
9. 落库回查(重新拉服务器数据,确认真的写进去了)
|
9. 落库回查(重新拉服务器数据,确认真的写进去了)
|
||||||
|
|
||||||
想额外塞几个节点(比如本局拿到的新 KaleidxScope 门)就传 extra_data:
|
--------------------------------------------------------------------------------
|
||||||
|
★ --extra-data:额外塞节点
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
想手工覆盖 / 补上某几个节点(比如本局拿到的新 KaleidxScope 门)时用:
|
||||||
|
|
||||||
|
# ① 从文件读(★ 推荐,不用跟 shell 引号搏斗)
|
||||||
|
python upsert.py <二维码> --send --extra-data extra.json
|
||||||
|
|
||||||
|
# ② 直接写 JSON 字符串
|
||||||
|
python upsert.py <二维码> --send --extra-data '{"isNewMapList":"1"}'
|
||||||
|
|
||||||
|
# ③ 多个来源,后给的覆盖先给的
|
||||||
|
python upsert.py <二维码> --send --extra-data base.json --extra-data patch.json
|
||||||
|
|
||||||
|
extra.json 长这样:
|
||||||
|
|
||||||
|
{
|
||||||
|
"userKaleidxScopeList": [
|
||||||
|
{"gateId": 1, "isGateFound": true, "isKeyFound": true, "isClear": false,
|
||||||
|
"totalRestLife": 0, "totalAchievement": 0, "totalDeluxscore": 0,
|
||||||
|
"bestAchievement": 0, "bestDeluxscore": 0,
|
||||||
|
"bestAchievementDate": "", "bestDeluxscoreDate": "",
|
||||||
|
"playCount": 0, "clearDate": "", "lastPlayDate": "", "isInfoWatched": false}
|
||||||
|
],
|
||||||
|
"isNewKaleidxScopeList": "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
★ 键必须是 upsertUserAll 的 36 个节点之一(含 11 条 isNew* 位串),
|
||||||
|
写错会明确报错并列出可用节点名。
|
||||||
|
★ 没给对应位串时会按数组长度自动补 "0" 并提示 ——
|
||||||
|
新增条目请显式传 "1","0" 表示存量更新。
|
||||||
|
★ JSON 在**扫码之前**就校验,写错了直接退出,不会浪费二维码。
|
||||||
|
|
||||||
|
── 在代码里直接调 api 的等价写法 ──
|
||||||
|
|
||||||
UpsertUserAll(
|
UpsertUserAll(
|
||||||
user_id, login.loginId,
|
user_id, login.loginId,
|
||||||
login_date_time=login.loginDateTime,
|
login_date_time=login.loginDateTime,
|
||||||
auth_date_time=login.dateTime,
|
auth_date_time=login.dateTime,
|
||||||
token=token,
|
token=token,
|
||||||
extra_data={
|
extra_data={"userKaleidxScopeList": [...], "isNewKaleidxScopeList": "1"},
|
||||||
"userKaleidxScopeList": [{"gateId": 1, "isGateFound": True, ...}],
|
|
||||||
"isNewKaleidxScopeList": "1",
|
|
||||||
},
|
|
||||||
confirm=True,
|
confirm=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
extra_data 接受 dict / JSON 字符串 / JSON 文件路径;
|
|
||||||
键必须是 36 个节点之一,没给位串时会按数组长度自动补并给出提示。
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -70,6 +102,8 @@ import sys
|
|||||||
from api import (
|
from api import (
|
||||||
setup_logging,
|
setup_logging,
|
||||||
teardown_logging,
|
teardown_logging,
|
||||||
|
log_info,
|
||||||
|
log_warning,
|
||||||
log_error,
|
log_error,
|
||||||
ParseQRCode,
|
ParseQRCode,
|
||||||
UserPreview,
|
UserPreview,
|
||||||
@@ -78,13 +112,18 @@ from api import (
|
|||||||
LogoutType,
|
LogoutType,
|
||||||
UpsertUserAll,
|
UpsertUserAll,
|
||||||
ConfirmRequiredError,
|
ConfirmRequiredError,
|
||||||
|
load_extra_data,
|
||||||
|
PatchError,
|
||||||
|
NODE_ORDER,
|
||||||
|
NODE_TYPES,
|
||||||
)
|
)
|
||||||
from config import PLACE_ID, REGION_ID
|
from config import PLACE_ID, REGION_ID
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv):
|
def parse_args(argv):
|
||||||
"""极简命令行解析:第一个非 -- 开头的参数是二维码。"""
|
"""极简命令行解析:第一个非 -- 开头的参数是二维码。"""
|
||||||
opts = {"qr": "", "send": False, "debug": False, "no_verify": False, "cooldown": None}
|
opts = {"qr": "", "send": False, "debug": False, "no_verify": False,
|
||||||
|
"cooldown": None, "extra_data": []}
|
||||||
i = 0
|
i = 0
|
||||||
while i < len(argv):
|
while i < len(argv):
|
||||||
a = argv[i]
|
a = argv[i]
|
||||||
@@ -94,6 +133,10 @@ def parse_args(argv):
|
|||||||
opts["debug"] = True
|
opts["debug"] = True
|
||||||
elif a == "--no-verify":
|
elif a == "--no-verify":
|
||||||
opts["no_verify"] = True
|
opts["no_verify"] = True
|
||||||
|
elif a == "--extra-data" and i + 1 < len(argv):
|
||||||
|
# 可重复;值是 JSON 字符串或 JSON 文件路径
|
||||||
|
opts["extra_data"].append(argv[i + 1])
|
||||||
|
i += 1
|
||||||
elif a == "--cooldown" and i + 1 < len(argv):
|
elif a == "--cooldown" and i + 1 < len(argv):
|
||||||
opts["cooldown"] = float(argv[i + 1])
|
opts["cooldown"] = float(argv[i + 1])
|
||||||
i += 1
|
i += 1
|
||||||
@@ -117,6 +160,32 @@ def main() -> int:
|
|||||||
# 日志会话:开头一句。之后每次 api 调用都会自动记录,主脚本不用再写日志
|
# 日志会话:开头一句。之后每次 api 调用都会自动记录,主脚本不用再写日志
|
||||||
setup_logging("upsert")
|
setup_logging("upsert")
|
||||||
|
|
||||||
|
# ---- ★ 先解析 --extra-data(放在扫码之前:JSON 写错就别浪费二维码了)----
|
||||||
|
extra = None
|
||||||
|
if opts["extra_data"]:
|
||||||
|
merged = {}
|
||||||
|
try:
|
||||||
|
for item in opts["extra_data"]:
|
||||||
|
part = load_extra_data(item)
|
||||||
|
merged.update(part)
|
||||||
|
log_info("--extra-data 读入 " + str(len(part)) + " 个节点:" + ", ".join(part))
|
||||||
|
except PatchError as exc:
|
||||||
|
log_error("--extra-data 解析失败:" + str(exc))
|
||||||
|
return 6
|
||||||
|
|
||||||
|
# 节点名也在这里查(不然要等到登录后组包时才报错,白跑一趟)
|
||||||
|
unknown = [k for k in merged if k not in NODE_TYPES]
|
||||||
|
if unknown:
|
||||||
|
log_error("--extra-data 里有未知节点名:" + ", ".join(unknown))
|
||||||
|
log_error(" 可用节点(" + str(len(NODE_ORDER)) + " 个):" + ", ".join(NODE_ORDER))
|
||||||
|
return 6
|
||||||
|
|
||||||
|
if not merged:
|
||||||
|
log_warning("--extra-data 解析出来是空的,忽略")
|
||||||
|
else:
|
||||||
|
extra = merged
|
||||||
|
log_info("--extra-data 合计 " + str(len(extra)) + " 个节点:" + ", ".join(extra))
|
||||||
|
|
||||||
# 会话状态:保证异常时也能登出(否则账号会卡在 isLogin=True,下次登录 returnCode=100)
|
# 会话状态:保证异常时也能登出(否则账号会卡在 isLogin=True,下次登录 returnCode=100)
|
||||||
session = {"user_id": 0, "token": "", "login_dt": 0, "logged_in": False}
|
session = {"user_id": 0, "token": "", "login_dt": 0, "logged_in": False}
|
||||||
try:
|
try:
|
||||||
@@ -140,6 +209,7 @@ def main() -> int:
|
|||||||
login_date_time=login.loginDateTime,
|
login_date_time=login.loginDateTime,
|
||||||
auth_date_time=login.dateTime,
|
auth_date_time=login.dateTime,
|
||||||
token=token,
|
token=token,
|
||||||
|
extra_data=extra,
|
||||||
dry_run=not opts["send"],
|
dry_run=not opts["send"],
|
||||||
confirm=opts["send"],
|
confirm=opts["send"],
|
||||||
cooldown_sec=opts["cooldown"],
|
cooldown_sec=opts["cooldown"],
|
||||||
|
|||||||
Reference in New Issue
Block a user