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:
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ChargeGuard.py —— 买票前置闸门(GetUserCharge 必须已经跑过)
|
||||
================================================================================
|
||||
|
||||
★ 这个模块解决什么问题:
|
||||
|
||||
````
|
||||
错误写法(危险):
|
||||
UpsertUserChargelog(user_id, 2, login.loginDateTime, confirm=True)
|
||||
# ↑ 没先查 GetUserCharge —— 万一账号已经持有一张功能票,
|
||||
# 服务端看到的就是「重复购买」,有封号风险。
|
||||
```
|
||||
|
||||
正确写法:
|
||||
```
|
||||
held = GetUserCharge(user_id) # ① 先查:有没有持票
|
||||
...
|
||||
UpsertUserChargelog(user_id, 2, ...) # ② 再买
|
||||
```
|
||||
|
||||
本模块在 ① 里记一笔「这个 user_id 刚查过票券」,在 ② 里检查这笔记录,
|
||||
没查过就抛 `ChargelogGuardError`(**未发包**)。
|
||||
|
||||
★ 还顺手挡第二件事:**同一个账号在同一个进程里只允许买一次票**。
|
||||
真机买票是「买完立刻被这一局用掉」(stock--),正常状态 stock 恒为 0;
|
||||
脚本连着买两张是明显的异常行为,这里直接拦掉。
|
||||
|
||||
★ 记忆只存在于**当前进程**(内存字典),脚本退出即清空 —— 不做持久化,
|
||||
免得下次运行时被上一次的记录误拦。
|
||||
|
||||
依据:
|
||||
source/Assembly-CSharp/Manager/TicketManager.cs:130-174 UseTicketPrepare
|
||||
source/Assembly-CSharp/Manager/TicketManager.cs:234-253 RequestUserCharge
|
||||
source/Assembly-CSharp/Net.Packet.Mai2/PacketGetUserCharge.cs:20
|
||||
source/Assembly-CSharp/Net.Packet.Mai2/PacketUploadUserChargelog.cs:19
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
#: GetUserCharge 结果的「新鲜度」窗口(秒)。
|
||||
#: 超过这个时间才买票,认为那次查询已经过期,需要重查。
|
||||
CHARGE_READ_FRESH_SEC = 600.0
|
||||
|
||||
#: user_id -> 最近一次 GetUserCharge 的 monotonic 时刻
|
||||
_LAST_READ: Dict[int, float] = {}
|
||||
|
||||
#: user_id -> 最近一次 GetUserCharge 返回的 userChargeList(原样)
|
||||
_LAST_LIST: Dict[int, List[Dict[str, Any]]] = {}
|
||||
|
||||
#: user_id -> 本进程内已经买过的 chargeId 列表
|
||||
_PURCHASES: Dict[int, List[int]] = {}
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 一、GetUserCharge 侧:记一笔
|
||||
# =============================================================================
|
||||
|
||||
def note_user_charge_read(
|
||||
user_id: int,
|
||||
charge_list: Optional[List[Dict[str, Any]]] = None,
|
||||
) -> None:
|
||||
"""
|
||||
记录「刚刚为这个 user_id 查过票券」。
|
||||
|
||||
★ 由 GetUserCharge() 在拿到响应后自动调用,调用方不用管。
|
||||
"""
|
||||
try:
|
||||
uid = int(user_id)
|
||||
except (TypeError, ValueError):
|
||||
return
|
||||
_LAST_READ[uid] = time.monotonic()
|
||||
if isinstance(charge_list, list):
|
||||
_LAST_LIST[uid] = [c for c in charge_list if isinstance(c, dict)]
|
||||
|
||||
|
||||
def last_read_age(user_id: int) -> Optional[float]:
|
||||
"""距上次 GetUserCharge 过了多少秒;从没查过返回 None。"""
|
||||
try:
|
||||
uid = int(user_id)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
ts = _LAST_READ.get(uid)
|
||||
if ts is None:
|
||||
return None
|
||||
return max(0.0, time.monotonic() - ts)
|
||||
|
||||
|
||||
def has_fresh_read(
|
||||
user_id: int,
|
||||
max_age: float = CHARGE_READ_FRESH_SEC,
|
||||
) -> bool:
|
||||
"""这个 user_id 最近(max_age 秒内)查过 GetUserCharge 吗?"""
|
||||
age = last_read_age(user_id)
|
||||
if age is None:
|
||||
return False
|
||||
return age <= float(max_age)
|
||||
|
||||
|
||||
def last_charge_list(user_id: int) -> List[Dict[str, Any]]:
|
||||
"""最近一次 GetUserCharge 返回的 userChargeList(没有则空表)。"""
|
||||
try:
|
||||
return list(_LAST_LIST.get(int(user_id)) or [])
|
||||
except (TypeError, ValueError):
|
||||
return []
|
||||
|
||||
|
||||
def held_charges(user_id: int) -> List[Dict[str, Any]]:
|
||||
"""最近一次查到的「持有中」(stock > 0)的票券。"""
|
||||
return [c for c in last_charge_list(user_id) if int(c.get("stock", 0) or 0) > 0]
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 二、UpsertUserChargelog 侧:买之前查
|
||||
# =============================================================================
|
||||
|
||||
def note_charge_purchase(user_id: int, charge_id: int) -> None:
|
||||
"""记录「这个 user_id 刚刚买了一张 chargeId 的票」。"""
|
||||
try:
|
||||
uid = int(user_id)
|
||||
except (TypeError, ValueError):
|
||||
return
|
||||
_PURCHASES.setdefault(uid, []).append(int(charge_id))
|
||||
|
||||
|
||||
def purchased_charge_ids(user_id: int) -> List[int]:
|
||||
"""本进程内这个 user_id 已经买过的 chargeId。"""
|
||||
try:
|
||||
return list(_PURCHASES.get(int(user_id)) or [])
|
||||
except (TypeError, ValueError):
|
||||
return []
|
||||
|
||||
|
||||
def reset(user_id: Optional[int] = None) -> None:
|
||||
"""清空记忆(测试用;不传 user_id 就全清)。"""
|
||||
if user_id is None:
|
||||
_LAST_READ.clear()
|
||||
_LAST_LIST.clear()
|
||||
_PURCHASES.clear()
|
||||
return
|
||||
try:
|
||||
uid = int(user_id)
|
||||
except (TypeError, ValueError):
|
||||
return
|
||||
_LAST_READ.pop(uid, None)
|
||||
_LAST_LIST.pop(uid, None)
|
||||
_PURCHASES.pop(uid, None)
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
chime.py —— 扫码登录(二维码 -> userId + token)
|
||||
Chime.py —— 扫码登录(二维码 -> userId + token)
|
||||
================================================================================
|
||||
|
||||
功能:把微信二维码串(或二维码 URL)交给 aimedb 服务器,换取 userId 与 token。
|
||||
@@ -76,7 +76,7 @@ QR_OPEN_GAME_ID = "MAID"
|
||||
QR_TITLE_KEY = "SDGB"
|
||||
QR_USER_AGENT = "WC_AIME_LIB"
|
||||
|
||||
from .log_setup import log_debug, log_error, log_info, log_request, log_response
|
||||
from .LogSetup import log_debug, log_error, log_info, log_request, log_response
|
||||
|
||||
# =============================================================================
|
||||
# 二维码格式常量(CCommQRCode.cs + 技术规范)
|
||||
@@ -288,7 +288,7 @@ def ParseQRCode(qr_input: str, *, chip_id: str = DEFAULT_CHIP_ID) -> Tuple[int,
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
from .log_setup import setup_logging, teardown_logging
|
||||
from .LogSetup import setup_logging, teardown_logging
|
||||
|
||||
def _main() -> int:
|
||||
setup_logging("chime_test")
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_game_charge.py —— 取游戏计费配置(GetGameChargeApi)
|
||||
GetGameCharge.py —— 取游戏计费配置(GetGameChargeApi)
|
||||
================================================================================
|
||||
|
||||
取服务器下发的计费档位。
|
||||
@@ -22,8 +22,8 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient, request_dict
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient, request_dict
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_game_settings.py —— 获取游戏设置(GetGameSettingApi)
|
||||
GetGameSettings.py —— 获取游戏设置(GetGameSettingApi)
|
||||
================================================================================
|
||||
|
||||
功能:取服务器下发的运行时配置(维护窗口、心跳间隔、包超时、各类上限)。
|
||||
@@ -25,8 +25,8 @@ from typing import Optional
|
||||
|
||||
from config import CLIENT_ID, PLACE_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info, log_warning
|
||||
from .maimai_net import MaimaiNetClient, request_dict
|
||||
from .LogSetup import log_info, log_warning
|
||||
from .MaimaiNet import MaimaiNetClient, request_dict
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -154,7 +154,7 @@ def is_ping_disabled(settings: GameSettingResponseVO) -> bool:
|
||||
# =============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
from .log_setup import setup_logging, teardown_logging
|
||||
from .LogSetup import setup_logging, teardown_logging
|
||||
|
||||
def _main() -> int:
|
||||
setup_logging("gamesettings_test")
|
||||
@@ -166,7 +166,7 @@ if __name__ == "__main__":
|
||||
print(" " + name.ljust(34) + "= " + str(getattr(gs, name)))
|
||||
return 0
|
||||
except Exception as exc:
|
||||
from .log_setup import log_error
|
||||
from .LogSetup import log_error
|
||||
log_error("获取游戏设置失败:" + str(exc))
|
||||
return 1
|
||||
finally:
|
||||
@@ -1,11 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_need_user_data.py —— 结算快照编排(统一调用各取数脚本)
|
||||
GetNeedUserData.py —— 结算快照编排(统一调用各取数脚本)
|
||||
================================================================================
|
||||
|
||||
功能:一键抓取 UpsertUserAll 需要的全部基线数据,整理成「节点字典」。
|
||||
|
||||
★ 本模块**只做编排**,取数逻辑都在各自的 api 脚本里(get_user_data.py / get_user_map.py …)。
|
||||
★ 本模块**只做编排**,取数逻辑都在各自的 api 脚本里(GetUserData.py / GetUserMap.py …)。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
为什么需要快照
|
||||
@@ -41,29 +41,29 @@ from typing import Any, Dict, List, Optional, Sequence
|
||||
|
||||
from config import CLIENT_ID, PLACE_ID, PLAY_SHOP_ITEM_IDS, REGION_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_debug, log_info, log_warning
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .LogSetup import log_debug, log_info, log_warning
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
|
||||
# 各取数脚本(一个接口一个脚本)
|
||||
from .get_user_data import GetUserData
|
||||
from .get_user_extend import GetUserExtend
|
||||
from .get_user_option import GetUserOption
|
||||
from .get_user_character import GetUserCharacter
|
||||
from .get_user_ghost import GetUserGhost
|
||||
from .get_user_map import GetUserMapAll
|
||||
from .get_user_login_bonus import GetUserLoginBonusAll
|
||||
from .get_user_rating import GetUserRating
|
||||
from .get_user_item import GetUserItemAll
|
||||
from .get_user_music import GetUserMusicDetailAll
|
||||
from .get_user_course import GetUserCourseAll
|
||||
from .get_user_charge import GetUserCharge
|
||||
from .get_user_favorite import GetUserFavoriteAll
|
||||
from .get_user_activity import GetUserActivity
|
||||
from .get_user_mission import GetUserMissionData
|
||||
from .get_user_intimate import GetUserIntimate
|
||||
from .get_user_shop_stock import GetUserShopStock
|
||||
from .get_user_favorite_item import GetUserFavoriteItemAll
|
||||
from .get_user_kaleidx_scope import GetUserKaleidxScope
|
||||
from .GetUserData import GetUserData
|
||||
from .GetUserExtend import GetUserExtend
|
||||
from .GetUserOption import GetUserOption
|
||||
from .GetUserCharacter import GetUserCharacter
|
||||
from .GetUserGhost import GetUserGhost
|
||||
from .GetUserMap import GetUserMapAll
|
||||
from .GetUserLoginBonus import GetUserLoginBonusAll
|
||||
from .GetUserRating import GetUserRating
|
||||
from .GetUserItem import GetUserItemAll
|
||||
from .GetUserMusic import GetUserMusicDetailAll
|
||||
from .GetUserCourse import GetUserCourseAll
|
||||
from .GetUserCharge import GetUserCharge
|
||||
from .GetUserFavorite import GetUserFavoriteAll
|
||||
from .GetUserActivity import GetUserActivity
|
||||
from .GetUserMission import GetUserMissionData
|
||||
from .GetUserIntimate import GetUserIntimate
|
||||
from .GetUserShopStock import GetUserShopStock
|
||||
from .GetUserFavoriteItem import GetUserFavoriteItemAll
|
||||
from .GetUserKaleidxScope import GetUserKaleidxScope
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -372,7 +372,7 @@ def summarize_snapshot(snapshot: Dict[str, Any]) -> Dict[str, int]:
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
from .log_setup import log_error, setup_logging, teardown_logging
|
||||
from .LogSetup import log_error, setup_logging, teardown_logging
|
||||
|
||||
def _main() -> int:
|
||||
setup_logging("get_need_user_data_test")
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_activity.py —— 取用户活动记录(GetUserActivityApi)
|
||||
GetUserActivity.py —— 取用户活动记录(GetUserActivityApi)
|
||||
================================================================================
|
||||
|
||||
取玩家的活动/成就记录(playList 与 musicList 两个列表)。
|
||||
@@ -25,9 +25,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserActivity(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_character.py —— 取用户角色列表(GetUserCharacterApi)
|
||||
GetUserCharacter.py —— 取用户角色列表(GetUserCharacterApi)
|
||||
================================================================================
|
||||
|
||||
取玩家所有角色的等级与觉醒度(真机 312 条)。
|
||||
@@ -23,9 +23,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserCharacter(
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
GetUserCharge.py —— 取用户票券(GetUserChargeApi)
|
||||
================================================================================
|
||||
|
||||
取玩家的票券持有情况(真机 3 条)。
|
||||
|
||||
对应接口:GetUserChargeApi
|
||||
请求体 :UserIdRequestVO
|
||||
响应 :{userId, length, userChargeList:[{chargeId,stock,purchaseDate,validDate,extNum1}]}
|
||||
依据 :source/Assembly-CSharp/Net.VO.Mai2/UserChargeRequestVO.cs
|
||||
|
||||
★ 回传 Upsert 时要丢掉 extNum1(真机 Upsert 报文里没有这个字段)。
|
||||
|
||||
★ 调用本接口会**登记一次「票券已查询」**(见 api/ChargeGuard.py)。
|
||||
UpsertUserChargelog 发包前会检查这笔登记 —— 没查过票就买票会被直接拒绝。
|
||||
|
||||
调用示例:
|
||||
from api import GetUserCharge
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .ChargeGuard import note_user_charge_read
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserCharge(
|
||||
user_id: int,
|
||||
*,
|
||||
client_id: str = CLIENT_ID,
|
||||
timeout_ms: Optional[int] = None,
|
||||
client: Optional[MaimaiNetClient] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
取用户票券(对应 GetUserChargeApi)。
|
||||
|
||||
返回:
|
||||
原始响应 dict,含 userChargeList
|
||||
"""
|
||||
log_info("取用户票券:user_id=" + str(user_id))
|
||||
raw = _get("GetUserChargeApi", UserIdRequestVO(userId=user_id), user_id,
|
||||
client_id=client_id, timeout_ms=timeout_ms, client=client)
|
||||
|
||||
# ★ 记一笔「这个账号刚查过票券」——
|
||||
# UpsertUserChargelog 发包前会检查这笔记录,没查过就拒绝购买。
|
||||
# 见 api/ChargeGuard.py 的说明。
|
||||
lst = (raw.get("userChargeList") if isinstance(raw, dict) else None) or []
|
||||
note_user_charge_read(user_id, lst)
|
||||
held = [c for c in lst if isinstance(c, dict) and int(c.get("stock", 0) or 0) > 0]
|
||||
if held:
|
||||
log_info("★ 该账号当前持有 " + str(len(held)) + " 张功能票:"
|
||||
+ "、".join("chargeId=" + str(c.get("chargeId"))
|
||||
+ " stock=" + str(c.get("stock")) for c in held))
|
||||
else:
|
||||
log_info("该账号当前没有持有功能票")
|
||||
return raw
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_course.py —— 取用户课题模式成绩(GetUserCourseApi)
|
||||
GetUserCourse.py —— 取用户课题模式成绩(GetUserCourseApi)
|
||||
================================================================================
|
||||
|
||||
取玩家的 COURSE(课题)模式通关记录(真机 16 条)。
|
||||
@@ -21,9 +21,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserCourseRequestVO, get_all_pages
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserCourseRequestVO, get_all_pages
|
||||
|
||||
|
||||
def GetUserCourse(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_data.py —— 取用户全量数据(GetUserDataApi)
|
||||
GetUserData.py —— 取用户全量数据(GetUserDataApi)
|
||||
================================================================================
|
||||
|
||||
取玩家档案(86 个字段的大对象),是 upsertUserAll.userData 的**唯一正确来源**。
|
||||
@@ -29,9 +29,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserData(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_extend.py —— 取用户扩展设置(GetUserExtendApi)
|
||||
GetUserExtend.py —— 取用户扩展设置(GetUserExtendApi)
|
||||
================================================================================
|
||||
|
||||
取玩家上次的选曲 / 分类 / 卡片槽等扩展状态。
|
||||
@@ -23,9 +23,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserExtend(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_favorite.py —— 取用户收藏(GetUserFavoriteApi)
|
||||
GetUserFavorite.py —— 取用户收藏(GetUserFavoriteApi)
|
||||
================================================================================
|
||||
|
||||
取玩家各类收藏夹(称号 / 头像 / 相框 / 搭档 / 背景…)。
|
||||
@@ -24,9 +24,9 @@ from typing import Any, Dict, List, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info, log_warning
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserFavoriteRequestVO, FAVORITE_ITEM_KINDS
|
||||
from .LogSetup import log_info, log_warning
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserFavoriteRequestVO, FAVORITE_ITEM_KINDS
|
||||
|
||||
|
||||
def GetUserFavorite(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_favorite_item.py —— 取用户收藏曲目/对手(GetUserFavoriteItemApi)
|
||||
GetUserFavoriteItem.py —— 取用户收藏曲目/对手(GetUserFavoriteItemApi)
|
||||
================================================================================
|
||||
|
||||
取玩家收藏的乐曲(kind=1)或收藏的对手(kind=2)。
|
||||
@@ -24,9 +24,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserFavoriteItemRequestVO, get_all_pages
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserFavoriteItemRequestVO, get_all_pages
|
||||
|
||||
#: kind 取值
|
||||
FAVORITE_ITEM_KIND_MUSIC = 1 # 收藏乐曲
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_friend_bonus.py —— 取用户好友里程(GetUserFriendBonusApi)
|
||||
GetUserFriendBonus.py —— 取用户好友里程(GetUserFriendBonusApi)
|
||||
================================================================================
|
||||
|
||||
取玩家本次可领取的好友里程(getMiles)。
|
||||
@@ -21,9 +21,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info, log_warning
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info, log_warning
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserFriendBonus(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_ghost.py —— 取用户幽灵数据(GetUserGhostApi)
|
||||
GetUserGhost.py —— 取用户幽灵数据(GetUserGhostApi)
|
||||
================================================================================
|
||||
|
||||
取玩家的「幽灵」对战记录(真机 15 条)。
|
||||
@@ -24,9 +24,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserGhost(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_intimate.py —— 取用户搭档亲密度(GetUserIntimateApi)
|
||||
GetUserIntimate.py —— 取用户搭档亲密度(GetUserIntimateApi)
|
||||
================================================================================
|
||||
|
||||
取玩家与各搭档的亲密度(真机 2 条)。
|
||||
@@ -20,9 +20,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserIntimate(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_item.py —— 取用户道具(GetUserItemApi)
|
||||
GetUserItem.py —— 取用户道具(GetUserItemApi)
|
||||
================================================================================
|
||||
|
||||
取玩家各类道具的库存(真机按 kind 分 12 次请求)。
|
||||
@@ -38,9 +38,9 @@ from typing import Any, Dict, List, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_debug, log_info, log_warning
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserItemRequestVO, ITEM_KINDS, ITEM_NEXT_INDEX_FACTOR
|
||||
from .LogSetup import log_debug, log_info, log_warning
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserItemRequestVO, ITEM_KINDS, ITEM_NEXT_INDEX_FACTOR
|
||||
|
||||
|
||||
def GetUserItem(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_kaleidx_scope.py —— 取 KALEIDX 门进度(GetUserKaleidxScopeApi / GetGameKaleidxScopeApi)
|
||||
GetUserKaleidxScope.py —— 取 KALEIDX 门进度(GetUserKaleidxScopeApi / GetGameKaleidxScopeApi)
|
||||
================================================================================
|
||||
|
||||
对应接口:
|
||||
@@ -17,7 +17,7 @@ get_user_kaleidx_scope.py —— 取 KALEIDX 门进度(GetUserKaleidxScopeApi
|
||||
★★ 重要区分(曾导致 userData 取空)
|
||||
--------------------------------------------------------------------------------
|
||||
GameKaleidxScopeResponseVO 里**只有** gameKaleidxScopeList 字段,没有 userData。
|
||||
玩家档案 userData 的正确来源是 GetUserDataApi(见 get_user_data.py)。
|
||||
玩家档案 userData 的正确来源是 GetUserDataApi(见 GetUserData.py)。
|
||||
|
||||
真机日志里「GetGameKaleidxScope 的响应带 userData」是抓包工具把时间相邻的
|
||||
两条响应合并显示造成的假象 —— 不要据此给 GetGameKaleidxScopeApi 发 userId,
|
||||
@@ -33,9 +33,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, EmptyRequestVO, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, EmptyRequestVO, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserKaleidxScope(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_login_bonus.py —— 取用户登录奖励(GetUserLoginBonusApi)
|
||||
GetUserLoginBonus.py —— 取用户登录奖励(GetUserLoginBonusApi)
|
||||
================================================================================
|
||||
|
||||
取玩家的登录奖励领取进度(真机 38 条,分两页)。
|
||||
@@ -22,9 +22,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserLoginBonusRequestVO, get_all_pages
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserLoginBonusRequestVO, get_all_pages
|
||||
|
||||
|
||||
def GetUserLoginBonus(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_map.py —— 取用户地图进度(GetUserMapApi)
|
||||
GetUserMap.py —— 取用户地图进度(GetUserMapApi)
|
||||
================================================================================
|
||||
|
||||
取玩家各地图的推进距离(真机 104 条)。
|
||||
@@ -23,9 +23,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdPageRequestVO, get_all_pages
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdPageRequestVO, get_all_pages
|
||||
|
||||
|
||||
def GetUserMap(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_mission.py —— 取用户任务与周数据(GetUserMissionDataApi)
|
||||
GetUserMission.py —— 取用户任务与周数据(GetUserMissionDataApi)
|
||||
================================================================================
|
||||
|
||||
取玩家的任务进度与周常数据(真机 6 条任务)。
|
||||
@@ -25,9 +25,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserMissionData(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_music.py —— 取用户曲目成绩(GetUserMusicApi)
|
||||
GetUserMusic.py —— 取用户曲目成绩(GetUserMusicApi)
|
||||
================================================================================
|
||||
|
||||
取玩家全部曲目的历史成绩归档(真机 739 组)。
|
||||
@@ -27,9 +27,9 @@ from typing import Any, Dict, List, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserMusicRequestVO, get_all_pages
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserMusicRequestVO, get_all_pages
|
||||
|
||||
|
||||
def GetUserMusicDetail(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_new_item.py —— 取新增道具(GetUserNewItemListApi)
|
||||
GetUserNewItem.py —— 取新增道具(GetUserNewItemListApi)
|
||||
================================================================================
|
||||
|
||||
上传结算后查询「本次新增了哪些道具」(真机响应 userItemList 为空)。
|
||||
@@ -25,8 +25,8 @@ from typing import Any, Dict, List, Optional
|
||||
|
||||
from config import CLIENT_ID, PLAY_VERSION_INT, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient, request_dict
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient, request_dict
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_option.py —— 取用户游戏设置(GetUserOptionApi)
|
||||
GetUserOption.py —— 取用户游戏设置(GetUserOptionApi)
|
||||
================================================================================
|
||||
|
||||
取玩家的音符速度、判定显示、音量等游戏内设置(48 个字段)。
|
||||
@@ -24,9 +24,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserOption(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_preview.py —— 用户预览(GetUserPreviewApi)
|
||||
GetUserPreview.py —— 用户预览(GetUserPreviewApi)
|
||||
================================================================================
|
||||
|
||||
功能:刷卡后欢迎界面的数据(昵称、Rating、是否已登录、封禁状态…)。
|
||||
@@ -29,8 +29,8 @@ from typing import Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info, log_warning
|
||||
from .maimai_net import MaimaiNetClient, request_dict
|
||||
from .LogSetup import log_info, log_warning
|
||||
from .MaimaiNet import MaimaiNetClient, request_dict
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -133,7 +133,7 @@ def GetUserPreview(
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
from .log_setup import log_error, setup_logging, teardown_logging
|
||||
from .LogSetup import log_error, setup_logging, teardown_logging
|
||||
|
||||
def _main() -> int:
|
||||
setup_logging("userpreview_test")
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_rating.py —— 取用户 Rating 明细(GetUserRatingApi)
|
||||
GetUserRating.py —— 取用户 Rating 明细(GetUserRatingApi)
|
||||
================================================================================
|
||||
|
||||
取玩家的 Rating 构成(旧谱 / 新谱 / 下一档候选 + 段位)。
|
||||
@@ -23,9 +23,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserRating(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_recommend_music.py —— 取推荐曲目(GetUserRecommendRateMusicApi / GetUserRecommendSelectMusicApi)
|
||||
GetUserRecommendMusic.py —— 取推荐曲目(GetUserRecommendRateMusicApi / GetUserRecommendSelectMusicApi)
|
||||
================================================================================
|
||||
|
||||
对应接口:
|
||||
@@ -23,9 +23,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserRecommendRateMusic(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_region.py —— 取用户地区游玩统计(GetUserRegionApi)
|
||||
GetUserRegion.py —— 取用户地区游玩统计(GetUserRegionApi)
|
||||
================================================================================
|
||||
|
||||
取玩家在各地区的游玩次数。
|
||||
@@ -21,9 +21,9 @@ from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserRegion(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_rival.py —— 取用户对手数据(GetUserRivalDataApi / GetUserRivalMusicApi)
|
||||
GetUserRival.py —— 取用户对手数据(GetUserRivalDataApi / GetUserRivalMusicApi)
|
||||
================================================================================
|
||||
|
||||
取玩家的对手名单及其曲目成绩。
|
||||
@@ -28,9 +28,9 @@ from typing import Any, Dict, List, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import (
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import (
|
||||
_get,
|
||||
UserRivalRequestVO,
|
||||
UserRivalMusicRequestVO,
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_shop_stock.py —— 取用户商店库存(GetUserShopStockApi)
|
||||
GetUserShopStock.py —— 取用户商店库存(GetUserShopStockApi)
|
||||
================================================================================
|
||||
|
||||
取玩家在各商店商品的已兑换次数。
|
||||
@@ -24,9 +24,9 @@ from typing import Any, Dict, List, Optional
|
||||
|
||||
from config import CLIENT_ID, PLAY_SHOP_ITEM_IDS, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserShopStockRequestVO
|
||||
from .LogSetup import log_info
|
||||
from .MaimaiNet import MaimaiNetClient
|
||||
from .UserGetterCommon import _get, UserShopStockRequestVO
|
||||
|
||||
|
||||
def GetUserShopStock(
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
log_setup.py —— 统一日志模块(INFO / DEBUG 双模式)
|
||||
LogSetup.py —— 统一日志模块(INFO / DEBUG 双模式)
|
||||
================================================================================
|
||||
|
||||
【两种模式】
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
maimai_net.py —— 舞萌DX 网络核心层(纯 HTTP 复刻)
|
||||
MaimaiNet.py —— 舞萌DX 网络核心层(纯 HTTP 复刻)
|
||||
================================================================================
|
||||
|
||||
本模块是【唯一】直接和游戏服务器说话的地方,其余 api 脚本都通过它收发报文。
|
||||
@@ -133,7 +133,7 @@ AES_IV = b"F>;24DjU9W6ZsRH["
|
||||
#: 用法:URL 尾段 = md5(逻辑接口名 + "MaimaiChn" + OBFUSCATE_PARAM) 小写 hex
|
||||
OBFUSCATE_PARAM = "8bF76dE9"
|
||||
|
||||
from .log_setup import log_debug, log_error, log_request, log_response
|
||||
from .LogSetup import log_debug, log_error, log_request, log_response
|
||||
|
||||
# =============================================================================
|
||||
# 协议常量
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
ping.py —— 心跳探活(PingMaimaiChn)
|
||||
Ping.py —— 心跳探活(PingMaimaiChn)
|
||||
================================================================================
|
||||
|
||||
功能:向游戏服务器发一次心跳,确认链路可达。
|
||||
@@ -41,8 +41,8 @@ from typing import Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info, log_warning
|
||||
from .maimai_net import MaimaiNetClient, request_dict
|
||||
from .LogSetup import log_info, log_warning
|
||||
from .MaimaiNet import MaimaiNetClient, request_dict
|
||||
|
||||
|
||||
def Ping(
|
||||
@@ -80,7 +80,7 @@ def Ping(
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from .log_setup import setup_logging, teardown_logging
|
||||
from .LogSetup import setup_logging, teardown_logging
|
||||
|
||||
def _main() -> int:
|
||||
setup_logging("ping_test")
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
upsert_user_all.py —— 上传成绩(UpsertUserAllApi)
|
||||
UpsertUserAll.py —— 上传成绩(UpsertUserAllApi)
|
||||
================================================================================
|
||||
|
||||
功能:把一局游玩的完整结算数据写进服务器。这是**唯一有副作用**的接口。
|
||||
@@ -158,8 +158,8 @@ PLAY_AFTER_GRADE_RANK = 1
|
||||
#: True = 从曲目记录推(仅调试用)
|
||||
DERIVE_GRADE_FROM_MUSIC_DETAIL = False
|
||||
|
||||
from .log_setup import log_debug, log_error, log_info, log_warning
|
||||
from .maimai_net import (
|
||||
from .LogSetup import log_debug, log_error, log_info, log_warning
|
||||
from .MaimaiNet import (
|
||||
MaimaiNetClient,
|
||||
local_utc_offset_seconds,
|
||||
login_date_time_now,
|
||||
@@ -552,7 +552,7 @@ def build_favorite_delta(
|
||||
"2" = 服务器侧有对应 itemKind(无论内容是否一致)
|
||||
"1" = 服务器侧查不到该 itemKind
|
||||
|
||||
★ 关键:**优先用 Get 侧最原始的响应建索引**,不要先用 get_user_favorite.py
|
||||
★ 关键:**优先用 Get 侧最原始的响应建索引**,不要先用 GetUserFavorite.py
|
||||
的「补 itemKind」逻辑归一化 —— 真机里 itemKind=10 的响应回的是
|
||||
{"itemKind":null, "itemIdList":[]},正因为查不到才自然产生 "1"。
|
||||
|
||||
@@ -1894,6 +1894,117 @@ def apply_play_effects(
|
||||
# 十二、组装 upsertUserAll
|
||||
# =============================================================================
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# ★ 歌曲解锁(解禁):MusicUnlock* 不是独立节点,而是并入 userItemList 的 UserItem
|
||||
# -----------------------------------------------------------------------------
|
||||
# 依据 source/Assembly-CSharp/Manager/UserData.cs:132-138 —— 客户端有四个解锁列表:
|
||||
# MusicUnlockList / MusicUnlockMasterList / MusicUnlockReMasterList / MusicUnlockStrongList
|
||||
# 它们**没有**对应的 userXxxList 节点,导出时统一并进 userItemList:
|
||||
# source/Assembly-CSharp/Net.VO.Mai2/VOExtensions.cs:981-1008
|
||||
# MusicUnlockList -> itemKind = 5
|
||||
# MusicUnlockMasterList -> itemKind = 6
|
||||
# MusicUnlockReMasterList -> itemKind = 7
|
||||
# MusicUnlockStrongList -> itemKind = 8
|
||||
# 条目形如 {"itemKind":5,"itemId":1085,"stock":1,"isValid":true}
|
||||
# (真机 data/12570152.json 的 userItemList 里 kind5=77 条、kind6=446 条、kind7=34 条)
|
||||
#
|
||||
# ★ 难度解锁的「天生免费」规则:
|
||||
# source/Assembly-CSharp/Manager/NotesListManager.cs:415-453 IsUnlockMaster
|
||||
# source/Assembly-CSharp/Manager/NotesListManager.cs:455-494 IsUnlockReMaster
|
||||
# 两个函数开头都有 `if (id < 10000) return true;`
|
||||
# -> musicId < 10000(标准谱)的 Master / Re:Master **天生解锁**,不必写 kind6/7;
|
||||
# 只有 musicId >= 10000(DX 谱)才需要额外解锁。
|
||||
# ★ 另外 Re:Master 还受活动开关约束(IsUnlockReMaster 里的
|
||||
# `if (!instance.IsOpenEvent(subEventId)) return false;`),
|
||||
# 那是机台/服务端侧的开关,脚本改不了 —— 写了 kind7 也可能进游戏仍是灰的。
|
||||
#
|
||||
# ★ 写 kind5 只是「把这首歌标记为已解锁」,不携带任何成绩;
|
||||
# 成绩另由 userMusicDetailList 承载,纯解锁不需要打歌。
|
||||
|
||||
#: UserItem.itemKind 取值(source/Assembly-CSharp/Manager/UserData.cs:132-138)
|
||||
ITEM_KIND_MUSIC_UNLOCK = 5
|
||||
ITEM_KIND_MUSIC_UNLOCK_MASTER = 6
|
||||
ITEM_KIND_MUSIC_UNLOCK_REMASTER = 7
|
||||
ITEM_KIND_MUSIC_UNLOCK_STRONG = 8
|
||||
|
||||
#: 小于这个 musicId 的谱面 Master / Re:Master 天生解锁(NotesListManager.cs:415/455)
|
||||
UNLOCK_MASTER_MIN_MUSIC_ID = 10000
|
||||
|
||||
|
||||
def build_unlock_items(
|
||||
music_ids: Sequence[int],
|
||||
existing: Optional[Sequence[Any]] = None,
|
||||
*,
|
||||
master: bool = True,
|
||||
remaster: bool = True,
|
||||
unlock_base: bool = True,
|
||||
) -> Tuple[List[Dict[str, Any]], List[str]]:
|
||||
"""
|
||||
构造「解锁歌曲 / 解禁难度」要追加的 userItemList 条目。
|
||||
|
||||
★ 只返回**服务器上还没有**的条目;已存在的跳过(重复插入会让服务端报错)。
|
||||
|
||||
参数:
|
||||
music_ids: 要解锁的 musicId 列表(重复的自动去掉)
|
||||
existing: 服务器现有的 userItemList(GetUserData 里的那份),
|
||||
用来判断哪些条目已经存在
|
||||
master: True = 同时解锁 Master 难度(仅 musicId >= 10000 才写 kind6)
|
||||
remaster: True = 同时解锁 Re:Master 难度(仅 musicId >= 10000 才写 kind7)
|
||||
unlock_base: True = 解锁歌曲本体(kind5)
|
||||
|
||||
返回:
|
||||
(条目列表, 说明文字列表)
|
||||
条目形如 {"itemKind":5,"itemId":1085,"stock":1,"isValid":true}
|
||||
"""
|
||||
have: set = set()
|
||||
for it in existing or []:
|
||||
if isinstance(it, dict):
|
||||
try:
|
||||
have.add((int(it.get("itemKind")), int(it.get("itemId"))))
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
|
||||
ids: List[int] = []
|
||||
for raw in music_ids or []:
|
||||
try:
|
||||
mid = int(raw)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if mid <= 0 or mid in ids:
|
||||
continue
|
||||
ids.append(mid)
|
||||
|
||||
items: List[Dict[str, Any]] = []
|
||||
notes: List[str] = []
|
||||
|
||||
def _add(kind: int, mid: int, what: str) -> None:
|
||||
if (kind, mid) in have:
|
||||
notes.append("解锁检查:musicId=" + str(mid) + " 的" + what
|
||||
+ "已经解锁(itemKind=" + str(kind) + "),跳过")
|
||||
return
|
||||
have.add((kind, mid))
|
||||
items.append({"itemKind": kind, "itemId": mid, "stock": 1, "isValid": True})
|
||||
notes.append("解锁:musicId=" + str(mid) + " " + what
|
||||
+ "(itemKind=" + str(kind) + ")")
|
||||
|
||||
for mid in ids:
|
||||
if unlock_base:
|
||||
_add(ITEM_KIND_MUSIC_UNLOCK, mid, "歌曲本体")
|
||||
if mid < UNLOCK_MASTER_MIN_MUSIC_ID:
|
||||
# ★ 标准谱(musicId < 10000)Master/Re:Master 天生解锁,不需要条目
|
||||
if master or remaster:
|
||||
notes.append("解锁检查:musicId=" + str(mid)
|
||||
+ " 是标准谱(< " + str(UNLOCK_MASTER_MIN_MUSIC_ID)
|
||||
+ "),Master/Re:Master 天生解锁,跳过")
|
||||
continue
|
||||
if master:
|
||||
_add(ITEM_KIND_MUSIC_UNLOCK_MASTER, mid, "Master 难度")
|
||||
if remaster:
|
||||
_add(ITEM_KIND_MUSIC_UNLOCK_REMASTER, mid, "Re:Master 难度")
|
||||
|
||||
return items, notes
|
||||
|
||||
|
||||
def build_user_all(
|
||||
snapshot: Optional[Any] = None,
|
||||
play: Optional[PlayData] = None,
|
||||
@@ -1909,6 +2020,9 @@ def build_user_all(
|
||||
playlog_id: int = 0,
|
||||
play_date_time: Optional[int] = None,
|
||||
extra_data: Optional[Any] = None,
|
||||
unlock_music_ids: Optional[Sequence[int]] = None,
|
||||
unlock_master: bool = True,
|
||||
unlock_remaster: bool = True,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
组装 upsertUserAll 的 36 个节点。
|
||||
@@ -1932,6 +2046,10 @@ def build_user_all(
|
||||
playlog_id: UserLoginApi 的 loginId(用于 userGamePlaylogList)
|
||||
extra_data: ★ 额外 JSON(dict / JSON 字符串 / 文件路径),
|
||||
自动匹配插入 36 节点,**优先级高于 patch**。见 merge_extra_data。
|
||||
unlock_music_ids: ★ 要解锁(解禁)的 musicId 列表。见 build_unlock_items。
|
||||
写进 userItemList:kind5=歌曲本体、kind6=Master、kind7=Re:Master。
|
||||
unlock_master: 解锁时是否一并解禁 Master(仅 DX 谱 musicId >= 10000 生效)
|
||||
unlock_remaster: 解锁时是否一并解禁 Re:Master(同上)
|
||||
|
||||
返回:
|
||||
36 节点的 dict
|
||||
@@ -2039,6 +2157,34 @@ def build_user_all(
|
||||
for note in merge_extra_data(user_all, extra_data):
|
||||
log_info(note)
|
||||
|
||||
# ---- ★ 歌曲解锁(解禁)----
|
||||
# 放在 extra_data 之后:extra_data 可能整体替换了 userItemList,
|
||||
# 解锁条目追加到最终列表末尾,位串补 "1"(= 服务端侧新增该 item)。
|
||||
# 依据见本文件「歌曲解锁」注释块。
|
||||
if unlock_music_ids:
|
||||
unlock_items, unlock_notes = build_unlock_items(
|
||||
unlock_music_ids,
|
||||
existing=user_all.get("userItemList") or [],
|
||||
master=unlock_master,
|
||||
remaster=unlock_remaster,
|
||||
)
|
||||
for note in unlock_notes:
|
||||
log_info(note)
|
||||
if unlock_items:
|
||||
old_items = list(user_all.get("userItemList") or [])
|
||||
old_bits = user_all.get("isNewItemList")
|
||||
if not isinstance(old_bits, str):
|
||||
old_bits = ""
|
||||
if len(old_bits) != len(old_items):
|
||||
# 先对齐到旧列表长度,避免新条目被 sync_bits_to_arrays 补成 "0"
|
||||
old_bits = (old_bits + "0" * len(old_items))[:len(old_items)]
|
||||
user_all["userItemList"] = old_items + unlock_items
|
||||
user_all["isNewItemList"] = old_bits + "1" * len(unlock_items)
|
||||
log_info("★ 本次解锁共新增 " + str(len(unlock_items))
|
||||
+ " 个 userItem 条目(位串补 " + "1" * len(unlock_items) + ")")
|
||||
else:
|
||||
log_info("解锁检查:指定的歌曲/难度在服务器上都已经解锁,无需改动")
|
||||
|
||||
# ---- 清理内部字段 ----
|
||||
for key in list(user_all.keys()):
|
||||
if key.startswith("_"):
|
||||
@@ -2350,16 +2496,16 @@ def _resolve_api_func(name: str, fallback: Any = None) -> Any:
|
||||
def _lazy(name: str) -> Any:
|
||||
"""按名字延迟取 api 子模块里的原始函数(_resolve_api_func 的兜底)。"""
|
||||
if name == "GetNeedUserData":
|
||||
from .get_need_user_data import GetNeedUserData
|
||||
from .GetNeedUserData import GetNeedUserData
|
||||
return GetNeedUserData
|
||||
if name == "GetUserNewItemList":
|
||||
from .get_user_new_item import GetUserNewItemList
|
||||
from .GetUserNewItem import GetUserNewItemList
|
||||
return GetUserNewItemList
|
||||
if name == "GetUserData":
|
||||
from .get_user_data import GetUserData
|
||||
from .GetUserData import GetUserData
|
||||
return GetUserData
|
||||
if name == "GetUserMusicDetailAll":
|
||||
from .get_user_music import GetUserMusicDetailAll
|
||||
from .GetUserMusic import GetUserMusicDetailAll
|
||||
return GetUserMusicDetailAll
|
||||
raise PatchError("未知的内部 api:" + str(name))
|
||||
|
||||
@@ -2478,6 +2624,9 @@ def UpsertUserAll(
|
||||
auto_characters: bool = True,
|
||||
play: Optional[PlayData] = None,
|
||||
extra_data: Optional[Any] = None,
|
||||
unlock_music_ids: Optional[Sequence[int]] = None,
|
||||
unlock_master: bool = True,
|
||||
unlock_remaster: bool = True,
|
||||
patch: Optional[Dict[str, Any]] = None,
|
||||
user_playlog_list: Optional[List[Any]] = None,
|
||||
pre_new_item_list: bool = True,
|
||||
@@ -2533,6 +2682,14 @@ def UpsertUserAll(
|
||||
"isNewKaleidxScopeList": "1"}
|
||||
优先级最高(覆盖快照演算出的同名字段)。
|
||||
没给位串时会按数组长度补 "0" 并给出提示。
|
||||
unlock_music_ids: ★ 要解锁(解禁)的 musicId 列表,例如 [1085, 11414]。
|
||||
写进 userItemList:kind5=歌曲本体、kind6=Master、kind7=Re:Master。
|
||||
★ musicId < 10000(标准谱)的 Master/Re:Master 天生解锁,
|
||||
只写 kind5;只有 DX 谱(>= 10000)才写 kind6/7。
|
||||
★ 已存在的条目自动跳过(重复插入服务端会报错)。
|
||||
见 build_unlock_items 上方的源码依据。
|
||||
unlock_master: 解锁时是否一并解禁 Master 难度(默认 True)
|
||||
unlock_remaster: 解锁时是否一并解禁 Re:Master 难度(默认 True)
|
||||
patch: 同 extra_data 的旧接口(只收 dict,不补位串提示)。
|
||||
user_playlog_list: 本局成绩明细(可选;不传则用 play 自动构造一条)
|
||||
pre_new_item_list: True(默认)= 发包前先调 GetUserNewItemListApi
|
||||
@@ -2624,6 +2781,8 @@ def UpsertUserAll(
|
||||
# ---------- 阶段 5:组装 upsertUserAll(快照基线 + 本局演算 + 差分)----------
|
||||
user_all = build_user_all(
|
||||
snapshot=snapshot, play=p, patch=patch, extra_data=extra_data,
|
||||
unlock_music_ids=unlock_music_ids,
|
||||
unlock_master=unlock_master, unlock_remaster=unlock_remaster,
|
||||
login_date_time=login_date_time, auth_date_time=auth_date_time,
|
||||
place_id=place_id, place_name=place_name,
|
||||
region_id=region_id, region_name=region_name, client_id=client_id,
|
||||
@@ -2709,7 +2868,7 @@ def UpsertUserAll(
|
||||
# =============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
from .log_setup import setup_logging, teardown_logging
|
||||
from .LogSetup import setup_logging, teardown_logging
|
||||
|
||||
def _main() -> int:
|
||||
setup_logging("upsert_user_all_test")
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""
|
||||
upsert_user_chargelog.py —— 购买功能票 / 上传购买记录(UpsertUserChargelogApi)
|
||||
UpsertUserChargelog.py —— 购买功能票 / 上传购买记录(UpsertUserChargelogApi)
|
||||
================================================================================
|
||||
|
||||
功能:把「买了一张功能票」这件事上报给服务器,并把该票的持有数 +1。
|
||||
@@ -140,9 +140,15 @@ PAID_TICKET_IDS = (2, 3, 4, 5, 6)
|
||||
TICKET_VALID_HOUR_OFFSET = -4
|
||||
TICKET_VALID_DAYS = 90
|
||||
|
||||
from .log_setup import log_debug, log_error, log_info, log_warning
|
||||
from .maimai_net import MaimaiNetClient, request_dict
|
||||
from .upsert_user_all import UpsertResponseVO, resolve_api_func
|
||||
from .ChargeGuard import (
|
||||
has_fresh_read,
|
||||
last_read_age,
|
||||
note_charge_purchase,
|
||||
purchased_charge_ids,
|
||||
)
|
||||
from .LogSetup import log_debug, log_error, log_info, log_warning
|
||||
from .MaimaiNet import MaimaiNetClient, request_dict
|
||||
from .UpsertUserAll import UpsertResponseVO, resolve_api_func
|
||||
|
||||
|
||||
class ChargelogConfirmError(RuntimeError):
|
||||
@@ -334,6 +340,8 @@ def UpsertUserChargelog(
|
||||
client_id: str = CLIENT_ID,
|
||||
guard_no_stock: bool = True,
|
||||
guard_purchasable: bool = True,
|
||||
guard_require_charge_read: bool = True,
|
||||
guard_single_purchase: bool = True,
|
||||
confirm: Optional[bool] = None,
|
||||
dry_run: bool = False,
|
||||
timeout_ms: Optional[int] = None,
|
||||
@@ -368,6 +376,16 @@ def UpsertUserChargelog(
|
||||
(抛 ChargelogGuardError,未发包)。
|
||||
理由见 ChargelogGuardError 的 docstring —— 有封号风险。
|
||||
确认要强行购买才传 False。
|
||||
guard_require_charge_read:
|
||||
★ 默认 True = **运行本函数之前必须先调用过 GetUserCharge**
|
||||
(本进程内、10 分钟内),否则直接拒绝购买。
|
||||
目的:防止「没查票就买票」造成同一账号重复购买。
|
||||
显式传了 charge_list 也算「查过了」。
|
||||
实现见 api/ChargeGuard.py。
|
||||
guard_single_purchase:
|
||||
★ 默认 True = **同一账号在同一进程里只允许买一次票**。
|
||||
真机买完票会立刻被本局用掉(stock 恒为 0),
|
||||
连着买两张属于异常行为。实现见 api/ChargeGuard.py。
|
||||
confirm: 副作用闸门;None 时取 config.UPSERT_CHARGELOG_CONFIRM
|
||||
dry_run: True = 只构造并返回报文,绝不发包
|
||||
|
||||
@@ -387,6 +405,34 @@ def UpsertUserChargelog(
|
||||
log_warning("chargeId=" + str(charge_id) + " 不在 PAID_TICKET_IDS="
|
||||
+ str(tuple(PAID_TICKET_IDS)) + " 里 —— 它可能不是可购买的付费票")
|
||||
|
||||
# ---------- 0. ★ 前置闸门:必须先跑过 GetUserCharge ----------
|
||||
# ★ 为什么放在「取当前状态」之前:下面那一步在 charge_list 为空时会
|
||||
# 自己调一次 GetUserCharge —— 闸门若放在后面,它自己就把自己放行了。
|
||||
if guard_require_charge_read and charge_list is None:
|
||||
if not has_fresh_read(user_id):
|
||||
msg = ("安全检查未通过:本次购买之前**没有调用过 GetUserCharge**。\n"
|
||||
" 买票前必须先查一次账号当前票券(有没有持票),否则无法排除"
|
||||
"「重复购买」——服务端看到的是状态矛盾,有**封号风险**。\n"
|
||||
" 正确顺序:GetUserCharge(user_id) -> UpsertUserChargelog(...)\n"
|
||||
" 或者直接把查到的票券列表传进来:charge_list=[...]\n"
|
||||
" 见 api/ChargeGuard.py。")
|
||||
log_error(msg)
|
||||
raise ChargelogGuardError(msg)
|
||||
log_info("前置检查通过:本次购买前已调用 GetUserCharge("
|
||||
+ str(round(float(last_read_age(user_id) or 0.0), 1)) + " 秒前)")
|
||||
|
||||
# ---------- 0.5 ★ 前置闸门:同一账号本进程内只允许买一次 ----------
|
||||
if guard_single_purchase:
|
||||
done = purchased_charge_ids(user_id)
|
||||
if done:
|
||||
msg = ("安全检查未通过:本进程内该账号(user_id=" + str(user_id)
|
||||
+ ")**已经买过功能票**(chargeId=" + str(done) + ")。\n"
|
||||
" 真机买完票会立刻被这一局用掉,正常状态 stock 恒为 0;"
|
||||
"连着买两张属于异常行为,有**封号风险**。\n"
|
||||
" 确需强行购买请传 guard_single_purchase=False。")
|
||||
log_error(msg)
|
||||
raise ChargelogGuardError(msg)
|
||||
|
||||
# ---------- 1. 取当前状态 ----------
|
||||
if charge_list is None:
|
||||
log_info("未传 charge_list,自动抓取当前票券 ...")
|
||||
@@ -540,6 +586,8 @@ def UpsertUserChargelog(
|
||||
|
||||
log_info("购买成功:returnCode=" + str(resp.returnCode) + " apiName=" + resp.apiName)
|
||||
log_info(" 票名 " + name + " 库存 " + str(old_stock) + " -> " + str(stock))
|
||||
# ★ 记一笔「这个账号本进程已经买过票」,同进程再买会被前置闸门拦下
|
||||
note_charge_purchase(user_id, charge_id)
|
||||
return resp
|
||||
|
||||
|
||||
@@ -548,7 +596,7 @@ def UpsertUserChargelog(
|
||||
# =============================================================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
from .log_setup import setup_logging, teardown_logging
|
||||
from .LogSetup import setup_logging, teardown_logging
|
||||
|
||||
def _main() -> int:
|
||||
setup_logging("chargelog_test")
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
user_getter_common.py —— 各取数脚本的公共底座
|
||||
UserGetterCommon.py —— 各取数脚本的公共底座
|
||||
================================================================================
|
||||
|
||||
只提供三样东西,取数逻辑都在各自的脚本里:
|
||||
@@ -36,8 +36,8 @@ from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_debug, log_warning
|
||||
from .maimai_net import MaimaiNetClient, get_default_client
|
||||
from .LogSetup import log_debug, log_warning
|
||||
from .MaimaiNet import MaimaiNetClient, get_default_client
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
userlogin.py —— 用户正式登录(UserLoginApi)
|
||||
UserLogin.py —— 用户正式登录(UserLoginApi)
|
||||
================================================================================
|
||||
|
||||
功能:创建登录会话,返回 loginId / loginDateTime / 登录天数统计。
|
||||
@@ -48,8 +48,8 @@ from typing import Optional
|
||||
|
||||
from config import CLIENT_ID, PLACE_ID, REGION_ID, TIMEOUT_LONG_MS
|
||||
|
||||
from .log_setup import log_debug, log_error, log_info
|
||||
from .maimai_net import (
|
||||
from .LogSetup import log_debug, log_error, log_info
|
||||
from .MaimaiNet import (
|
||||
MaimaiNetClient,
|
||||
local_utc_offset_seconds,
|
||||
login_date_time_now,
|
||||
@@ -198,7 +198,7 @@ def UserLogin(
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
from .log_setup import setup_logging, teardown_logging
|
||||
from .LogSetup import setup_logging, teardown_logging
|
||||
|
||||
def _main() -> int:
|
||||
setup_logging("userlogin_test")
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
userlogout.py —— 用户登出(UserLogoutApi)
|
||||
UserLogout.py —— 用户登出(UserLogoutApi)
|
||||
================================================================================
|
||||
|
||||
功能:结束登录会话,可选上报本次会话的网络统计(delayLog)。
|
||||
@@ -39,8 +39,8 @@ from typing import Any, Dict, List, Optional
|
||||
|
||||
from config import CLIENT_ID, PLACE_ID, REGION_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_debug, log_error, log_info
|
||||
from .maimai_net import MaimaiNetClient, request_dict
|
||||
from .LogSetup import log_debug, log_error, log_info
|
||||
from .MaimaiNet import MaimaiNetClient, request_dict
|
||||
|
||||
#: DelayLog.request 的定长(DelayLog.cs:17)
|
||||
DELAY_LOG_SLOTS = 26
|
||||
@@ -228,7 +228,7 @@ def UserLogout(
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
from .log_setup import setup_logging, teardown_logging
|
||||
from .LogSetup import setup_logging, teardown_logging
|
||||
|
||||
def _main() -> int:
|
||||
setup_logging("userlogout_test")
|
||||
+111
-78
@@ -20,59 +20,60 @@ api 包 —— 统一导出
|
||||
目录结构(一个 api 一个脚本)
|
||||
--------------------------------------------------------------------------------
|
||||
基础设施
|
||||
log_setup.py 统一日志(INFO / DEBUG 双模式)
|
||||
maimai_net.py 网络核心(AES / deflate / URL 混淆 / Cookie)
|
||||
user_getter_common.py 取数脚本公共底座(请求 VO / _get / 翻页)
|
||||
LogSetup.py 统一日志(INFO / DEBUG 双模式)
|
||||
MaimaiNet.py 网络核心(AES / deflate / URL 混淆 / Cookie)
|
||||
UserGetterCommon.py 取数脚本公共底座(请求 VO / _get / 翻页)
|
||||
ChargeGuard.py ★ 买票前置闸门:买票前必须先跑过 GetUserCharge
|
||||
|
||||
会话链路
|
||||
chime.py 扫码登录 -> userId + token
|
||||
get_game_settings.py GetGameSettingApi(游戏设置)
|
||||
ping.py 心跳探活
|
||||
get_user_preview.py GetUserPreviewApi(用户预览)
|
||||
userlogin.py UserLoginApi(正式登录 -> loginId + loginDateTime)
|
||||
userlogout.py UserLogoutApi(登出)
|
||||
Chime.py 扫码登录 -> userId + token
|
||||
GetGameSettings.py GetGameSettingApi(游戏设置)
|
||||
Ping.py 心跳探活
|
||||
GetUserPreview.py GetUserPreviewApi(用户预览)
|
||||
UserLogin.py UserLoginApi(正式登录 -> loginId + loginDateTime)
|
||||
UserLogout.py UserLogoutApi(登出)
|
||||
|
||||
上传 / 结算(有副作用的接口)
|
||||
upsert_user_all.py ★ UpsertUserAllApi(上传成绩)
|
||||
upsert_user_chargelog.py UpsertUserChargelogApi(购买功能票)
|
||||
get_need_user_data.py ★ 结算快照编排(统一调用下面的取数脚本),
|
||||
UpsertUserAll.py ★ UpsertUserAllApi(上传成绩 + 解锁歌曲/解禁难度)
|
||||
UpsertUserChargelog.py UpsertUserChargelogApi(购买功能票)
|
||||
GetNeedUserData.py ★ 结算快照编排(统一调用下面的取数脚本),
|
||||
并把结果落盘到 <工程根>/data/<userId>.json
|
||||
|
||||
★ 命名约定:服务端 API 名里带 Get 的,**脚本名与函数名一律带 get / Get**
|
||||
(不要模棱两可)。所以下面这些文件全是 get_ 开头。
|
||||
|
||||
取数 —— GetUser*(一个接口一个脚本)
|
||||
get_user_data.py GetUserDataApi
|
||||
get_user_extend.py GetUserExtendApi
|
||||
get_user_option.py GetUserOptionApi
|
||||
get_user_character.py GetUserCharacterApi
|
||||
get_user_ghost.py GetUserGhostApi
|
||||
get_user_map.py GetUserMapApi
|
||||
get_user_login_bonus.py GetUserLoginBonusApi
|
||||
get_user_rating.py GetUserRatingApi
|
||||
get_user_item.py GetUserItemApi
|
||||
get_user_music.py GetUserMusicApi
|
||||
get_user_course.py GetUserCourseApi
|
||||
get_user_charge.py GetUserChargeApi
|
||||
get_user_favorite.py GetUserFavoriteApi
|
||||
get_user_activity.py GetUserActivityApi
|
||||
get_user_mission.py GetUserMissionDataApi
|
||||
get_user_intimate.py GetUserIntimateApi
|
||||
get_user_shop_stock.py GetUserShopStockApi
|
||||
get_user_favorite_item.py GetUserFavoriteItemApi
|
||||
get_user_rival.py GetUserRivalDataApi / GetUserRivalMusicApi
|
||||
get_user_friend_bonus.py GetUserFriendBonusApi
|
||||
get_user_kaleidx_scope.py GetUserKaleidxScopeApi / GetGameKaleidxScopeApi
|
||||
get_user_region.py GetUserRegionApi
|
||||
get_user_recommend_music.py GetUserRecommendRateMusicApi / GetUserRecommendSelectMusicApi
|
||||
get_user_new_item.py GetUserNewItemListApi
|
||||
GetUserData.py GetUserDataApi
|
||||
GetUserExtend.py GetUserExtendApi
|
||||
GetUserOption.py GetUserOptionApi
|
||||
GetUserCharacter.py GetUserCharacterApi
|
||||
GetUserGhost.py GetUserGhostApi
|
||||
GetUserMap.py GetUserMapApi
|
||||
GetUserLoginBonus.py GetUserLoginBonusApi
|
||||
GetUserRating.py GetUserRatingApi
|
||||
GetUserItem.py GetUserItemApi
|
||||
GetUserMusic.py GetUserMusicApi
|
||||
GetUserCourse.py GetUserCourseApi
|
||||
GetUserCharge.py GetUserChargeApi
|
||||
GetUserFavorite.py GetUserFavoriteApi
|
||||
GetUserActivity.py GetUserActivityApi
|
||||
GetUserMission.py GetUserMissionDataApi
|
||||
GetUserIntimate.py GetUserIntimateApi
|
||||
GetUserShopStock.py GetUserShopStockApi
|
||||
GetUserFavoriteItem.py GetUserFavoriteItemApi
|
||||
GetUserRival.py GetUserRivalDataApi / GetUserRivalMusicApi
|
||||
GetUserFriendBonus.py GetUserFriendBonusApi
|
||||
GetUserKaleidxScope.py GetUserKaleidxScopeApi / GetGameKaleidxScopeApi
|
||||
GetUserRegion.py GetUserRegionApi
|
||||
GetUserRecommendMusic.py GetUserRecommendRateMusicApi / GetUserRecommendSelectMusicApi
|
||||
GetUserNewItem.py GetUserNewItemListApi
|
||||
|
||||
取数 —— GetGame*
|
||||
get_game_settings.py GetGameSettingApi
|
||||
get_game_charge.py GetGameChargeApi
|
||||
GetGameSettings.py GetGameSettingApi
|
||||
GetGameCharge.py GetGameChargeApi
|
||||
"""
|
||||
|
||||
from .log_setup import (
|
||||
from .LogSetup import (
|
||||
# 会话
|
||||
setup_logging,
|
||||
teardown_logging,
|
||||
@@ -107,8 +108,8 @@ from .log_setup import (
|
||||
# 1) 网络核心
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from .maimai_net import (
|
||||
# 协议常量(原在 config.py,已下沉到 maimai_net.py)
|
||||
from .MaimaiNet import (
|
||||
# 协议常量(原在 config.py,已下沉到 MaimaiNet.py)
|
||||
BASE_URL,
|
||||
AES_KEY,
|
||||
AES_IV,
|
||||
@@ -141,9 +142,9 @@ from .maimai_net import (
|
||||
# 2) 会话链路
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from .chime import (
|
||||
from .Chime import (
|
||||
ParseQRCode,
|
||||
# aimedb / QR 常量(原在 config.py,已下沉到 chime.py)
|
||||
# aimedb / QR 常量(原在 config.py,已下沉到 Chime.py)
|
||||
AIMEDB_HOST,
|
||||
AIMEDB_PORT,
|
||||
AIMEDB_PATH,
|
||||
@@ -153,7 +154,7 @@ from .chime import (
|
||||
QR_USER_AGENT,
|
||||
)
|
||||
|
||||
from .get_game_settings import (
|
||||
from .GetGameSettings import (
|
||||
GetGameSettings,
|
||||
GameSetting,
|
||||
GameSettingRequestVO,
|
||||
@@ -162,21 +163,21 @@ from .get_game_settings import (
|
||||
is_ping_disabled,
|
||||
)
|
||||
|
||||
from .ping import Ping
|
||||
from .Ping import Ping
|
||||
|
||||
from .get_user_preview import (
|
||||
from .GetUserPreview import (
|
||||
GetUserPreview,
|
||||
UserPreviewRequestVO,
|
||||
UserPreviewResponseVO,
|
||||
)
|
||||
|
||||
from .userlogin import (
|
||||
from .UserLogin import (
|
||||
UserLogin,
|
||||
UserLoginRequestVO,
|
||||
UserLoginResponseVO,
|
||||
)
|
||||
|
||||
from .userlogout import (
|
||||
from .UserLogout import (
|
||||
UserLogout,
|
||||
LogoutType,
|
||||
DelayLog,
|
||||
@@ -193,7 +194,7 @@ from .userlogout import (
|
||||
# 3) 上传成绩
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from .upsert_user_all import (
|
||||
from .UpsertUserAll import (
|
||||
UpsertUserAll,
|
||||
PlayData,
|
||||
build_play_from_config,
|
||||
@@ -236,6 +237,13 @@ from .upsert_user_all import (
|
||||
load_extra_data,
|
||||
merge_extra_data,
|
||||
verify_upsert_landing,
|
||||
# ★ 歌曲解锁(解禁):写进 userItemList 的 itemKind 5/6/7
|
||||
build_unlock_items,
|
||||
ITEM_KIND_MUSIC_UNLOCK,
|
||||
ITEM_KIND_MUSIC_UNLOCK_MASTER,
|
||||
ITEM_KIND_MUSIC_UNLOCK_REMASTER,
|
||||
ITEM_KIND_MUSIC_UNLOCK_STRONG,
|
||||
UNLOCK_MASTER_MIN_MUSIC_ID,
|
||||
default_user_weekly_data,
|
||||
default_user_2p_playlog,
|
||||
calc_play_special,
|
||||
@@ -255,7 +263,7 @@ from .upsert_user_all import (
|
||||
USER_MISSION_DROP_KEYS,
|
||||
)
|
||||
|
||||
from .get_need_user_data import (
|
||||
from .GetNeedUserData import (
|
||||
GetNeedUserData,
|
||||
save_snapshot,
|
||||
load_snapshot,
|
||||
@@ -270,8 +278,24 @@ from .get_need_user_data import (
|
||||
list_saved_user_ids,
|
||||
)
|
||||
|
||||
from .upsert_user_chargelog import (
|
||||
# 票券主数据(原在 config.py,已下沉到 upsert_user_chargelog.py)
|
||||
# ---------------------------------------------------------------------------
|
||||
# ★ 买票前置闸门:GetUserCharge 必须先跑过(见 api/ChargeGuard.py)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from .ChargeGuard import (
|
||||
CHARGE_READ_FRESH_SEC,
|
||||
has_fresh_read,
|
||||
held_charges,
|
||||
last_charge_list,
|
||||
last_read_age,
|
||||
note_charge_purchase,
|
||||
note_user_charge_read,
|
||||
purchased_charge_ids,
|
||||
reset as reset_charge_guard,
|
||||
)
|
||||
|
||||
from .UpsertUserChargelog import (
|
||||
# 票券主数据(原在 config.py,已下沉到 UpsertUserChargelog.py)
|
||||
TICKET_CREDIT_NUM,
|
||||
TICKET_NAMES,
|
||||
PAID_TICKET_IDS,
|
||||
@@ -294,7 +318,7 @@ from .upsert_user_chargelog import (
|
||||
# 4) 取数公共底座
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from .user_getter_common import (
|
||||
from .UserGetterCommon import (
|
||||
EmptyRequestVO as GetterEmptyRequestVO,
|
||||
UserIdRequestVO as GetterUserIdRequestVO,
|
||||
UserIdPageRequestVO,
|
||||
@@ -318,37 +342,37 @@ from .user_getter_common import (
|
||||
# 5) 取数脚本(一个接口一个脚本)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from .get_user_data import GetUserData
|
||||
from .get_user_extend import GetUserExtend
|
||||
from .get_user_option import GetUserOption
|
||||
from .get_user_character import GetUserCharacter
|
||||
from .get_user_ghost import GetUserGhost
|
||||
from .get_user_map import GetUserMap, GetUserMapAll
|
||||
from .get_user_login_bonus import GetUserLoginBonus, GetUserLoginBonusAll
|
||||
from .get_user_rating import GetUserRating
|
||||
from .get_user_item import GetUserItem, GetUserItemAll
|
||||
from .get_user_music import GetUserMusicDetail, GetUserMusicDetailAll
|
||||
from .get_user_course import GetUserCourse, GetUserCourseAll
|
||||
from .get_user_charge import GetUserCharge
|
||||
from .get_user_favorite import GetUserFavorite, GetUserFavoriteAll
|
||||
from .get_user_activity import GetUserActivity
|
||||
from .get_user_mission import GetUserMissionData
|
||||
from .get_user_intimate import GetUserIntimate
|
||||
from .get_user_shop_stock import GetUserShopStock
|
||||
from .get_user_favorite_item import (
|
||||
from .GetUserData import GetUserData
|
||||
from .GetUserExtend import GetUserExtend
|
||||
from .GetUserOption import GetUserOption
|
||||
from .GetUserCharacter import GetUserCharacter
|
||||
from .GetUserGhost import GetUserGhost
|
||||
from .GetUserMap import GetUserMap, GetUserMapAll
|
||||
from .GetUserLoginBonus import GetUserLoginBonus, GetUserLoginBonusAll
|
||||
from .GetUserRating import GetUserRating
|
||||
from .GetUserItem import GetUserItem, GetUserItemAll
|
||||
from .GetUserMusic import GetUserMusicDetail, GetUserMusicDetailAll
|
||||
from .GetUserCourse import GetUserCourse, GetUserCourseAll
|
||||
from .GetUserCharge import GetUserCharge
|
||||
from .GetUserFavorite import GetUserFavorite, GetUserFavoriteAll
|
||||
from .GetUserActivity import GetUserActivity
|
||||
from .GetUserMission import GetUserMissionData
|
||||
from .GetUserIntimate import GetUserIntimate
|
||||
from .GetUserShopStock import GetUserShopStock
|
||||
from .GetUserFavoriteItem import (
|
||||
GetUserFavoriteItem,
|
||||
GetUserFavoriteItemAll,
|
||||
FAVORITE_ITEM_KIND_MUSIC,
|
||||
FAVORITE_ITEM_KIND_RIVAL,
|
||||
)
|
||||
from .get_user_rival import GetUserRivalData, GetUserRivalMusic, GetUserRivalMusicAll
|
||||
from .get_user_friend_bonus import GetUserFriendBonus, extract_gift_points
|
||||
from .get_user_kaleidx_scope import GetUserKaleidxScope, GetGameKaleidxScope
|
||||
from .get_user_region import GetUserRegion
|
||||
from .get_user_recommend_music import GetUserRecommendRateMusic, GetUserRecommendSelectMusic
|
||||
from .get_user_new_item import GetUserNewItemList, UserNewItemListRequestVO
|
||||
from .GetUserRival import GetUserRivalData, GetUserRivalMusic, GetUserRivalMusicAll
|
||||
from .GetUserFriendBonus import GetUserFriendBonus, extract_gift_points
|
||||
from .GetUserKaleidxScope import GetUserKaleidxScope, GetGameKaleidxScope
|
||||
from .GetUserRegion import GetUserRegion
|
||||
from .GetUserRecommendMusic import GetUserRecommendRateMusic, GetUserRecommendSelectMusic
|
||||
from .GetUserNewItem import GetUserNewItemList, UserNewItemListRequestVO
|
||||
|
||||
from .get_game_charge import GetGameCharge, GameChargeRequestVO
|
||||
from .GetGameCharge import GetGameCharge, GameChargeRequestVO
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 6) 需要日志跟踪的 api 函数(新增 api 时把函数名加到这里)
|
||||
@@ -369,6 +393,7 @@ _API_FUNCS = (
|
||||
"merge_extra_data",
|
||||
"verify_upsert_landing",
|
||||
"build_user_all",
|
||||
"build_unlock_items",
|
||||
# --- 购买功能票 ---
|
||||
"UpsertUserChargelog",
|
||||
"ticket_name",
|
||||
@@ -471,8 +496,16 @@ __all__ = [
|
||||
# ---- 上传成绩 ----
|
||||
"UpsertUserAll", "PlayData", "build_play_from_config", "derive_play_characters",
|
||||
"load_extra_data", "merge_extra_data", "verify_upsert_landing",
|
||||
# ---- 歌曲解锁(解禁)----
|
||||
"build_unlock_items", "UNLOCK_MASTER_MIN_MUSIC_ID",
|
||||
"ITEM_KIND_MUSIC_UNLOCK", "ITEM_KIND_MUSIC_UNLOCK_MASTER",
|
||||
"ITEM_KIND_MUSIC_UNLOCK_REMASTER", "ITEM_KIND_MUSIC_UNLOCK_STRONG",
|
||||
# ---- 购买功能票 ----
|
||||
"UpsertUserChargelog", "ChargelogConfirmError", "ChargelogGuardError",
|
||||
# ---- 买票前置闸门(GetUserCharge 必须先跑过)----
|
||||
"CHARGE_READ_FRESH_SEC", "has_fresh_read", "held_charges", "last_charge_list",
|
||||
"last_read_age", "note_charge_purchase", "note_user_charge_read",
|
||||
"purchased_charge_ids", "reset_charge_guard",
|
||||
"UserChargeVO", "UserChargelogVO", "UserChargelogRequestVO",
|
||||
"ticket_name", "ticket_price", "calc_valid_date", "parse_wall_time", "now_cst",
|
||||
"UserAllRequestVO", "UpsertResponseVO",
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
get_user_charge.py —— 取用户票券(GetUserChargeApi)
|
||||
================================================================================
|
||||
|
||||
取玩家的票券持有情况(真机 3 条)。
|
||||
|
||||
对应接口:GetUserChargeApi
|
||||
请求体 :UserIdRequestVO
|
||||
响应 :{userId, length, userChargeList:[{chargeId,stock,purchaseDate,validDate,extNum1}]}
|
||||
依据 :source/Assembly-CSharp/Net.VO.Mai2/UserChargeRequestVO.cs
|
||||
|
||||
★ 回传 Upsert 时要丢掉 extNum1(真机 Upsert 报文里没有这个字段)。
|
||||
|
||||
调用示例:
|
||||
from api import GetUserCharge
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from config import CLIENT_ID, TIMEOUT_MS
|
||||
|
||||
from .log_setup import log_info
|
||||
from .maimai_net import MaimaiNetClient
|
||||
from .user_getter_common import _get, UserIdRequestVO
|
||||
|
||||
|
||||
def GetUserCharge(
|
||||
user_id: int,
|
||||
*,
|
||||
client_id: str = CLIENT_ID,
|
||||
timeout_ms: Optional[int] = None,
|
||||
client: Optional[MaimaiNetClient] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
取用户票券(对应 GetUserChargeApi)。
|
||||
|
||||
返回:
|
||||
原始响应 dict,含 userChargeList
|
||||
"""
|
||||
log_info("取用户票券:user_id=" + str(user_id))
|
||||
return _get("GetUserChargeApi", UserIdRequestVO(userId=user_id), user_id,
|
||||
client_id=client_id, timeout_ms=timeout_ms, client=client)
|
||||
Reference in New Issue
Block a user