初步增加, 扫码登录成功

This commit is contained in:
yml2213
2026-08-12 16:22:55 +08:00
parent 7db3b2cd4f
commit d19c0902f2
60 changed files with 7413 additions and 315 deletions
@@ -0,0 +1,203 @@
#!/usr/bin/env python3
"""用已登录的 YYB CK 选择和平精英点券档位、区服和角色。"""
from __future__ import annotations
import argparse
import json
import random
import re
import sys
import time
import uuid
from pathlib import Path
from urllib.parse import unquote, urlencode
from curl_cffi import requests
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
from pyvm.login_profile import midas_login_params # noqa: E402
YYB_APP_ID = 52575843
SOURCE_ID = "24292013"
PRODUCTS_URL = "https://ydd.yyb.qq.com/new_direct_buy_shop/GetTokenMod"
CMALL_URL = "https://storeapi.pay.qq.com/api/unipay/{offer_id}/cmall_query"
PLATFORMS = {
"android": {
"label": "Android",
"query_platform": "pc_android",
"cmall_pf": "mds_storeopen_qb-__mds_myappjp_PC_aW9zd2hpdGVsaX0-android",
"order_pf": "mds_myappjp-__mds_myappjp_PC_aW9zd2hpdGVsaX0-android",
},
"ios": {
"label": "iOS",
"query_platform": "pc_ios",
"cmall_pf": "mds_storeopen_qb-__mds_myappjp_PC_aW9zd2hpdGVsaX0-iap",
"order_pf": "mds_myappjp-__mds_myappjp_PC_aW9zd2hpdGVsaX0-iap",
},
}
UA = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36"
)
def load_cookies(path: Path) -> dict[str, str]:
session = json.loads(path.read_text(encoding="utf-8"))
cookies = {str(key): str(value) for key, value in session.get("cookies", {}).items() if value}
if not cookies.get("openid") or not cookies.get("accesstoken"):
raise ValueError("会话缺少 openid/accesstoken,请先执行 login-wechat.py")
return cookies
def product_options(cookies: dict[str, str], platform: str) -> list[dict[str, str | int]]:
response = requests.post(
PRODUCTS_URL,
json={"platform": PLATFORMS[platform]["query_platform"], "source_id": SOURCE_ID,
"yyb_app_id": YYB_APP_ID},
headers={"Accept": "application/json, text/plain, */*", "Origin": "https://m.yyb.qq.com",
"Referer": "https://m.yyb.qq.com/boc-mall/goods-mall/", "User-Agent": UA},
cookies=cookies, impersonate="chrome", timeout=30,
)
if response.status_code != 200:
raise RuntimeError(f"点券商品查询失败: HTTP {response.status_code}")
document = response.json()
if document.get("code") not in (None, 0):
raise RuntimeError(f"点券商品查询失败: {document.get('code')} {document.get('message', '')}")
products = document.get("token_mod", {}).get("products", [])
result: list[dict[str, str | int]] = []
for item in products:
product = item.get("product", {}) if isinstance(item, dict) else {}
match = re.fullmatch(r"(\d+)点券", str(product.get("product_name", "")))
if not match or str(product.get("status")) != "20":
continue
result.append({
"points": int(match.group(1)),
"product_id": str(product.get("product_id", "")),
"price_fen": int(product.get("price", 0)),
"offer_id": str(product.get("res_offer_id", "")),
"name": str(product.get("product_name", "")),
})
result.sort(key=lambda item: int(item["points"]))
if not result or any(not item["product_id"] or not item["offer_id"] for item in result):
raise RuntimeError("点券商品响应缺少 product_id 或 offer_id")
return result
class Cmall:
def __init__(self, cookies: dict[str, str], offer_id: str, platform: str) -> None:
self.cookies = cookies
self.offer_id = offer_id
self.platform = platform
self.session_token = f"{str(uuid.uuid4()).upper()}{int(time.time() * 1000)}"
def query(self, cmd: str, **extra: str) -> dict:
login = midas_login_params(self.cookies)
params = {
"from_h5": "1", "pf": PLATFORMS[self.platform]["cmall_pf"], "r": str(random.random()), "cmd": cmd,
"session_token": self.session_token,
"pfkey": "pfkey", "webversion": "", **extra,
**login,
}
# 当前商城页面将查询参数放在 URL 上,但请求方法为 POST 且没有 body。
response = requests.post(
CMALL_URL.format(offer_id=self.offer_id) + "?" + urlencode(params),
headers={"Origin": "https://z.iwan.yyb.qq.com", "Referer": "https://z.iwan.yyb.qq.com/",
"User-Agent": UA},
cookies=self.cookies, impersonate="chrome", timeout=30,
)
if response.status_code != 200:
raise RuntimeError(f"游戏数据查询失败: HTTP {response.status_code}")
document = response.json()
if document.get("ret") not in (0, "0"):
raise RuntimeError(f"游戏数据查询失败: {document.get('ret')} {document.get('msg', '')}")
return document
def zones(self) -> list[dict[str, str]]:
response = self.query("14", use_currency_offerid="1")
zones = response.get("zone_list", [])
return [{"zone_id": str(item.get("zone_id", "")), "name": str(item.get("zone_name", ""))}
for item in zones if isinstance(item, dict) and item.get("zone_id")]
def roles(self, zone_id: str) -> list[dict[str, str]]:
response = self.query("15", use_currency_offerid="1", zoneid=zone_id)
roles = response.get("role_info") or response.get("role_list") or []
return [{"role_id": str(item.get("role_id", "")),
"name": unquote(str(item.get("role_name", ""))),
"ban_status": str(item.get("ban_status", ""))}
for item in roles if isinstance(item, dict) and item.get("role_id")]
def choose(label: str, options: list[dict], display) -> dict:
if not options:
raise RuntimeError(f"没有可选择的{label}")
print(f"\n可选{label}:")
for index, item in enumerate(options, start=1):
print(f" {index}. {display(item)}")
while True:
raw = input(f"请选择{label}序号 [1-{len(options)}]: ").strip()
if raw.isdigit() and 1 <= int(raw) <= len(options):
return options[int(raw) - 1]
print("请输入列表中的有效序号。")
def main() -> int:
parser = argparse.ArgumentParser(description="和平精英点券和角色选择(纯 CK 查询)")
parser.add_argument("--session", type=Path, default=ROOT / "config/mall-session.json")
parser.add_argument("--output", type=Path, default=ROOT / "config/peace-elite-selection.json")
parser.add_argument("--platform", choices=tuple(PLATFORMS), default=None,
help="预选 Android/iOS;不传则显示平台菜单")
parser.add_argument("--points", type=int, default=None, help="预选点券数;不传则显示菜单")
parser.add_argument("--list-products", action="store_true", help="仅列出当前所有点券档位")
args = parser.parse_args()
cookies = load_cookies(args.session)
if args.platform is None:
selected_platform = choose(
"平台", [{"id": key, **value} for key, value in PLATFORMS.items()],
lambda item: item["label"],
)
platform = str(selected_platform["id"])
else:
platform = args.platform
print(f"已选择平台: {PLATFORMS[platform]['label']}")
products = product_options(cookies, platform)
if args.list_products:
for product in products:
print(f"{product['points']}点券\t{product['price_fen'] / 100:g}\t{product['product_id']}")
return 0
if args.points is None:
product = choose("点券档位", products, lambda item: f"{item['points']}点券({item['price_fen'] / 100:g}元)")
else:
product = next((item for item in products if item["points"] == args.points), None)
if product is None:
available = ", ".join(str(item["points"]) for item in products)
raise ValueError(f"不支持 {args.points} 点券;当前可选: {available}")
print(f"已选择: {product['points']}点券({product['price_fen'] / 100:g}元)")
cmall = Cmall(cookies, str(product["offer_id"]), platform)
zone = choose("区服", cmall.zones(), lambda item: f"{item['name']}ID {item['zone_id']}")
roles = cmall.roles(zone["zone_id"])
role = choose("角色", roles, lambda item: f"{item['name']}{'禁用' if item['ban_status'] == '1' else '可用'}")
if role["ban_status"] == "1":
raise RuntimeError("所选角色已被封禁,不能充值")
selection = {"platform": platform, "order_pf": PLATFORMS[platform]["order_pf"],
"points": product["points"], "product_id": product["product_id"],
"offer_id": product["offer_id"], "price_fen": product["price_fen"],
"zone_id": zone["zone_id"], "zone_name": zone["name"],
"role_id": role["role_id"], "role_name": role["name"]}
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(selection, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"\n已选择 {selection['points']}点券 / {selection['zone_name']} / {selection['role_name']}")
print(f"选择已保存: {args.output}")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (OSError, ValueError, RuntimeError, requests.RequestsError) as error:
print(f"选择失败: {error}")
raise SystemExit(1) from error