feat(python): 完成 Python 全量迁移——剩余平台、登录通道与公共层修复

- 新增 5sing/apple/xm/me/qt/lr 平台包与取流实现(含 xm-sign、MissEvan 签名、蜻蜓 HMAC、Apple developer token)
- 新增 wy/kg/tx/lr 登录:扫码、手机号、账密(kg 20028 风控指纹)、cookie 绑定、kg/tx 续期
- request 层:保留多值 Set-Cookie;JSON body 紧凑化(修复 kg 网关验签);直连不经系统代理(对齐 Node)
- 登录路由统一 Body(embed=True),修复扫码轮询 422;lifespan 接入登录续期调度
- ruff/format/pyright 全绿;14 平台真实联网与 HTTP 路由验证通过
This commit is contained in:
snowy-yang committed 2026-10-23 21:55:45 +08:00
1 parent 800871ef6e
commit dfc06bf4de
182 files changed
+26620

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
"""酷狗通用请求封装与签名(对应 sdk/kg/util.ts + sdk/auth/kg.ts 的设备/签名部分)。
`app/sdk/auth/kg.py` 仍是登录流程的 stub(禁止修改),因此设备指纹与 android/web
签名在这里单独实现:取流(apis/kg.py)与取流兜底都要用同一套指纹,放这里避免重复。
"""
from __future__ import annotations
import hashlib
import uuid
from typing import Any
from urllib.parse import quote
from app.sdk.auth_store import get_credentials, set_credentials
from app.sdk.common.request import http_fetch
from app.sdk.common.utils import to_md5
ANDROID_SALT = "OIlwieks28dk2k092lksi2UIkp"
WEB_SALT = "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt"
_RANDOM_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789"
def signature_params(params: str, platform: str = "android", body: str = "") -> str:
"""按 key 排序拼接后以盐包裹取 MD5(android 与 web 只是盐不同)。"""
key = WEB_SALT if platform == "web" else ANDROID_SALT
items = params.split("&")
items.sort()
return to_md5(f"{key}{''.join(items)}{body}{key}")
def android_sign(params: dict[str, Any], data: str = "") -> str:
"""网关签名:`${ANDROID_SALT}排序后的k=v拼接${data}${ANDROID_SALT}` 的 MD5。"""
import json
parts = []
for key in sorted(params):
value = params[key]
# JSON.stringify 是紧凑格式(无空格),登录/注册签名依赖逐字节一致
encoded = (
json.dumps(value, ensure_ascii=False, separators=(",", ":")) if isinstance(value, (dict, list)) else value
)
parts.append(f"{key}={encoded}")
return to_md5(f"{ANDROID_SALT}{''.join(parts)}{data}{ANDROID_SALT}")
def encode_uri_component(value: Any) -> str:
"""对齐 JS 的 encodeURIComponent(不编码 !'()*,Python 的 quote 会编码它们)。"""
return quote(str(value), safe="!~*'()")
def random_string(n: int) -> str:
import random
return "".join(_RANDOM_CHARS[random.randrange(len(_RANDOM_CHARS))] for _ in range(n))
def pkcs7_pad(data: bytes, block: int = 16) -> bytes:
"""PKCS#7 填充 —— Node 的 createCipheriv 会自动补,cryptography 不会。"""
remain = block - (len(data) % block)
return data + bytes([remain]) * remain
def pkcs7_unpad(data: bytes, block: int = 16) -> bytes:
"""PKCS#7 去填充(对应 Node createDecipheriv 的自动 unpad)。"""
if not data:
return data
remain = data[-1]
if remain < 1 or remain > block or len(data) < remain:
return data
return data[:-remain]
def get_device() -> dict[str, Any]:
"""设备指纹持久化:guid/mid/dfid 稳定复用(取流与登录共用同一指纹)。"""
dev = get_credentials("_kg_device")
if not dev:
dev = {"guid": str(uuid.uuid4()), "mid": "", "dfid": random_string(24)}
if not dev.get("mid"):
dev["mid"] = str(int(hashlib.md5(str(dev.get("guid") or "").encode()).hexdigest(), 16))
set_credentials("_kg_device", dev)
if not dev.get("dfid"):
dev["dfid"] = random_string(24)
return dev
async def create_http_fetch(url: str, options: dict[str, Any] | None = None, retry_num: int = 0) -> Any:
"""带重试的请求:非 200 或 errcode 非 0 一律重试(最多 3 次)。
成功时按原实现的取值优先级返回:body.data → body.info(数组时返回整 body) → body.info。
"""
if retry_num > 2:
raise RuntimeError("try max num")
try:
result = await http_fetch(url, **(options or {}))
except Exception as err: # noqa: BLE001 - 可降级失败:换一次请求重试
print(f"[kg] 请求失败将重试({retry_num + 1}/3) {url}: {err}")
return await create_http_fetch(url, options, retry_num + 1)
body = result.body if isinstance(result.body, dict) else {}
err_code = body.get("error_code", body.get("errcode", body.get("err_code")))
if result.status_code != 200 or err_code != 0:
return await create_http_fetch(url, options, retry_num + 1)
if body.get("data"):
return body["data"]
if isinstance(body.get("info"), list):
return body
return body.get("info")