v0 plus附加功能
- 新增 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 项
This commit is contained in:
+156
-3
@@ -27,6 +27,8 @@ import sys
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
import api
|
||||
import unlockmusic
|
||||
import upsert
|
||||
from api import (
|
||||
BITS_TO_ARRAY,
|
||||
JUDGE_BOXES,
|
||||
@@ -38,6 +40,7 @@ from api import (
|
||||
build_delta,
|
||||
build_favorite_delta,
|
||||
build_play_from_config,
|
||||
build_unlock_items,
|
||||
build_user_all,
|
||||
build_user_game_playlog,
|
||||
build_user_playlog,
|
||||
@@ -49,6 +52,13 @@ from api import (
|
||||
UpsertUserChargelog,
|
||||
ChargelogConfirmError,
|
||||
ChargelogGuardError,
|
||||
has_fresh_read,
|
||||
held_charges,
|
||||
last_read_age,
|
||||
note_charge_purchase,
|
||||
note_user_charge_read,
|
||||
purchased_charge_ids,
|
||||
reset_charge_guard,
|
||||
ticket_name,
|
||||
ticket_price,
|
||||
calc_valid_date,
|
||||
@@ -70,13 +80,13 @@ from api import (
|
||||
login_date_time_now,
|
||||
local_utc_offset_seconds,
|
||||
)
|
||||
from api.upsert_user_all import (
|
||||
from api.UpsertUserAll import (
|
||||
ConfirmRequiredError,
|
||||
PatchError,
|
||||
UpsertUserAll,
|
||||
UserAllRequestVO,
|
||||
)
|
||||
from api.upsert_user_chargelog import _now_string
|
||||
from api.UpsertUserChargelog import _now_string
|
||||
|
||||
# ★ 这些常量原先在 config.py,2026-10-06 已下沉到各 api 模块 —— 从 api 取。
|
||||
from api import (
|
||||
@@ -351,7 +361,7 @@ fav_server_raw.append({"itemKind": None, "itemIdList": []})
|
||||
_, fav_flags_raw = build_favorite_delta(fav_local, fav_server_raw)
|
||||
check("itemKind=null 的那条 -> 1(复刻真机 22212)", fav_flags_raw == "22212", fav_flags_raw)
|
||||
|
||||
# 归一化兜底:基线被 get_user_favorite.py 补齐 itemKind 后,服务器列表里 5 个 kind 齐全,
|
||||
# 归一化兜底:基线被 GetUserFavorite.py 补齐 itemKind 后,服务器列表里 5 个 kind 齐全,
|
||||
# 单看列表已看不出 itemKind=10 原本是 null,必须靠 assume_missing 显式声明。
|
||||
fav_server_norm = [{"itemKind": k, "itemIdList": []} for k in (3, 1, 2, 10, 11)]
|
||||
_, fav_flags_norm = build_favorite_delta(fav_local, fav_server_norm, (10,))
|
||||
@@ -1102,6 +1112,149 @@ except ChargelogConfirmError:
|
||||
check("买票未开闸门抛 ChargelogConfirmError(未发包)", True)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 13.7 歌曲解锁(解禁)
|
||||
# =============================================================================
|
||||
|
||||
section("13.7 歌曲解锁(解禁)")
|
||||
|
||||
# 依据 source/Assembly-CSharp/Net.VO.Mai2/VOExtensions.cs:981-1008
|
||||
# kind5 = MusicUnlockList(歌曲本体)
|
||||
# kind6 = MusicUnlockMasterList(Master)
|
||||
# kind7 = MusicUnlockReMasterList(Re:Master)
|
||||
# 依据 source/Assembly-CSharp/Manager/NotesListManager.cs:415/455
|
||||
# `if (id < 10000) return true;` -> 标准谱 Master/Re:Master 天生解锁
|
||||
|
||||
_unl_std, _unl_std_notes = build_unlock_items([1085])
|
||||
check("标准谱(<10000)只写 kind5,不写 kind6/7",
|
||||
[it["itemKind"] for it in _unl_std] == [5], str([it["itemKind"] for it in _unl_std]))
|
||||
check("解锁条目形态 = {itemKind,itemId,stock,isValid}",
|
||||
_unl_std[0] == {"itemKind": 5, "itemId": 1085, "stock": 1, "isValid": True},
|
||||
str(_unl_std[0]))
|
||||
check("标准谱给出「天生解锁」说明",
|
||||
any("天生解锁" in n for n in _unl_std_notes), str(_unl_std_notes))
|
||||
|
||||
_unl_dx, _ = build_unlock_items([11414])
|
||||
check("DX 谱写 kind5 + kind6 + kind7",
|
||||
[it["itemKind"] for it in _unl_dx] == [5, 6, 7], str([it["itemKind"] for it in _unl_dx]))
|
||||
|
||||
_unl_m, _ = build_unlock_items([11414], master=False)
|
||||
check("master=False 不写 kind6", [it["itemKind"] for it in _unl_m] == [5, 7],
|
||||
str([it["itemKind"] for it in _unl_m]))
|
||||
_unl_r, _ = build_unlock_items([11414], remaster=False)
|
||||
check("remaster=False 不写 kind7", [it["itemKind"] for it in _unl_r] == [5, 6],
|
||||
str([it["itemKind"] for it in _unl_r]))
|
||||
|
||||
_unl_dup, _ = build_unlock_items([11414], existing=[{"itemKind": 5, "itemId": 11414}])
|
||||
check("已存在的条目自动跳过(不重复插入)",
|
||||
[it["itemKind"] for it in _unl_dup] == [6, 7], str([it["itemKind"] for it in _unl_dup]))
|
||||
_unl_all, _ = build_unlock_items(
|
||||
[11414], existing=[{"itemKind": k, "itemId": 11414} for k in (5, 6, 7)])
|
||||
check("全都有时返回空列表", _unl_all == [], str(_unl_all))
|
||||
check("musicId 重复只写一次", len(build_unlock_items([1085, 1085])[0]) == 1)
|
||||
|
||||
_ua_unl = build_user_all(baseline, login_date_time=1791159576, playlog_id=1,
|
||||
unlock_music_ids=[11414])
|
||||
check("build_user_all 把解锁条目追加进 userItemList",
|
||||
[it["itemKind"] for it in _ua_unl["userItemList"]] == [5, 6, 7],
|
||||
str(_ua_unl["userItemList"]))
|
||||
check("解锁条目的位串全是 1(新增)", _ua_unl["isNewItemList"] == "111",
|
||||
_ua_unl["isNewItemList"])
|
||||
check("位串长度与 userItemList 条数一致",
|
||||
len(_ua_unl["isNewItemList"]) == len(_ua_unl["userItemList"]))
|
||||
check("不给 unlock_music_ids 时不产生 userItem 条目",
|
||||
build_user_all(baseline, login_date_time=1791159576,
|
||||
playlog_id=1)["userItemList"] == [])
|
||||
|
||||
_ua_unl3 = build_user_all(
|
||||
baseline, login_date_time=1791159576, playlog_id=1,
|
||||
extra_data={"userItemList": [{"itemKind": 5, "itemId": 1085,
|
||||
"stock": 1, "isValid": True}],
|
||||
"isNewItemList": "1"},
|
||||
unlock_music_ids=[11414])
|
||||
check("extra_data 给了 userItemList 时解锁条目追加在其后",
|
||||
[it["itemId"] for it in _ua_unl3["userItemList"]] == [1085, 11414, 11414, 11414],
|
||||
str([it["itemId"] for it in _ua_unl3["userItemList"]]))
|
||||
check("已有条目的位串保留、新条目补 1", _ua_unl3["isNewItemList"] == "1111",
|
||||
_ua_unl3["isNewItemList"])
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 13.8 unlockmusic.py 的参数解析(主脚本复用 upsert.run)
|
||||
# =============================================================================
|
||||
|
||||
section("13.8 unlockmusic.py 参数解析")
|
||||
|
||||
_uopts = unlockmusic.parse_args(["QRCODE", "--music", "11414", "--send"])
|
||||
check("--music 解析成 int 列表", _uopts["unlock_music"] == [11414],
|
||||
str(_uopts["unlock_music"]))
|
||||
check("二维码取第一个非 -- 参数", _uopts["qr"] == "QRCODE", _uopts["qr"])
|
||||
check("--send 置位", _uopts["send"] is True)
|
||||
|
||||
_uopts = unlockmusic.parse_args(["QR", "--music", "1085,11414", "--music", "7"])
|
||||
check("逗号(含中文逗号)+ 可重复都认",
|
||||
_uopts["unlock_music"] == [1085, 11414, 7], str(_uopts["unlock_music"]))
|
||||
|
||||
_uopts = unlockmusic.parse_args(["QR", "--unlock-music", "11414",
|
||||
"--no-unlock-master", "--no-unlock-remaster"])
|
||||
check("--unlock-music 是 --music 的别名", _uopts["unlock_music"] == [11414])
|
||||
check("两个难度开关默认 True、可关掉",
|
||||
_uopts["unlock_master"] is False and _uopts["unlock_remaster"] is False)
|
||||
|
||||
_uopts = unlockmusic.parse_args(["QR", "--music", "abc"])
|
||||
check("非法 musicId 记进 unlock_bad", _uopts.get("unlock_bad") == "abc")
|
||||
|
||||
check("upsert.parse_args 不再认识 --unlock-music",
|
||||
"unlock_music" not in upsert.parse_args(["QR", "--unlock-music", "11414"]))
|
||||
check("upsert.run 存在且可被别的脚本复用", callable(getattr(upsert, "run", None)))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 13.9 买票前置闸门(必须先跑 GetUserCharge)
|
||||
# =============================================================================
|
||||
|
||||
section("13.9 买票前置闸门(必须先跑 GetUserCharge)")
|
||||
|
||||
reset_charge_guard()
|
||||
check("没查过票时 has_fresh_read 为 False", has_fresh_read(55555) is False)
|
||||
check("没查过票时 last_read_age 为 None", last_read_age(55555) is None)
|
||||
|
||||
note_user_charge_read(55555, [{"chargeId": 2, "stock": 0},
|
||||
{"chargeId": 5, "stock": 1}])
|
||||
check("查过之后 has_fresh_read 为 True", has_fresh_read(55555) is True)
|
||||
check("held_charges 只挑 stock>0 的",
|
||||
[c["chargeId"] for c in held_charges(55555)] == [5], str(held_charges(55555)))
|
||||
|
||||
try:
|
||||
UpsertUserChargelog(66666, 2, 1791159576, user_data={},
|
||||
guard_purchasable=False, dry_run=True)
|
||||
check("没查票就买票应抛 ChargelogGuardError", False, "未抛异常")
|
||||
except ChargelogGuardError as exc:
|
||||
check("没查票就买票抛 ChargelogGuardError(未发包)", True)
|
||||
check("报错信息里点明要先调 GetUserCharge",
|
||||
"GetUserCharge" in str(exc), str(exc)[:120])
|
||||
|
||||
check("显式传 charge_list 视为已查票(可通过)",
|
||||
UpsertUserChargelog(66666, 2, 1791159576, charge_list=[], user_data={},
|
||||
guard_purchasable=False,
|
||||
dry_run=True).userCharge.chargeId == 2)
|
||||
|
||||
note_charge_purchase(77777, 2)
|
||||
check("purchased_charge_ids 记录已买", purchased_charge_ids(77777) == [2])
|
||||
note_user_charge_read(77777, [])
|
||||
try:
|
||||
UpsertUserChargelog(77777, 2, 1791159576, charge_list=[], user_data={},
|
||||
guard_purchasable=False, dry_run=True)
|
||||
check("同进程重复买票应被拦下", False, "未抛异常")
|
||||
except ChargelogGuardError as exc:
|
||||
check("同进程重复买票抛 ChargelogGuardError", True)
|
||||
check("报错信息点明已经买过", "已经买过" in str(exc), str(exc)[:120])
|
||||
|
||||
reset_charge_guard()
|
||||
check("reset 之后记忆清空",
|
||||
has_fresh_read(55555) is False and purchased_charge_ids(77777) == [])
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 14. 副作用闸门
|
||||
# =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user