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

+161
View File
@@ -0,0 +1,161 @@
"""酷狗行为指纹模拟(对应 sdk/auth/kgSimulate.ts)—— sid/edt 生成。
移植自 MakcRe/KuGouMusicApi util/generate_simulate.js,用途:20028 二次验证时替代浏览器 WASM 上报行为数据。
协议:明文 = mid=..;userid=..;dfid=..;webgl=..;webdriver=0;ts=..;data=<事件流>
* edt = AES-128-CBC(明文, key=md5(rand16)[0:16], iv='kugousecurity123') → Base64
* sid = RSA-OAEP-SHA256(key) → Base64(公钥取自酷狗 WASM 二进制)
事件流:贝塞尔曲线鼠标轨迹(type 3) + 滚动(type 5) + 窗口(type 6),条目间以 : 分隔,
每条事件后跟一条哨兵记录(时间差字段替换为接近 0xFFFFFFFF 的随机值)。
"""
from __future__ import annotations
import base64
import math
import random
import time
from typing import Any
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from app.sdk.common.utils import to_md5
from app.sdk.platforms.kg.util import pkcs7_pad, random_string
SIM_PUBLIC_KEY = """-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoW2+Ylo8ALePSQTP0xBF
lFmEOHvBD9tS+s7DBlfKEu3RzzvZTaX1JtYbX4+AVUqj6ARz8IM+CKByqGFvbHN/
W64XxNI+q7z36ajCL3VTJ2W5G9MCJitc6oGbire4NQfhaEq0nC+hxBWQvCbIFflA
2ItrLUbSU7z1bHA/a+jlQm4OWvY+IKnTryOJTPuT1yNOVjbJ8wBLKy2DgQr9pPqW
PmEQtGpR5IM9V8Kao6PaSdKYOWGbX3i2+RzIKhvZUxxtJwdVbqPlDPlW9h4/xIBc
56Lgvr4aIl8nFtwbj4UJVUTFuGrs0tY9H/tXvZ22dUCKuGxW/gW7ZF+gXz6vHtYa
rQIDAQAB
-----END PUBLIC KEY-----"""
SIM_IV = b"kugousecurity123"
def _ri(low: int, high: int) -> int:
"""JS ``Math.floor(Math.random() * (max - min + 1)) + min``。"""
return math.floor(random.random() * (high - low + 1)) + low
def _round(value: float) -> int:
"""JS Math.round(.5 向上取整,不是 Python 的银行家舍入)。"""
return math.floor(value + 0.5)
_sentinel = 0xFFFFFFFF - _ri(0, 20)
def _f3(t: int | str, i: int, x: int, y: int) -> str:
return f"3,{t},{i},{x},{y}"
def _f5(t: int | str, i: int) -> str:
return f"5,{t},{i}"
def _f6(t: int | str, i: int, x: int, y: int) -> str:
return f"6,{t},{i},{x},{y}"
def _bezier_path(sx: float, sy: float, ex: float, ey: float, n: int) -> list[dict[str, float]]:
"""三阶贝塞尔鼠标轨迹:随机控制点 + 起步抖动大、后程收敛。"""
c1x = sx + (ex - sx) * 0.3 + _ri(-80, 80)
c1y = sy + (ey - sy) * 0.2 + _ri(-60, 60)
c2x = sx + (ex - sx) * 0.7 + _ri(-60, 60)
c2y = sy + (ey - sy) * 0.8 + _ri(-40, 40)
points: list[dict[str, float]] = []
for i in range(n + 1):
t = i / n
u = 1 - t
x = u * u * u * sx + 3 * u * u * t * c1x + 3 * u * t * t * c2x + t * t * t * ex
y = u * u * u * sy + 3 * u * u * t * c1y + 3 * u * t * t * c2y + t * t * t * ey
jitter = max(0.5, 3 - t * 2.5)
points.append(
{
"x": x + (random.random() - 0.5) * jitter,
"y": y + (random.random() - 0.5) * jitter,
}
)
return points
def _generate_edt_data(start_x: int, start_y: int, end_x: int, end_y: int, mouse_points: int) -> str:
entries: list[str] = []
ts = 0
ei = 0
entries.extend([_f5(0, 0), _f5(_sentinel, 0), _f5(0, 0), _f5(_sentinel, 0)])
ts += _ri(5, 20)
entries.extend([_f6(ts, ei, 750, 500), _f6(_sentinel, ei, 750, 500)])
ei += 1
for _ in range(3):
ts += _ri(80, 600)
entries.extend([_f5(ts, ei), _f5(_sentinel, ei)])
ei += 1
path = _bezier_path(start_x, start_y, end_x, end_y, mouse_points)
si = 0
for i, point in enumerate(path):
ts += _ri(8, 50)
rx = _round(point["x"])
ry = _round(point["y"])
entries.extend([_f3(ts, si, rx, ry), _f3(_sentinel, si, rx, ry)])
if i > 0 and i % 12 == 0:
ts += _ri(20, 60)
entries.extend([_f5(ts, ei), _f5(_sentinel, ei)])
ei += 1
si = (si + 1) % 2
ts += _ri(5, 30)
entries.extend(
[
_f3(ts, 1, _round(end_x + _ri(-5, 5)), _round(end_y + _ri(-5, 5))),
_f3(_sentinel, 1, _round(end_x), _round(end_y)),
]
)
return ":".join(entries)
_public_key = None
def _load_public_key() -> Any:
global _public_key
if _public_key is None:
_public_key = serialization.load_pem_public_key(SIM_PUBLIC_KEY.encode())
return _public_key
def generate_simulate(mid: str | int, userid: str | int, dfid: str, webgl_hash: str = "") -> dict[str, str]:
"""生成模拟行为指纹(sid/edt),验证接口 /v4/verify_user_info 原样上送。"""
global _sentinel
_sentinel = 0xFFFFFFFF - _ri(0, 20)
key = to_md5(random_string(16))[:16]
points = _ri(30, 60)
start_x = _ri(200, 600)
start_y = _ri(200, 500)
end_x = _ri(500, 700)
end_y = _ri(80, 150)
webgl = webgl_hash or to_md5(random_string(32))
edt_data = _generate_edt_data(start_x, start_y, end_x, end_y, points)
plaintext = (
f"mid={mid or 0};userid={userid or 0};dfid={dfid or 0};webgl={webgl};"
f"webdriver=0;ts={int(time.time() * 1000)};data={edt_data}"
)
cipher = Cipher(algorithms.AES(key.encode()), modes.CBC(SIM_IV))
encryptor = cipher.encryptor()
edt = base64.b64encode(encryptor.update(pkcs7_pad(plaintext.encode())) + encryptor.finalize()).decode()
sid = base64.b64encode(
_load_public_key().encrypt(
key.encode(),
padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()), algorithm=hashes.SHA256(), label=None),
)
).decode()
return {"edt": edt, "sid": sid}