|
|
@@ -1,6 +1,7 @@
|
|
|
import time
|
|
|
import json
|
|
|
import random
|
|
|
+import socket
|
|
|
import uuid
|
|
|
import shutil
|
|
|
import re
|
|
|
@@ -18,7 +19,9 @@ from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult,
|
|
|
from toolkit.proxy_tunnel import ProxyTunnel
|
|
|
from toolkit.vs_cloud_api import VSCloudApi
|
|
|
from utils.mouse import HumanMouse
|
|
|
-from utils.scroll import HumanScroll
|
|
|
+from utils.keyboard import HumanKeyboard
|
|
|
+from utils.fingerprint_utils import FingerprintGenerator
|
|
|
+
|
|
|
|
|
|
class BrowserResponse:
|
|
|
def __init__(self, result_dict):
|
|
|
@@ -116,13 +119,6 @@ class ItaPlugin(IVSPlg):
|
|
|
"""
|
|
|
self._log(f"Initializing Session (ID: {self.instance_id})...")
|
|
|
co = ChromiumOptions()
|
|
|
- # -------------------------------------------------------------
|
|
|
- # [核心修复] 解决 'not enough values to unpack'
|
|
|
- # -------------------------------------------------------------
|
|
|
- # 1. 不要用 co.auto_port(),因为它依赖解析 stdout,会被 DBus 报错干扰
|
|
|
- # 2. 我们手动随机生成一个端口
|
|
|
- import random
|
|
|
- import socket
|
|
|
|
|
|
def get_free_port():
|
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
|
@@ -131,80 +127,116 @@ class ItaPlugin(IVSPlg):
|
|
|
|
|
|
debug_port = get_free_port()
|
|
|
self._log(f"Assigned Debug Port: {debug_port}")
|
|
|
-
|
|
|
- # --- [关键配置] 设置独立的用户数据目录 ---
|
|
|
- # 这样每个实例的 Cache, Cookies, LocalStorage 都是完全隔离的
|
|
|
- # 同时也防止了多进程争抢同一个 Default 文件夹导致的崩溃
|
|
|
co.set_user_data_path(self.user_data_path)
|
|
|
|
|
|
- # --- 1. 指定浏览器路径 (适配 Docker) ---
|
|
|
chrome_path = os.getenv("CHROME_BIN")
|
|
|
if chrome_path and os.path.exists(chrome_path):
|
|
|
co.set_paths(browser_path=chrome_path)
|
|
|
|
|
|
- # --- [核心修改] 代理配置 ---
|
|
|
if self.config.proxy and self.config.proxy.ip:
|
|
|
p = self.config.proxy
|
|
|
|
|
|
if p.username and p.password:
|
|
|
self._log(f"Starting Proxy Tunnel for {p.ip}...")
|
|
|
|
|
|
- # 1. 启动本地隧道
|
|
|
self.tunnel = ProxyTunnel(p.ip, p.port, p.username, p.password)
|
|
|
local_proxy = self.tunnel.start()
|
|
|
|
|
|
self._log(f"Tunnel started at {local_proxy}")
|
|
|
-
|
|
|
- # 2. Chrome 连接本地免密端口
|
|
|
- # 必须使用 --proxy-server 强制指定,绝对稳健
|
|
|
co.set_argument(f'--proxy-server={local_proxy}')
|
|
|
|
|
|
else:
|
|
|
- # 无密码代理,直接用
|
|
|
proxy_str = f"{p.scheme}://{p.ip}:{p.port}"
|
|
|
co.set_argument(f'--proxy-server={proxy_str}')
|
|
|
else:
|
|
|
self._log("[WARN] No proxy configured!")
|
|
|
|
|
|
+ fingerprint_gen = FingerprintGenerator()
|
|
|
+ specific_fp = fingerprint_gen.generate(self.config.account.username)
|
|
|
+ self._log(f'browser fingerprint={specific_fp}')
|
|
|
+
|
|
|
co.headless(False)
|
|
|
co.set_argument('--no-sandbox')
|
|
|
- co.set_argument('--disable-gpu')
|
|
|
+ # co.set_argument('--disable-gpu')
|
|
|
co.set_argument('--disable-dev-shm-usage')
|
|
|
co.set_argument('--window-size=1920,1080')
|
|
|
co.set_argument('--disable-blink-features=AutomationControlled')
|
|
|
-
|
|
|
+ co.set_argument('--ignore-gpu-blocklist') # 忽略无显卡黑名单
|
|
|
+ co.set_argument('--enable-webgl') # 强制开启 WebGL
|
|
|
+ co.set_argument('--use-gl=angle') # 使用 ANGLE 渲染后端
|
|
|
+ co.set_argument('--use-angle=swiftshader')# 强制使用 CPU 进行 3D 渲染 (这步最关键!)
|
|
|
+ co.set_argument(f"--fingerprint={specific_fp.get('seed')}")
|
|
|
+ co.set_argument(f"--fingerprint-platform={specific_fp.get('platform')}")
|
|
|
+ co.set_argument(f"--fingerprint-brand={specific_fp.get('brand')}")
|
|
|
try:
|
|
|
self.page = ChromiumPage(co)
|
|
|
+ if self.config.debug:
|
|
|
+ self.page.get('https://example.com')
|
|
|
+ js_script = """
|
|
|
+ function getFingerprint() {
|
|
|
+ let webglVendor = 'Unknown';
|
|
|
+ let webglRenderer = 'Unknown';
|
|
|
+ try {
|
|
|
+ let canvas = document.createElement('canvas');
|
|
|
+ let gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
|
|
|
+ if (gl) {
|
|
|
+ let debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
|
|
|
+ if (debugInfo) {
|
|
|
+ webglVendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
|
|
|
+ webglRenderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch(e) {}
|
|
|
+
|
|
|
+ return {
|
|
|
+ "User-Agent": navigator.userAgent,
|
|
|
+ "Platform": navigator.userAgentData ? navigator.userAgentData.platform : navigator.platform,
|
|
|
+ "Brands": navigator.userAgentData ? navigator.userAgentData.brands.map(b => b.brand).join(', ') : 'Not Supported',
|
|
|
+ "CPU Cores": navigator.hardwareConcurrency,
|
|
|
+ "Language": navigator.language,
|
|
|
+ "Timezone": Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
|
+ "WebGL Vendor": webglVendor,
|
|
|
+ "WebGL Renderer": webglRenderer
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return getFingerprint();
|
|
|
+ """
|
|
|
+
|
|
|
+ fp_data = self.page.run_js(js_script)
|
|
|
+ self._log("================ 预检浏览器指纹数据 ================")
|
|
|
+ self._log(json.dumps(fp_data, indent=4, ensure_ascii=False))
|
|
|
+ self._log("====================================================")
|
|
|
|
|
|
login_url = f"{self._host}/Home"
|
|
|
self._log(f"Navigating to {login_url}")
|
|
|
self.page.get(login_url)
|
|
|
|
|
|
+ self._log("Init humanize tools...")
|
|
|
+ self.mouse = HumanMouse(self.page, debug=True)
|
|
|
+ self.keyboard = HumanKeyboard(self.page)
|
|
|
+ self._log("Random mouse start position...")
|
|
|
+ viewport_width = self.page.rect.viewport_size[0]
|
|
|
+ viewport_height = self.page.rect.viewport_size[1]
|
|
|
+ init_x = random.randint(10, viewport_width - 10)
|
|
|
+ init_y = random.randint(10, viewport_height - 10)
|
|
|
+ self.mouse.move(init_x, init_y)
|
|
|
+
|
|
|
# 等待登录框
|
|
|
if not self.page.wait.ele_displayed('#login-email', timeout=20):
|
|
|
raise BizLogicError("Login page not loaded")
|
|
|
|
|
|
# 填充用户名密码
|
|
|
- self.page.ele('#login-email').input(self.config.account.username)
|
|
|
- self.page.ele('#login-password').input(self.config.account.password)
|
|
|
+ self.mouse.human_click_ele(self.page.ele('#login-email'))
|
|
|
+ self.keyboard.type_text(self.config.account.username)
|
|
|
+
|
|
|
+ self.mouse.human_click_ele(self.page.ele('#login-password'))
|
|
|
+ self.keyboard.type_text(self.config.account.password)
|
|
|
|
|
|
- # 4. [核心修改] 解决 ReCaptcha V3 Enterprise 并注入
|
|
|
- # Prenotami 使用的是 Enterprise V3, Action = 'LOGIN'
|
|
|
- self._solve_and_inject_prenotami_captcha()
|
|
|
- human_mouse = HumanMouse(self.page, debug=True)
|
|
|
- human_scroll = HumanScroll(self.page)
|
|
|
# 先定位
|
|
|
self._log("Locating Login button...")
|
|
|
- login_btn = self.page.ele('@id=captcha-trigger')
|
|
|
-
|
|
|
- self._log("Scrolling to make button visible...")
|
|
|
- human_scroll.scroll_to_element(login_btn, humanize=True)
|
|
|
+ login_btn = self.page.ele('#captcha-trigger')
|
|
|
|
|
|
- self._log("Moving mouse to Login button...")
|
|
|
- human_mouse.move_to(login_btn, duration=random.uniform(0.6, 1.0))
|
|
|
- time.sleep(random.uniform(0.3, 0.5))
|
|
|
- self._log("Clicking Login button...")
|
|
|
- login_btn.click()
|
|
|
+ self.mouse.human_click_ele(login_btn)
|
|
|
self._log("Login button clicked.")
|
|
|
|
|
|
# 等待 URL 变化或特定元素出现
|