style: 统一 Ruff 代码格式
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
- mall.py: mall 侧高层 API(MallSession + generate_encrypt_msg)
|
||||
- session.py: 会话态模型
|
||||
"""
|
||||
|
||||
from .algorithm import (
|
||||
build_plaintext,
|
||||
decode_d,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@
|
||||
→ webSave(33 块变换)
|
||||
→ encrypt_msg(1056 hex)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -16,17 +17,39 @@ from typing import Any
|
||||
from .algorithm import generate_encrypt_msg_offline
|
||||
|
||||
REPLAY = Path(__file__).resolve().parent.parent / "replay"
|
||||
ORDER_FIELDS = ["token_id", "openid", "openkey", "session_id", "session_type", "zoneid",
|
||||
"pay_method", "buy_quantity", "mb_pwd", "pay_id", "auth_key",
|
||||
"card_value", "accounttype", "provide_uin", "extend", "ts",
|
||||
"from_h5", "webversion"]
|
||||
ORDER_FIELDS = [
|
||||
"token_id",
|
||||
"openid",
|
||||
"openkey",
|
||||
"session_id",
|
||||
"session_type",
|
||||
"zoneid",
|
||||
"pay_method",
|
||||
"buy_quantity",
|
||||
"mb_pwd",
|
||||
"pay_id",
|
||||
"auth_key",
|
||||
"card_value",
|
||||
"accounttype",
|
||||
"provide_uin",
|
||||
"extend",
|
||||
"ts",
|
||||
"from_h5",
|
||||
"webversion",
|
||||
]
|
||||
|
||||
|
||||
class GoodsSession:
|
||||
"""goods 会话态:xMidasOps(59640,服务端生成)+ key16/key1 + args_template。"""
|
||||
|
||||
def __init__(self, xmidas_ops: list, key16: list, key1: list,
|
||||
args_template_d: str = "", xmidas_token: str = ""):
|
||||
def __init__(
|
||||
self,
|
||||
xmidas_ops: list,
|
||||
key16: list,
|
||||
key1: list,
|
||||
args_template_d: str = "",
|
||||
xmidas_token: str = "",
|
||||
):
|
||||
self.xmidas_ops = xmidas_ops
|
||||
self.key16 = key16
|
||||
self.key1 = key1
|
||||
@@ -44,8 +67,13 @@ class GoodsSession:
|
||||
def from_session_state(cls, path: str | Path) -> "GoodsSession":
|
||||
"""从 scripts/capture-session.mjs 生成的 session-state.json 加载。"""
|
||||
d = json.loads(Path(path).read_text(encoding="utf-8"))
|
||||
return cls(d["xmidas_ops"], d["key16"], d["key1"],
|
||||
d.get("args_template_d", ""), d.get("xmidas_token", ""))
|
||||
return cls(
|
||||
d["xmidas_ops"],
|
||||
d["key16"],
|
||||
d["key1"],
|
||||
d.get("args_template_d", ""),
|
||||
d.get("xmidas_token", ""),
|
||||
)
|
||||
|
||||
def to_json(self) -> dict:
|
||||
return {
|
||||
@@ -58,17 +86,29 @@ class GoodsSession:
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, d: dict) -> "GoodsSession":
|
||||
return cls(d["xmidas_ops"], d["key16"], d["key1"],
|
||||
d.get("args_template_d", ""), d.get("xmidas_token", ""))
|
||||
return cls(
|
||||
d["xmidas_ops"],
|
||||
d["key16"],
|
||||
d["key1"],
|
||||
d.get("args_template_d", ""),
|
||||
d.get("xmidas_token", ""),
|
||||
)
|
||||
|
||||
|
||||
def generate_encrypt_msg(session: GoodsSession, order: dict) -> str:
|
||||
"""用会话态 + 订单参数生成 goods encrypt_msg(1056 hex)。"""
|
||||
from .algorithm import decode_d
|
||||
|
||||
params = {k: order.get(k, "") for k in ORDER_FIELDS}
|
||||
args_tpl = decode_d(session.args_template_d) if session.args_template_d else None
|
||||
return generate_encrypt_msg_offline(
|
||||
params, order.get("fk_extend", ""), order.get("ts", ""), order.get("_rand", ""),
|
||||
xmidas=session.xmidas_ops, xmidas_token=session.xmidas_token,
|
||||
args_template=args_tpl, key16=session.key16, key1=session.key1,
|
||||
params,
|
||||
order.get("fk_extend", ""),
|
||||
order.get("ts", ""),
|
||||
order.get("_rand", ""),
|
||||
xmidas=session.xmidas_ops,
|
||||
xmidas_token=session.xmidas_token,
|
||||
args_template=args_tpl,
|
||||
key16=session.key16,
|
||||
key1=session.key1,
|
||||
)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""YYB mall APIs require different session fields for WeChat and QQ OAuth."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
WECHAT_APPID = "wxd44977328b36e647"
|
||||
|
||||
@@ -12,6 +12,7 @@ mall 加密链路(E3 已验证,U-2022 闭环):
|
||||
注意:xMidasOps 是 mall 详情页页面级数据表(59620 长度,服务端生成),
|
||||
必须从浏览器捕获(与 goods 的 59640 不同)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
@@ -24,9 +25,28 @@ from .algorithm import UNDEF, JSObject, Window
|
||||
from .pagedoo_vm import PagedooVM
|
||||
|
||||
REPLAY = Path(__file__).resolve().parent.parent / "replay" / "mall"
|
||||
GLOBALS = [UNDEF, None, True, False, 4294967295, 3995986053, 2103143698, 1622111212,
|
||||
4263108271, 3162892160, 1960464030, 2867129963, 3224029870, 3514649446,
|
||||
1382846327, 1898428403, 1268470028, 1457769175, 1595352606, 1100935262]
|
||||
GLOBALS = [
|
||||
UNDEF,
|
||||
None,
|
||||
True,
|
||||
False,
|
||||
4294967295,
|
||||
3995986053,
|
||||
2103143698,
|
||||
1622111212,
|
||||
4263108271,
|
||||
3162892160,
|
||||
1960464030,
|
||||
2867129963,
|
||||
3224029870,
|
||||
3514649446,
|
||||
1382846327,
|
||||
1898428403,
|
||||
1268470028,
|
||||
1457769175,
|
||||
1595352606,
|
||||
1100935262,
|
||||
]
|
||||
|
||||
|
||||
class MallSession:
|
||||
@@ -45,9 +65,13 @@ class MallSession:
|
||||
|
||||
def validate(self) -> None:
|
||||
if len(self.transform_input) != 18:
|
||||
raise ValueError(f"transform_input 应为 18 槽,实际 {len(self.transform_input)}")
|
||||
raise ValueError(
|
||||
f"transform_input 应为 18 槽,实际 {len(self.transform_input)}"
|
||||
)
|
||||
if len(self.xmidas_ops) != 59620:
|
||||
raise ValueError(f"mall xMidasOps 应为 59620(非 goods 59640),实际 {len(self.xmidas_ops)}")
|
||||
raise ValueError(
|
||||
f"mall xMidasOps 应为 59620(非 goods 59640),实际 {len(self.xmidas_ops)}"
|
||||
)
|
||||
mid = self.transform_input[10]
|
||||
if isinstance(mid, list) and mid and isinstance(mid[0], list):
|
||||
if len(mid[0]) != 624:
|
||||
@@ -61,7 +85,11 @@ class MallSession:
|
||||
- J|...|e377650|{JSON} e377650 创建参数(transform_input)
|
||||
- XMIDAS_OPS|url|59620数组 mall 详情页 xMidasOps
|
||||
"""
|
||||
lines = Path(frames_jsonl).read_text(encoding="utf-8", errors="replace").splitlines()
|
||||
lines = (
|
||||
Path(frames_jsonl)
|
||||
.read_text(encoding="utf-8", errors="replace")
|
||||
.splitlines()
|
||||
)
|
||||
transform_input = None
|
||||
xmidas = None
|
||||
# xMidasOps:取 mall 详情页(z.iwan / pagedoo)那条
|
||||
@@ -85,7 +113,9 @@ class MallSession:
|
||||
transform_input = json.loads(l.split("|e377650|", 1)[1])
|
||||
break
|
||||
if transform_input is None:
|
||||
raise ValueError("frames.jsonl 中未找到 e377650 J 转储(需 mall 详情页购买触发加密)")
|
||||
raise ValueError(
|
||||
"frames.jsonl 中未找到 e377650 J 转储(需 mall 详情页购买触发加密)"
|
||||
)
|
||||
if xmidas is None:
|
||||
raise ValueError("frames.jsonl 中未找到 59620 长度 xMidasOps")
|
||||
return cls(transform_input, xmidas)
|
||||
@@ -112,8 +142,18 @@ def _mk_window(xmidas: list) -> Window:
|
||||
w.set("sessionStorage", JSObject())
|
||||
w.set("screen", JSObject())
|
||||
w.set("history", JSObject())
|
||||
w.set("XMLHttpRequest", type("XHR", (), {
|
||||
"open": lambda *a: None, "send": lambda *a: None, "setRequestHeader": lambda *a: None}))
|
||||
w.set(
|
||||
"XMLHttpRequest",
|
||||
type(
|
||||
"XHR",
|
||||
(),
|
||||
{
|
||||
"open": lambda *a: None,
|
||||
"send": lambda *a: None,
|
||||
"setRequestHeader": lambda *a: None,
|
||||
},
|
||||
),
|
||||
)
|
||||
w.set("fetch", lambda *a: None)
|
||||
w.set("xMidasOps", xmidas)
|
||||
return w
|
||||
@@ -143,5 +183,7 @@ def generate_encrypt_msg(session: MallSession, random_seed: int = 1) -> str:
|
||||
|
||||
h9 = h[9][0] if isinstance(h[9], list) and h[9] else h[9]
|
||||
if not isinstance(h9, list) or len(h9) != 624:
|
||||
raise RuntimeError(f"e377650 输出异常: {type(h9).__name__} len={len(h9) if isinstance(h9, list) else '?'}")
|
||||
raise RuntimeError(
|
||||
f"e377650 输出异常: {type(h9).__name__} len={len(h9) if isinstance(h9, list) else '?'}"
|
||||
)
|
||||
return "".join(f"{x & 255:02x}" for x in h9)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
"""Read YYB's official order list after a payment is completed."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
@@ -70,7 +71,9 @@ def order_completion_states(document: dict[str, Any]) -> dict[str, bool]:
|
||||
}
|
||||
|
||||
|
||||
def find_completed_order(document: dict[str, Any], previous_states: dict[str, bool]) -> dict[str, Any] | None:
|
||||
def find_completed_order(
|
||||
document: dict[str, Any], previous_states: dict[str, bool]
|
||||
) -> dict[str, Any] | None:
|
||||
"""Find an order that appeared or transitioned to completed after the QR display."""
|
||||
for item in document.get("list", []):
|
||||
if not isinstance(item, dict):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,5 @@
|
||||
"""YYB 下单和付款失败响应的统一归类。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
这里仅收敛已由成功请求验证的固定值,不负责推断或修改协议字段。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
@@ -47,21 +48,38 @@ def file_fingerprint(path: Path) -> str:
|
||||
return _sha256_prefix_bytes(path.read_bytes())
|
||||
|
||||
|
||||
def validate_goods_materials(args_template: list, xmidas: list[int] | None = None) -> None:
|
||||
def validate_goods_materials(
|
||||
args_template: list, xmidas: list[int] | None = None
|
||||
) -> None:
|
||||
"""校验 goods webSave VM 所需静态表结构,发现升级时尽早失败。"""
|
||||
if not isinstance(args_template, list) or len(args_template) != 18:
|
||||
raise ValueError("goods args-template 必须是 18 槽数组")
|
||||
for index in (0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16):
|
||||
if not isinstance(args_template[index], list) or not args_template[index]:
|
||||
raise ValueError(f"goods args-template 槽 {index} 缺失")
|
||||
expected_lengths = {0: 16, 1: 256, 2: 256, 3: 256, 4: 256, 5: 256,
|
||||
6: 16, 10: 528, 11: 1024, 12: 1024, 13: 256,
|
||||
14: 256, 15: 256, 16: 256}
|
||||
expected_lengths = {
|
||||
0: 16,
|
||||
1: 256,
|
||||
2: 256,
|
||||
3: 256,
|
||||
4: 256,
|
||||
5: 256,
|
||||
6: 16,
|
||||
10: 528,
|
||||
11: 1024,
|
||||
12: 1024,
|
||||
13: 256,
|
||||
14: 256,
|
||||
15: 256,
|
||||
16: 256,
|
||||
}
|
||||
for index, expected in expected_lengths.items():
|
||||
value = args_template[index][0]
|
||||
if not isinstance(value, list) or len(value) != expected:
|
||||
actual = len(value) if isinstance(value, list) else "非数组"
|
||||
raise ValueError(f"goods args-template 槽 {index} 长度异常: {actual} != {expected}")
|
||||
raise ValueError(
|
||||
f"goods args-template 槽 {index} 长度异常: {actual} != {expected}"
|
||||
)
|
||||
if xmidas is not None and len(xmidas) != 59640:
|
||||
raise ValueError(f"goods xMidasOps 长度异常: {len(xmidas)} != 59640")
|
||||
|
||||
@@ -70,7 +88,9 @@ def validate_mall_materials(transform_fixed: dict[str, Any]) -> None:
|
||||
"""校验 mall 固定槽模板;动态槽仍由当前会话的 GetPayToken 填充。"""
|
||||
if not isinstance(transform_fixed, dict):
|
||||
raise ValueError("mall transform-fixed 必须是对象")
|
||||
required = {str(index) for index in (1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17)}
|
||||
required = {
|
||||
str(index) for index in (1, 2, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17)
|
||||
}
|
||||
missing = sorted(required - set(transform_fixed))
|
||||
if missing:
|
||||
raise ValueError(f"mall transform-fixed 缺少槽: {', '.join(missing)}")
|
||||
@@ -79,7 +99,9 @@ def validate_mall_materials(transform_fixed: dict[str, Any]) -> None:
|
||||
raise ValueError(f"mall transform-fixed 槽 {index} 不是数组")
|
||||
|
||||
|
||||
def goods_material_diagnostics(root: Path, args_template: list, xmidas: list[int]) -> dict[str, Any]:
|
||||
def goods_material_diagnostics(
|
||||
root: Path, args_template: list, xmidas: list[int]
|
||||
) -> dict[str, Any]:
|
||||
"""构造脱敏协议指纹,便于区分页面升级和服务端业务拒绝。"""
|
||||
validate_goods_materials(args_template, xmidas)
|
||||
replay = root / "replay"
|
||||
|
||||
@@ -6,6 +6,7 @@ F-2049/F-2051(E3):encrypt_msg 与当前会话绑定——
|
||||
- key1: 诱饵密钥(点击级,捕获即可)
|
||||
服务端能验证 key 派生状态(随机 key 变体 ret:1099),因此新订单必须先捕获会话态。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
@@ -31,7 +32,9 @@ class SessionState:
|
||||
key16: list[int] = field(default_factory=list)
|
||||
key1: list[int] = field(default_factory=list)
|
||||
xmidas_token: str = DEFAULT_XMIDAS_TOKEN
|
||||
args_template_d: str = "" # webSave 18 参深拷贝(deepcap PC 85091 的 C[2] 原始 D 编码,会话绑定)
|
||||
args_template_d: str = (
|
||||
"" # webSave 18 参深拷贝(deepcap PC 85091 的 C[2] 原始 D 编码,会话绑定)
|
||||
)
|
||||
cookies: dict[str, str] = field(default_factory=dict)
|
||||
openid: str = ""
|
||||
openkey: str = ""
|
||||
|
||||
Reference in New Issue
Block a user