当前仅 upsert.py 可用:扫码登录 -> 上传一局成绩 -> 落库回查 -> 登出。 - api/ 下 40+ 个接口模块已实现,236 项零网络自检全绿 - 上传成绩链路已真机跑通,报文与真机抓包逐字节一致 - 开发文档见 docs 分支;source/ 与 logs/ 不入库
170 lines
6.3 KiB
Python
170 lines
6.3 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
upsert.py —— 主脚本:扫码登录 → 上传成绩 → 登出
|
||
================================================================================
|
||
|
||
整条流程只有四步,而且**一行日志都不用自己写** ——
|
||
每个 api 函数被调用时会自动打印:
|
||
|
||
=== UserLogin 开始运行 ===
|
||
[接口说明] 用户正式登录(开启本次登录会话)。
|
||
[位置参数] 12345678, 'iUfgRmjr...'
|
||
[关键字参数] region_id=1, place_id=1000
|
||
=== UserLogin 运行结束 ===
|
||
|
||
日志模式改 config.py 一行:
|
||
|
||
LOG_LEVEL = "info" 只展示 api 脚本的运行情况和结果
|
||
LOG_LEVEL = "debug" 额外展示向 api 发送的数据与 api 返回的数据
|
||
|
||
日志文件(两种同时写):
|
||
|
||
logs/latest.log 只含**当次**主脚本运行(下次覆盖)
|
||
logs/YYYY-MM-DD.log 存放**当日**产生的所有日志(追加)
|
||
|
||
--------------------------------------------------------------------------------
|
||
用法
|
||
--------------------------------------------------------------------------------
|
||
python upsert.py <二维码> # 预检:登录 + 抓快照 + 组包,不发包
|
||
python upsert.py <二维码> --send # 真实上传成绩(★ 有副作用,不可撤销)
|
||
python upsert.py <二维码> --send --debug # 同上,日志里带完整收发报文
|
||
python upsert.py <二维码> --send --cooldown 0 # 跳过发包前的 60 秒冷却
|
||
python upsert.py <二维码> --send --no-verify # 跳过上传后的落库回查
|
||
|
||
--------------------------------------------------------------------------------
|
||
★ UpsertUserAll 一个函数就把整条结算链路跑完了
|
||
--------------------------------------------------------------------------------
|
||
1. 抓基线快照(没传 snapshot 时自动抓,用 token)
|
||
2. 从快照推本局 5 只角色(游玩前等级)
|
||
3. 构造本局成绩明细 userPlaylogList
|
||
4. 调前置接口 GetUserNewItemListApi
|
||
5. 组装 upsertUserAll 的 36 个节点(快照基线 + 本局演算 + 差分)
|
||
6. 合并 extra_data 里的外部 JSON
|
||
7. 发包前自检
|
||
8. 冷却 -> 真实发包
|
||
9. 落库回查(重新拉服务器数据,确认真的写进去了)
|
||
|
||
想额外塞几个节点(比如本局拿到的新 KaleidxScope 门)就传 extra_data:
|
||
|
||
UpsertUserAll(
|
||
user_id, login.loginId,
|
||
login_date_time=login.loginDateTime,
|
||
auth_date_time=login.dateTime,
|
||
token=token,
|
||
extra_data={
|
||
"userKaleidxScopeList": [{"gateId": 1, "isGateFound": True, ...}],
|
||
"isNewKaleidxScopeList": "1",
|
||
},
|
||
confirm=True,
|
||
)
|
||
|
||
extra_data 接受 dict / JSON 字符串 / JSON 文件路径;
|
||
键必须是 36 个节点之一,没给位串时会按数组长度自动补并给出提示。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
|
||
from api import (
|
||
setup_logging,
|
||
teardown_logging,
|
||
log_error,
|
||
ParseQRCode,
|
||
UserPreview,
|
||
UserLogin,
|
||
UserLogout,
|
||
LogoutType,
|
||
UpsertUserAll,
|
||
ConfirmRequiredError,
|
||
)
|
||
from config import PLACE_ID, REGION_ID
|
||
|
||
|
||
def parse_args(argv):
|
||
"""极简命令行解析:第一个非 -- 开头的参数是二维码。"""
|
||
opts = {"qr": "", "send": False, "debug": False, "no_verify": False, "cooldown": None}
|
||
i = 0
|
||
while i < len(argv):
|
||
a = argv[i]
|
||
if a == "--send":
|
||
opts["send"] = True
|
||
elif a == "--debug":
|
||
opts["debug"] = True
|
||
elif a == "--no-verify":
|
||
opts["no_verify"] = True
|
||
elif a == "--cooldown" and i + 1 < len(argv):
|
||
opts["cooldown"] = float(argv[i + 1])
|
||
i += 1
|
||
elif not a.startswith("--") and not opts["qr"]:
|
||
opts["qr"] = a
|
||
i += 1
|
||
return opts
|
||
|
||
|
||
def main() -> int:
|
||
opts = parse_args(sys.argv[1:])
|
||
if not opts["qr"]:
|
||
print(__doc__)
|
||
return 1
|
||
|
||
# --debug 只影响本次运行,不改 config.py
|
||
if opts["debug"]:
|
||
import config as _cfg
|
||
_cfg.LOG_LEVEL = "debug"
|
||
|
||
# 日志会话:开头一句。之后每次 api 调用都会自动记录,主脚本不用再写日志
|
||
setup_logging("upsert")
|
||
|
||
# 会话状态:保证异常时也能登出(否则账号会卡在 isLogin=True,下次登录 returnCode=100)
|
||
session = {"user_id": 0, "token": "", "login_dt": 0, "logged_in": False}
|
||
try:
|
||
# ---- 扫码:二维码 -> userId + token ----
|
||
user_id, token = ParseQRCode(opts["qr"])
|
||
session.update(user_id=user_id, token=token)
|
||
|
||
# ---- 预览:顺便挡住「账号已在游戏中」的情况,避免顶号 ----
|
||
preview = UserPreview(user_id, token)
|
||
if preview.isLogin:
|
||
log_error("账号处于已登录状态,请先在机台登出或等待会话超时")
|
||
return 2
|
||
|
||
# ---- 登录:拿到 playlogId 与 loginDateTime(结算上报要用)----
|
||
login = UserLogin(user_id, token, region_id=REGION_ID, place_id=PLACE_ID)
|
||
session.update(login_dt=login.loginDateTime, logged_in=True)
|
||
|
||
# ---- 结算:★ 一个函数搞定构造 + 发包 + 落库回查 ----
|
||
UpsertUserAll(
|
||
user_id, login.loginId,
|
||
login_date_time=login.loginDateTime,
|
||
auth_date_time=login.dateTime,
|
||
token=token,
|
||
dry_run=not opts["send"],
|
||
confirm=opts["send"],
|
||
cooldown_sec=opts["cooldown"],
|
||
verify_landing=not opts["no_verify"],
|
||
place_id=PLACE_ID, region_id=REGION_ID,
|
||
)
|
||
return 0
|
||
|
||
except ConfirmRequiredError:
|
||
return 5
|
||
except Exception as exc:
|
||
log_error(type(exc).__name__ + ": " + str(exc))
|
||
return 1
|
||
finally:
|
||
# 兜底登出:登录过且还没登出就释放会话,否则账号卡在 isLogin=True
|
||
if session["logged_in"]:
|
||
try:
|
||
UserLogout(session["user_id"], session["token"], session["login_dt"],
|
||
logout_type=LogoutType.TestIn,
|
||
region_id=REGION_ID, place_id=PLACE_ID)
|
||
except Exception as exc:
|
||
log_error("[收尾] 强制登出失败(会话可能需等待服务端超时):" + str(exc))
|
||
teardown_logging()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|