148 lines
6.4 KiB
Python
148 lines
6.4 KiB
Python
"""端到端: 伪装成 Android WebView 的真实浏览器自动解快手滑块。
|
|
|
|
350014 anti-check 根因(已确诊): Playwright Chrome 泄漏
|
|
- navigator.webdriver = true
|
|
- navigator.platform = "Win32" (应 Linux armv8l)
|
|
- WebGL renderer = Intel Arc D3D11 (应 Adreno)
|
|
→ 服务端识别为桌面自动化浏览器 → 350014。这也解释用户"PC浏览器怎么滑都过不去"
|
|
(真人 PC 浏览器 platform=Win32 + 桌面 GPU 同样被永久标记)。
|
|
修复: add_init_script 在 SDK 采指纹前伪装 webdriver/platform/WebGL=Adreno,
|
|
注入设备 cookie(did/kpn/...), 复用 _auto_solve_slider(ddddocr+offset)。
|
|
"""
|
|
from __future__ import annotations
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from core.captcha_assist import ( # noqa: E402
|
|
_auto_solve_slider,
|
|
_update_captcha_assets,
|
|
_wait_captcha_assets,
|
|
)
|
|
from playwright.sync_api import sync_playwright # noqa: E402
|
|
|
|
OFFSETS = [-48, -36, -24, -60, -12, 0, 12, 24, 36, 48]
|
|
UA = ("Mozilla/5.0 (Linux; Android 16; PJZ110 Build/UKQ1.230917.001; wv) "
|
|
"AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.134 "
|
|
"Mobile Safari/537.36")
|
|
|
|
# 伪装 Android WebView 的 init script(在页面/iframe 任何脚本前注入)
|
|
SPOOF_INIT = r"""
|
|
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
|
|
Object.defineProperty(navigator, 'platform', {get: () => 'Linux armv8l'});
|
|
try { Object.defineProperty(navigator, 'appVersion', {get: () => '5.0 (Linux; Android 16; PJZ110) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/126.0.6478.134 Mobile Safari/537.36'}); } catch(e){}
|
|
try { Object.defineProperty(navigator, 'maxTouchPoints', {get: () => 5}); } catch(e){}
|
|
(function(){
|
|
const V='Qualcomm', R='Adreno (TM) 830';
|
|
function hook(proto){
|
|
if(!proto) return;
|
|
const orig = proto.getParameter;
|
|
proto.getParameter = function(p){
|
|
if(p===37445) return V; // UNMASKED_VENDOR_WEBGL
|
|
if(p===37446) return R; // UNMASKED_RENDERER_WEBGL
|
|
if(p===7937) return R; // RENDERER
|
|
if(p===7936) return V; // VENDOR
|
|
return orig.apply(this, arguments);
|
|
};
|
|
}
|
|
if(window.WebGLRenderingContext) hook(WebGLRenderingContext.prototype);
|
|
if(window.WebGL2RenderingContext) hook(WebGL2RenderingContext.prototype);
|
|
})();
|
|
"""
|
|
|
|
DEV_COOKIES = {
|
|
"kpn": "NEBULA", "kpf": "ANDROID_PHONE", "userId": "0",
|
|
"did": "ANDROID_" + "0" * 16, "didv": "1751000000000",
|
|
"c": "CN", "ver": "14.5.50", "appver": "14.5.50.11631",
|
|
"language": "zh-cn", "countryCode": "+86", "sys": "ANDROID_16",
|
|
"mod": "OnePlus(PJZ110)", "deviceName": "OnePlus(PJZ110)",
|
|
"net": "WIFI", "client_key": "2ac2a76d", "os": "android",
|
|
}
|
|
|
|
|
|
def harvest_error_url(mobile: str) -> str:
|
|
out = subprocess.run(
|
|
[sys.executable, str(ROOT / "tools" / "sms_login_cli.py"),
|
|
"--mobile", mobile, "--code", "000000"],
|
|
capture_output=True, text=True, cwd=str(ROOT), timeout=120,
|
|
).stdout
|
|
m = re.search(r'https://app\.m\.kuaishou\.com/verify/captcha\.html\?key=-?\d+[^"]*', out)
|
|
if not m:
|
|
raise RuntimeError(f"harvest fail:\n{out[-600:]}")
|
|
return m.group(0)
|
|
|
|
|
|
def main() -> int:
|
|
error_url = harvest_error_url("17666663175")
|
|
print(f"[e2e] error_url={error_url[:90]}", flush=True)
|
|
|
|
verify_results: list[dict] = []
|
|
assets: dict = {"config": {}, "bg": b"", "cut": b""}
|
|
solved_token = ""
|
|
|
|
def on_response(resp):
|
|
if "kSecretApiVerify" in resp.url and resp.request.method == "POST":
|
|
try:
|
|
body = resp.json()
|
|
except Exception:
|
|
return
|
|
verify_results.append({"result": body.get("result"), "desc": body.get("desc"),
|
|
"token": (body.get("captchaToken") or "")[:24]})
|
|
print(f"[e2e] verify -> result={body.get('result')} desc={body.get('desc')}", flush=True)
|
|
_update_captcha_assets(resp, assets)
|
|
|
|
with sync_playwright() as pw:
|
|
# 关键: 不用 --use-gl=swiftshader(软件渲染→WebGL canvas 指纹=SwiftShader,
|
|
# kwpsec 当作 bot→350014)。用真 Chrome 默认硬件 GL(probe 实测这样 anti-bot
|
|
# 过→350002)。也不加 SPOOF_INIT: 真硬件 GL 是 Intel ANGLE, 若 getParameter
|
|
# 谎报 Adreno 会与真实 canvas 像素哈希冲突, 反而被识破。真 Chrome 默认指纹即通过。
|
|
browser = pw.chromium.launch(channel="chrome", headless=True,
|
|
args=["--no-sandbox", "--disable-web-security"])
|
|
ctx = browser.new_context(user_agent=UA, viewport={"width": 412, "height": 915},
|
|
device_scale_factor=3, is_mobile=True, has_touch=True,
|
|
locale="zh-CN")
|
|
ctx.add_cookies([{"name": k, "value": v, "domain": ".kuaishou.com", "path": "/"}
|
|
for k, v in DEV_COOKIES.items()])
|
|
page = ctx.new_page()
|
|
page.on("response", on_response)
|
|
page.goto(error_url, wait_until="domcontentloaded", timeout=30000)
|
|
|
|
for attempt, off in enumerate(OFFSETS):
|
|
try:
|
|
_wait_captcha_assets(page, assets, timeout=20)
|
|
except Exception as e:
|
|
print(f"[e2e] wait assets fail: {e}", flush=True)
|
|
break
|
|
try:
|
|
r = _auto_solve_slider(page, assets, timeout=20, offset=off)
|
|
print(f"[e2e] drag#{attempt} offset={off} target_x={r.get('target_x')} "
|
|
f"drag={r.get('drag')}", flush=True)
|
|
except Exception as e:
|
|
print(f"[e2e] drag#{attempt} err: {e}", flush=True)
|
|
deadline = time.monotonic() + 4
|
|
while time.monotonic() < deadline:
|
|
page.wait_for_timeout(200)
|
|
if verify_results and verify_results[-1]["result"] == 1:
|
|
break
|
|
if verify_results and verify_results[-1]["result"] == 1:
|
|
solved_token = verify_results[-1]["token"]
|
|
break
|
|
assets["bg"] = b""; assets["cut"] = b""
|
|
page.wait_for_timeout(2500)
|
|
browser.close()
|
|
|
|
print("\n===== SUMMARY =====", flush=True)
|
|
print("verify_results:", json.dumps(verify_results, ensure_ascii=False), flush=True)
|
|
print("SOLVED:", bool(solved_token), "token:", solved_token or "(none)", flush=True)
|
|
return 0 if solved_token else 2
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|