style: 统一 Ruff 代码格式

This commit is contained in:
yml2213
2026-08-30 21:04:52 +08:00
parent c891ac982e
commit 47e19ed7b2
90 changed files with 5574 additions and 2350 deletions
+220 -71
View File
@@ -17,6 +17,7 @@
python3 main.py sample-session # 用迁移的 live 捕获生成示例会话态
python3 main.py sample-order # 用迁移的 order7 生成示例订单
"""
from __future__ import annotations
import argparse
@@ -50,14 +51,31 @@ from pyvm.protocol import ( # noqa: E402
REPLAY = ROOT / "replay"
DEFAULT_APPID = PAY_APPID
DEFAULT_SAVE_URL = f"https://api.unipay.qq.com/v1/r/{DEFAULT_APPID}/web_save"
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",
]
# ---------------------------------------------------------------- 工具
def _write_private_text(path: Path, content: str) -> None:
"""写入任务协议证据并限制为当前用户可读。"""
path.parent.mkdir(parents=True, exist_ok=True)
@@ -65,6 +83,7 @@ def _write_private_text(path: Path, content: str) -> None:
path.write_text(content, encoding="utf-8")
os.chmod(path, 0o600)
def _load_cap(path: Path) -> dict:
"""加载 deepCap 捕获 JSON,兼容三种存档格式,返回顶层 dict(含 C 键)。
@@ -113,32 +132,47 @@ def parse_plaintext(path: str | Path) -> dict:
# ---------------------------------------------------------------- 命令
def cmd_verify(args=None) -> int:
"""校验当前受版本控制的 goods 协议材料,不依赖已删除的历史抓包。"""
args_path = REPLAY / "live" / "default" / "args-template.json"
args_template = json.loads(args_path.read_text(encoding="utf-8"))
validate_goods_materials(args_template)
required = ("bytecode.json", "constants.json", "xmidasops.json", "e2e/multi-1.json",
"e2e/ws-args.json", "deepcaps2/cap-69667.json")
required = (
"bytecode.json",
"constants.json",
"xmidasops.json",
"e2e/multi-1.json",
"e2e/ws-args.json",
"deepcaps2/cap-69667.json",
)
missing = [name for name in required if not (REPLAY / name).exists()]
if missing:
print(f"❌ goods 协议材料缺失: {', '.join(missing)}")
return 1
print("✅ goods 协议材料校验通过(18 槽模板、Te/S-box、VM 文件均完整)")
print(" 历史逐字节黄金向量未随仓库保留;如需恢复,可放入 replay/golden 后由 CI 自动执行。")
print(
" 历史逐字节黄金向量未随仓库保留;如需恢复,可放入 replay/golden 后由 CI 自动执行。"
)
return 0
def _gen_with_session(st: SessionState, order: dict) -> str:
"""用会话态 + 订单参数生成 encrypt_msg(args_template 会话绑定,必须用捕获值)。"""
from pyvm.algorithm import decode_d
params = {k: order.get(k, "") for k in ORDER_FIELDS}
args_tpl = decode_d(st.args_template_d)
return generate_encrypt_msg_offline(
params, order.get("fk_extend", ""), order.get("ts", ""), order.get("_rand", ""),
xmidas=st.xmidas_ops, xmidas_token=st.xmidas_token,
params,
order.get("fk_extend", ""),
order.get("ts", ""),
order.get("_rand", ""),
xmidas=st.xmidas_ops,
xmidas_token=st.xmidas_token,
args_template=args_tpl,
key16=st.key16, key1=st.key1,
key16=st.key16,
key1=st.key1,
)
@@ -186,7 +220,8 @@ def cmd_submit(args) -> int:
url = args.url or DEFAULT_SAVE_URL
cookie_str = "; ".join(f"{k}={v}" for k, v in st.cookies.items())
req = urllib.request.Request(
url, data=body.encode("utf-8"),
url,
data=body.encode("utf-8"),
headers={
"User-Agent": GOODS_USER_AGENT,
"Content-Type": "application/x-www-form-urlencoded",
@@ -221,6 +256,7 @@ def cmd_submit(args) -> int:
def cmd_sample_session(args) -> int:
"""用迁移的 live 捕获生成示例会话态(key16/key1/xmidas_ops)。"""
from pyvm.algorithm import decode_d
web_args = decode_d(_load_cap(REPLAY / "live/caps7/cap-85091.json")["C"][2])
cap7 = _load_cap(REPLAY / "live/caps7/cap-85091.json")
st = SessionState(
@@ -228,7 +264,9 @@ def cmd_sample_session(args) -> int:
key16=list(web_args[6][0]),
key1=list(web_args[0][0]),
xmidas_token="DE46DBA4754D42A6B66ADD4319FF80C144D9ED22ED73384C271793D9A9AA2FFBD6C104BE8D4A7F4ED2A8688FCB6F7540",
args_template_d=cap7["C"][2] if isinstance(cap7["C"][2], str) else json.dumps(cap7["C"][2], ensure_ascii=False),
args_template_d=cap7["C"][2]
if isinstance(cap7["C"][2], str)
else json.dumps(cap7["C"][2], ensure_ascii=False),
cookies={},
source="live/order7 (归档示例,cookies 为空)",
)
@@ -252,6 +290,7 @@ def cmd_sample_order(args) -> int:
# ---------------------------------------------------------------- mall 命令
def cmd_mall_verify(args=None) -> int:
"""校验当前受版本控制的 mall VM 和固定槽模板。"""
fixed_path = REPLAY / "mall" / "transform-fixed.json"
@@ -282,7 +321,9 @@ def cmd_mall_gen(args) -> int:
def cmd_mall_sample(args) -> int:
"""用归档同会话黄金对生成示例 mall 会话态(config/mall-session.json)。"""
g = json.loads((ROOT / "config/golden/golden-final.json").read_text(encoding="utf-8"))
g = json.loads(
(ROOT / "config/golden/golden-final.json").read_text(encoding="utf-8")
)
session = MallSession(g["transform_input"], g["xmidas_ops"])
out = Path(args.output) if args.output else ROOT / "config" / "mall-session.json"
out.parent.mkdir(parents=True, exist_ok=True)
@@ -325,7 +366,8 @@ def cmd_mall_submit(args) -> int:
if "midas_openkey" not in cookies and "accesstoken" in cookies:
cookie_str += "; midas_openkey=" + cookies["accesstoken"]
req = urllib.request.Request(
url, data=new_body.encode("utf-8"),
url,
data=new_body.encode("utf-8"),
headers={
"Content-Type": "application/json",
"Accept": "application/json, text/plain, */*",
@@ -346,7 +388,11 @@ def cmd_mall_submit(args) -> int:
print(f"[submit] 网络错误: {e!r}")
return 3
print(f"[submit] 响应: {raw[:800]}")
out = Path(args.output) if args.output else ROOT / "config" / "mall-order-response.json"
out = (
Path(args.output)
if args.output
else ROOT / "config" / "mall-order-response.json"
)
js = None
ret = None
try:
@@ -370,7 +416,9 @@ def cmd_mall_submit(args) -> int:
print(f" 失败响应 → {fail_out}(保留上次成功响应)")
if ret in ("1018", 1018):
print(" 原因: mall 登录态失效——请重新在浏览器登录并采集 mall 会话态")
print(" (node scripts/capture-mall-data.mjs,或手动刷新 mall-session.json 的 cookies)")
print(
" (node scripts/capture-mall-data.mjs,或手动刷新 mall-session.json 的 cookies)"
)
return 1
@@ -383,11 +431,15 @@ def cmd_mall_capture(args) -> int:
def main() -> int:
ap = argparse.ArgumentParser(description="YYB 加密参数生成框架(goods web_save + mall PlaceOrder)")
ap = argparse.ArgumentParser(
description="YYB 加密参数生成框架(goods web_save + mall PlaceOrder)"
)
sub = ap.add_subparsers(dest="module", required=True)
# ---- goods 组 ----
pg = sub.add_parser("goods", help="goods 侧(web_save / web_new_encrypt,CHAOS VM 116 opcode)")
pg = sub.add_parser(
"goods", help="goods 侧(web_save / web_new_encrypt,CHAOS VM 116 opcode)"
)
gsub = pg.add_subparsers(dest="cmd", required=True)
gsub.add_parser("verify", help="校验当前 goods 协议材料")
p = gsub.add_parser("gen", help="仅生成 encrypt_msg(不联网)")
@@ -416,30 +468,64 @@ def main() -> int:
p.add_argument("--frames", default=None, help="最新捕获的 frames.jsonl(优先)")
p.add_argument("--output", default=None)
p = msub.add_parser("submit", help="生成 encrypt_msg + 提交 PlaceOrder(纯 Python)")
p.add_argument("--session", required=True, help="mall-session.json(含 transform_input/xmidas/cookies)")
p.add_argument("--order-template", default=str(ROOT / "config" / "mall-order-template.json"))
p.add_argument(
"--session",
required=True,
help="mall-session.json(含 transform_input/xmidas/cookies)",
)
p.add_argument(
"--order-template", default=str(ROOT / "config" / "mall-order-template.json")
)
p.add_argument("--output", default=None)
p = msub.add_parser("auto", help="仅凭 CK 全自动下单(纯HTTP GetPayToken + 纯Python encrypt_msg,无浏览器采集)")
p = msub.add_parser(
"auto",
help="仅凭 CK 全自动下单(纯HTTP GetPayToken + 纯Python encrypt_msg,无浏览器采集)",
)
p.add_argument("--session", default=str(ROOT / "config" / "mall-session.json"))
p.add_argument("--order-template", default=str(ROOT / "config" / "mall-order-template.json"))
p.add_argument("--product-id", default=None, help="和平精英点券商品 ID;默认使用模板当前商品")
p.add_argument(
"--order-template", default=str(ROOT / "config" / "mall-order-template.json")
)
p.add_argument(
"--product-id", default=None, help="和平精英点券商品 ID;默认使用模板当前商品"
)
p.add_argument("--offer-id", default=None, help="当前商品服务端 offer ID")
p.add_argument("--quantity", type=int, default=None, help="当前点券商品购买份数(正整数)")
p.add_argument(
"--quantity", type=int, default=None, help="当前点券商品购买份数(正整数)"
)
p.add_argument("--role-id", default=None, help="游戏角色 ID;默认使用模板当前角色")
p.add_argument("--role-name", default=None, help="游戏角色名称;默认使用模板当前角色")
p.add_argument(
"--role-name", default=None, help="游戏角色名称;默认使用模板当前角色"
)
p.add_argument("--zone-id", default=None, help="游戏区服 ID;默认使用模板当前区服")
p.add_argument("--zone-name", default=None, help="游戏区服名称")
p.add_argument("--area", default=None, help="游戏大区 ID(QQ 平台与 zoneid 不同,来自角色查询的 partition_info")
p.add_argument(
"--area",
default=None,
help="游戏大区 ID(QQ 平台与 zoneid 不同,来自角色查询的 partition_info",
)
p.add_argument("--partition", default=None, help="游戏分区 IDQQ 平台可为空)")
p.add_argument("--pf", default=None, help="支付平台标识;由角色选择器按 Android/iOS 写入")
p.add_argument(
"--pf", default=None, help="支付平台标识;由角色选择器按 Android/iOS 写入"
)
p.add_argument("--output", default=None)
p = msub.add_parser("pay", help="mall 下单 -> goods web_save -> 微信支付二维码(纯 Python)")
p.add_argument("--mall-response", default=str(ROOT / "config" / "mall-order-response.json"),
help="PlaceOrder 响应(含 url_params/token)")
p.add_argument("--goods-dir", default=str(ROOT / "replay" / "live" / "order10"),
help="goods 会话态目录(plaintext/body/xmidasops/keys/cap/web-token)")
p.add_argument("--session", default=str(ROOT / "config" / "mall-session.json"),
help="mall 登录态(含 cookies)")
p = msub.add_parser(
"pay", help="mall 下单 -> goods web_save -> 微信支付二维码(纯 Python)"
)
p.add_argument(
"--mall-response",
default=str(ROOT / "config" / "mall-order-response.json"),
help="PlaceOrder 响应(含 url_params/token)",
)
p.add_argument(
"--goods-dir",
default=str(ROOT / "replay" / "live" / "order10"),
help="goods 会话态目录(plaintext/body/xmidasops/keys/cap/web-token)",
)
p.add_argument(
"--session",
default=str(ROOT / "config" / "mall-session.json"),
help="mall 登录态(含 cookies)",
)
p.add_argument("--appid", default=DEFAULT_APPID)
p.add_argument("--output", default=None, help="二维码 PNG 输出路径")
msub.add_parser("capture", help="用浏览器捕获 mall 会话态(Node 脚本)")
@@ -447,20 +533,29 @@ def main() -> int:
args = ap.parse_args()
try:
if args.module == "goods":
return {"verify": cmd_verify, "gen": cmd_gen, "submit": cmd_submit,
"sample-session": cmd_sample_session, "sample-order": cmd_sample_order}[args.cmd](args)
return {
"verify": cmd_verify,
"gen": cmd_gen,
"submit": cmd_submit,
"sample-session": cmd_sample_session,
"sample-order": cmd_sample_order,
}[args.cmd](args)
elif args.module == "mall":
return {"verify": cmd_mall_verify, "gen": cmd_mall_gen, "submit": cmd_mall_submit,
"sample-session": cmd_mall_sample, "pay": cmd_mall_pay, "auto": cmd_mall_auto,
"capture": cmd_mall_capture}[args.cmd](args)
return {
"verify": cmd_mall_verify,
"gen": cmd_mall_gen,
"submit": cmd_mall_submit,
"sample-session": cmd_mall_sample,
"pay": cmd_mall_pay,
"auto": cmd_mall_auto,
"capture": cmd_mall_capture,
}[args.cmd](args)
return 2
except (FileNotFoundError, ValueError) as e:
print(f"错误: {e}")
return 2
def build_mall_transform(fixed: dict) -> list:
"""仅CK全自动:固定槽模板 + 随机 key16/槽6/明文缓冲 构造 transform_input。
@@ -487,6 +582,7 @@ def mall_getpaytoken(cookies: dict) -> tuple[list, str]:
证据: evidence/getpaytoken-pure-http.json
"""
import urllib.request
login = midas_login_params(cookies)
login["offer_id"] = "800001492"
body = {
@@ -494,8 +590,12 @@ def mall_getpaytoken(cookies: dict) -> tuple[list, str]:
"call_param": {
"call_func": "GetPayToken",
"call_param_json": json.dumps(
{"version": "pagedoo-v2.0.0", "app_id": "202406061128117473047424",
"content_id": "ct1755160919_GEOCGTMN"}),
{
"version": "pagedoo-v2.0.0",
"app_id": "202406061128117473047424",
"content_id": "ct1755160919_GEOCGTMN",
}
),
"call_type": "security_service",
"login_check_param_json": json.dumps(login),
},
@@ -505,12 +605,19 @@ def mall_getpaytoken(cookies: dict) -> tuple[list, str]:
for k, v in [("midas_openid", "openid"), ("midas_openkey", "accesstoken")]:
if k not in cookies and v in cookies:
cookie_str += f"; {k}=" + cookies[v]
req = urllib.request.Request(url, data=json.dumps(body).encode("utf-8"), headers={
"Content-Type": "application/json", "Accept": "application/json, text/plain, */*",
"Origin": "https://z.iwan.yyb.qq.com", "Referer": "https://z.iwan.yyb.qq.com/",
"Cookie": cookie_str,
"User-Agent": MALL_USER_AGENT,
}, method="POST")
req = urllib.request.Request(
url,
data=json.dumps(body).encode("utf-8"),
headers={
"Content-Type": "application/json",
"Accept": "application/json, text/plain, */*",
"Origin": "https://z.iwan.yyb.qq.com",
"Referer": "https://z.iwan.yyb.qq.com/",
"Cookie": cookie_str,
"User-Agent": MALL_USER_AGENT,
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=30) as r:
raw = r.read().decode("utf-8", "replace")
@@ -549,7 +656,11 @@ def _apply_card_selection(payload: dict, args) -> None:
if quantity is not None and quantity <= 0:
raise ValueError("--quantity 必须是正整数")
products = payload.get("product_list")
if not isinstance(products, list) or not products or not isinstance(products[0], dict):
if (
not isinstance(products, list)
or not products
or not isinstance(products[0], dict)
):
raise ValueError("订单模板缺少 product_list[0]")
product = products[0]
product_id = getattr(args, "product_id", None)
@@ -617,7 +728,9 @@ def cmd_mall_auto(args) -> int:
return 1
print(f"[auto] arrays {len(arrays)} | pay_token {pay_token[:16]}...")
print("[auto] ② 构造 transform_input(固定槽 + 随机 key/明文缓冲)...")
fixed = json.loads((ROOT / "replay/mall/transform-fixed.json").read_text(encoding="utf-8"))
fixed = json.loads(
(ROOT / "replay/mall/transform-fixed.json").read_text(encoding="utf-8")
)
validate_mall_materials(fixed)
ti = build_mall_transform(fixed)
print("[auto] ③ 纯 Python 生成 encrypt_msg...")
@@ -639,18 +752,29 @@ def cmd_mall_auto(args) -> int:
if k not in cookies and v in cookies:
cookie_str += f"; {k}=" + cookies[v]
url = MALL_API_URL + "?t=" + str(int(time.time() * 1000))
req = urllib.request.Request(url, data=new_body.encode("utf-8"), headers={
"Content-Type": "application/json", "Accept": "application/json, text/plain, */*",
"Origin": "https://z.iwan.yyb.qq.com", "Referer": "https://z.iwan.yyb.qq.com/",
"Cookie": cookie_str,
"User-Agent": MALL_USER_AGENT,
}, method="POST")
req = urllib.request.Request(
url,
data=new_body.encode("utf-8"),
headers={
"Content-Type": "application/json",
"Accept": "application/json, text/plain, */*",
"Origin": "https://z.iwan.yyb.qq.com",
"Referer": "https://z.iwan.yyb.qq.com/",
"Cookie": cookie_str,
"User-Agent": MALL_USER_AGENT,
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=30) as r:
raw = r.read().decode("utf-8", "replace")
except urllib.error.HTTPError as e:
raw = e.read().decode("utf-8", "replace")
out = Path(args.output) if args.output else ROOT / "config" / "mall-order-response.json"
out = (
Path(args.output)
if args.output
else ROOT / "config" / "mall-order-response.json"
)
_write_private_text(out, raw)
js = json.loads(raw)
ret = js.get("result_code")
@@ -669,7 +793,9 @@ def cmd_mall_auto(args) -> int:
return 0
detail = js.get("result_info", "") or call_reply.get("result_info", "") or ""
print(f"❌ 下单失败 ret={ret} inner={inner_ret} ({detail})")
print(f" {describe_payment_failure(str(cookies.get('logintype', '')), 'order', f'{ret} {inner_ret} {detail}')}")
print(
f" {describe_payment_failure(str(cookies.get('logintype', '')), 'order', f'{ret} {inner_ret} {detail}')}"
)
print(f" 响应已保存 → {out}")
return 1
@@ -714,10 +840,13 @@ def cmd_mall_pay(args) -> int:
cr = json.loads(resp["data"]["call_reply"])
up = cr["data"]["url_params"]
from urllib.parse import parse_qs, urlparse
q = parse_qs(urlparse(up).query)
mall_tid = q.get("token_id", [""])[0]
if mall_tid and mall_tid != token_id:
print(f"⚠️ mall 响应订单({mall_tid[:16]}...)与捕获会话订单({token_id[:16]}...)不一致")
print(
f"⚠️ mall 响应订单({mall_tid[:16]}...)与捕获会话订单({token_id[:16]}...)不一致"
)
print(" goods web_token 绑定捕获页面订单,以捕获 body 订单为准继续")
except Exception as e: # noqa: BLE001
print(f"⚠️ mall 响应读取失败(忽略): {e!r}")
@@ -727,6 +856,7 @@ def cmd_mall_pay(args) -> int:
keys = json.loads((gd / "keys.json").read_text(encoding="utf-8"))
xmidas = json.loads((gd / "xmidasops.json").read_text(encoding="utf-8"))
from pyvm.algorithm import decode_d, recover_plaintext_from_buffer
at_file = gd / "args-template.json"
if at_file.exists():
# 新版采集:decoded 18参直接读取(capture-goods-session.mjs J 转储)
@@ -740,8 +870,10 @@ def cmd_mall_pay(args) -> int:
if not web_token:
m = re.search(r"web_token=([0-9A-F]+)", body_tpl)
web_token = m.group(1) if m else ""
print(f"[pay] goods 会话态: xmidasops={len(xmidas)} key16={'' if keys.get('key16') else ''} "
f"web_token={web_token[:12]}...")
print(
f"[pay] goods 会话态: xmidasops={len(xmidas)} key16={'' if keys.get('key16') else ''} "
f"web_token={web_token[:12]}..."
)
# 3. 恢复页面真实明文(ts/fk_extend/_rand),刷新 ts 为当前一致值
try:
@@ -761,22 +893,37 @@ def cmd_mall_pay(args) -> int:
now_ms = int(time.time() * 1000)
params["ts"] = str(now_ms // 1000)
if rec_ts:
print(f"[pay] 页面真实明文: ts={rec_ts}(捕获) -> 使用当前 ts={params['ts']} "
f"(E3:ts 与 body t 一致即可,不必等于页面值)")
print(
f"[pay] 页面真实明文: ts={rec_ts}(捕获) -> 使用当前 ts={params['ts']} "
f"(E3:ts 与 body t 一致即可,不必等于页面值)"
)
print("[pay] 生成 goods encrypt_msg(纯 Python)...")
hex_msg = generate_encrypt_msg_offline(
params, fk_extend, params["ts"], rand_val,
xmidas=xmidas, args_template=web_args,
key16=keys.get("key16"), key1=keys.get("key1"),
params,
fk_extend,
params["ts"],
rand_val,
xmidas=xmidas,
args_template=web_args,
key16=keys.get("key16"),
key1=keys.get("key1"),
)
print(f"[pay] encrypt_msg ({len(hex_msg)} hex): {hex_msg[:32]}...")
# 4. 构造 web_save body(会话订单 token + 当前动态值 + 纯 py encrypt_msg)
body = body_tpl
for k, v in [("token_id", token_id), ("transaction_id", transaction_id),
("out_trade_no", out_trade_no), ("offer_type", offer_type)]:
for k, v in [
("token_id", token_id),
("transaction_id", transaction_id),
("out_trade_no", out_trade_no),
("offer_type", offer_type),
]:
body = re.sub(rf"{k}=[^&]*", f"{k}=" + v, body, count=1)
body = re.sub(r"pc_st=[^&]+", "pc_st=" + str(uuid.uuid4()).upper() + str(int(time.time() * 1000)), body)
body = re.sub(
r"pc_st=[^&]+",
"pc_st=" + str(uuid.uuid4()).upper() + str(int(time.time() * 1000)),
body,
)
body = re.sub(r"r=[0-9.]+", "r=" + str(secrets.SystemRandom().random()), body)
body = re.sub(r"&t=[0-9]+", "&t=" + str(int(time.time() * 1000)), body)
body = re.sub(r"encrypt_msg=[0-9a-f]+", "encrypt_msg=" + hex_msg, body)
@@ -798,7 +945,8 @@ def cmd_mall_pay(args) -> int:
# 6. POST web_save
url = f"https://api.unipay.qq.com/v1/r/{args.appid}/web_save"
req = urllib.request.Request(
url, data=body.encode("utf-8"),
url,
data=body.encode("utf-8"),
headers={
"User-Agent": MALL_USER_AGENT,
"Content-Type": "application/x-www-form-urlencoded",
@@ -855,5 +1003,6 @@ def cmd_mall_pay(args) -> int:
pass
return 0
if __name__ == "__main__":
sys.exit(main())