"""Проверка отпечатков профилей: сгенерированный отпечаток применяется, стабилен внутри профиля, и сколько профилей из выборки различаются. Запускается внутри образа (нужны Chrome и Xvfb): make fp-check """ import asyncio import json import sys import tempfile from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src")) from api import profile # noqa: E402 from engine import AsyncStealthySession # noqa: E402 # то, что обычно входит в хеш fingerprint-библиотек и видно странице FP_JS = """async () => { const c = document.createElement('canvas'); c.width = 240; c.height = 60; const x = c.getContext('2d'); x.textBaseline = 'top'; x.font = '16px Arial'; x.fillStyle = '#f60'; x.fillRect(10, 10, 100, 30); x.fillStyle = '#069'; x.fillText('Robotex fp', 4, 20); const digest = async s => Array.from(new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(s)))) .slice(0, 6).map(b => b.toString(16).padStart(2, '0')).join(''); const uad = await navigator.userAgentData.getHighEntropyValues(['fullVersionList']); return { ua: navigator.userAgent, ua_ch: uad.fullVersionList.map(b => b.brand + '/' + b.version.split('.')[0]).join(','), screen: [screen.width, screen.height, devicePixelRatio], hwc: navigator.hardwareConcurrency, canvas: await digest(c.toDataURL()), }; }""" async def measure(user_data_dir: Path) -> dict: settings = profile.load_or_create(user_data_dir, None) kwargs = profile.browser_kwargs(settings) async with AsyncStealthySession(headless=False, real_chrome=True, user_data_dir=str(user_data_dir), retries=1, **kwargs) as s: return await s.fetch("https://example.com", page_action=lambda page: page.evaluate(FP_JS)) async def main(count: int = 6) -> int: dirs = [tempfile.TemporaryDirectory() for _ in range(count)] try: fps = [await measure(Path(d.name)) for d in dirs] again = await measure(Path(dirs[0].name)) saved = [profile.load(Path(d.name)) for d in dirs] finally: for d in dirs: d.cleanup() for i, fp in enumerate(fps): print(f"profile {i} {json.dumps(fp, ensure_ascii=False)}") print(f"profile 0 again {json.dumps(again, ensure_ascii=False)}") errors = [] if fps[0] != again: errors.append("profile 0 fingerprint changed between launches") major = profile.chrome_major() for i, (fp, st) in enumerate(zip(fps, saved)): want = [st["fingerprint"]["screen"]["width"], st["fingerprint"]["screen"]["height"], st["fingerprint"]["device_scale_factor"]] if fp["screen"] != want: errors.append(f"profile {i}: screen {fp['screen']} != saved {want}") if f"Chrome/{major}." not in fp["ua"] or f"Google Chrome/{major}" not in fp["ua_ch"]: errors.append(f"profile {i}: user agent does not match installed Chrome {major}") distinct = len({json.dumps(fp, sort_keys=True) for fp in fps}) print(f"distinct fingerprints: {distinct} of {count}") print("\n".join(errors) or "OK: generated fingerprints are applied and stable within a profile") return 1 if errors else 0 if __name__ == "__main__": sys.exit(asyncio.run(main()))