瀏覽代碼

feat: update

jerry 3 周之前
父節點
當前提交
02abffa413
共有 5 個文件被更改,包括 153 次插入173 次删除
  1. 8 16
      config/config.json
  2. 90 89
      plugins/tls_plugin.py
  3. 0 0
      test/__init__.py
  4. 48 46
      tls_registration_bot.py
  5. 7 22
      utils/mouse.py

+ 8 - 16
config/config.json

@@ -1012,16 +1012,12 @@
                     "country": "France"
                     "country": "France"
                 }
                 }
             ],
             ],
-            "website": "https://visas-fr.tlscontact.com/country/gb/vac/gbLON2fr/",
+            "website": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
             "free_config": {
             "free_config": {
+                "tls_url": "https://visas-fr.tlscontact.com/en-us/country/gb/vac/gbLON2fr",
+                "location": "London",
                 "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
                 "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-                "apt_config": {
-                    "code": "gbLON2fr",
-                    "country": "gb",
-                    "mission": "fr",
-                    "city": "London"
-                },
-                "interest_month": "02-2026",
+                "interest_month": "06-2026",
                 "target_labels": [
                 "target_labels": [
                     "",
                     "",
                     "pta"
                     "pta"
@@ -1031,7 +1027,7 @@
         {
         {
             "identifier": "tls.cn.cng.fr",
             "identifier": "tls.cn.cng.fr",
             "debug": false,
             "debug": false,
-            "enable": false,
+            "enable": true,
             "need_account": true,
             "need_account": true,
             "need_proxy": true,
             "need_proxy": true,
             "proxy_pool": "local",
             "proxy_pool": "local",
@@ -1072,15 +1068,11 @@
                     "country": "France"
                     "country": "France"
                 }
                 }
             ],
             ],
-            "website": "https://visas-fr.tlscontact.com/country/cn/vac/cnCNG2fr/",
+            "website": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnCNG2fr",
             "free_config": {
             "free_config": {
+                "tls_url": "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnCNG2fr",
+                "location": "Chengdu",
                 "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
                 "capsolver_key": "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A",
-                "apt_config": {
-                    "code": "cnCNG2fr",
-                    "country": "cn",
-                    "mission": "fr",
-                    "city": "Chengdu"
-                },
                 "interest_month": "06-2026",
                 "interest_month": "06-2026",
                 "target_labels": [
                 "target_labels": [
                     "", "pta"
                     "", "pta"

+ 90 - 89
plugins/tls_plugin.py

@@ -17,6 +17,8 @@ from vs_plg import IVSPlg
 from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult, AvailabilityStatus, TimeSlot, DateAvailability, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from vs_types import VSPlgConfig, AppointmentType, VSQueryResult, VSBookResult, AvailabilityStatus, TimeSlot, DateAvailability, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
 from toolkit.proxy_tunnel import ProxyTunnel
 from toolkit.proxy_tunnel import ProxyTunnel
+from utils.mouse import HumanMouse
+from utils.keyboard import HumanKeyboard
 
 
 
 
 class BrowserResponse:
 class BrowserResponse:
@@ -51,6 +53,8 @@ class TlsPlugin(IVSPlg):
         self.is_healthy = True
         self.is_healthy = True
         self.logger = None
         self.logger = None
         
         
+        self.mouse = None
+        self.keyboard = None
         self.page: Optional[ChromiumPage] = None    
         self.page: Optional[ChromiumPage] = None    
         self.travel_group: Optional[Dict] = None
         self.travel_group: Optional[Dict] = None
         
         
@@ -67,7 +71,7 @@ class TlsPlugin(IVSPlg):
     def get_group_id(self) -> str:
     def get_group_id(self) -> str:
         return self.group_id
         return self.group_id
     
     
-    def set_log(self, logger: Callable[[str], None]) -> None:
+    def set_log(self, logger: Callable[[str], None]):
         self.logger = logger
         self.logger = logger
     
     
     def _log(self, message):
     def _log(self, message):
@@ -162,53 +166,36 @@ class TlsPlugin(IVSPlg):
 
 
         try:
         try:
             self.page = ChromiumPage(co)
             self.page = ChromiumPage(co)
-            
-            apt_config = self.free_config.get('apt_config', {})
-            if not apt_config:
-                raise NotFoundError("apt_config config missing")
-
-            login_url = "https://visas-fr.tlscontact.com/en-us/login"
-            params = {
-                "issuerId": apt_config["code"],
-                "country": apt_config["country"],
-                "vac": apt_config["code"],
-                "redirect": f"/en-us/country/{apt_config['country']}/vac/{apt_config['code']}"
-            }
-            full_login_url = f"{login_url}?{urlencode(params)}"
-            
-            self._log(f"Navigating: {full_login_url}")
-            self.page.get(full_login_url)
-            
-            cf = CloudflareBypasser(self.page, log=self.config.debug)
-            if not cf.bypass(max_retry=15):
+            tls_url = self.free_config.get('tls_url', '')
+            self._log(f"Navigating: {tls_url}")
+            self.page.get(tls_url)
+            time.sleep(5)
+            cf_bypasser = CloudflareBypasser(self.page, log=True)
+            if not cf_bypasser.bypass(max_retry=15):
                 raise BizLogicError("Cloudflare bypass timeout")
                 raise BizLogicError("Cloudflare bypass timeout")
+            time.sleep(3)
+            cf_bypasser.handle_waiting_room()
             
             
-            wait_start = time.time()
-            while True:
-                html = self.page.html.lower()
-                cloudflare_waitingroom_indicators = [
-                    "file d'attente" in html,
-                    'waiting room' in html
-                ]
-                if any(cloudflare_waitingroom_indicators):
-                    if time.time() - wait_start > 60 * 60:
-                        raise BizLogicError(message="Cloudflare waiting room timeout (1h).")
-                        
-                    self._log("In Waiting Room... Waiting for auto-refresh.")
-                    time.sleep(30)
-                else:
-                    break
-
-            # --- 登录页面检查 ---
-            if not self.page.ele('#email-input-field'):
-                self._log("Reloading Login Page...")
-                self.page.get(full_login_url)
-                if not self.page.wait.ele_displayed('#email-input-field', timeout=15):
-                    self._save_screenshot("login_load_fail")
-                    raise BizLogicError("Login form not loaded")
+            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) 
+            
+            btn_selector = '#btn-login'
+            if not self.page.wait.ele_displayed(btn_selector, timeout=3):
+                register_btn = self.page.ele("tag:a@@href:login")
+                self.mouse.human_click_ele(register_btn)
+                time.sleep(3)
+            if not self.page.wait.ele_displayed(btn_selector, timeout=10):
+                raise BizLogicError(message=f"Can't find selector={btn_selector}")
+            time.sleep(random.uniform(0.5, 1))
 
 
-            # --- JS 注入登录 ---
-            g_token = ""
+            recaptchav2_token = ""
             if self.page.ele('.g-recaptcha') or self.page.ele('xpath://iframe[contains(@src, "recaptcha")]'):
             if self.page.ele('.g-recaptcha') or self.page.ele('xpath://iframe[contains(@src, "recaptcha")]'):
                 self._log("Solving ReCaptcha...")
                 self._log("Solving ReCaptcha...")
                 rc_params = {
                 rc_params = {
@@ -217,60 +204,81 @@ class TlsPlugin(IVSPlg):
                     "siteKey": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0", 
                     "siteKey": "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0", 
                     "apiToken": self.free_config.get("capsolver_key", "")
                     "apiToken": self.free_config.get("capsolver_key", "")
                 }
                 }
-                g_token = self._solve_recaptcha(rc_params)
+                recaptchav2_token = self._solve_recaptcha(rc_params)
 
 
             username = self.config.account.username
             username = self.config.account.username
             password = self.config.account.password
             password = self.config.account.password
             
             
-            js_login = f"""
-            var u = document.getElementById('email-input-field');
-            if(u) {{ u.value = "{username}"; u.dispatchEvent(new Event('input', {{bubbles:true}})); }}
+            input_ele = self.page.ele('#email-input-field')
+            self.mouse.human_click_ele(input_ele)
+            time.sleep(random.uniform(0.2, 0.6))
+            self.keyboard.type_text(username, humanize=True)
             
             
-            var p = document.getElementById('password-input-field');
-            if(p) {{ p.value = "{password}"; p.dispatchEvent(new Event('input', {{bubbles:true}})); }}
-            
-            var g = document.getElementById('g-recaptcha-response');
-            if(g) {{ g.value = "{g_token}"; }}
+            time.sleep(random.uniform(0.5, 1.2)) 
+        
+            pwd_ele = self.page.ele('#password-input-field')
+            self.mouse.human_click_ele(pwd_ele)
+            time.sleep(random.uniform(0.2, 0.6))
+            self.keyboard.type_text(password, humanize=True)
             
             
-            var btn = document.getElementById('btn-login');
-            if(btn) {{ btn.click(); return true; }} else {{ return false; }}
-            """
+            if recaptchav2_token:
+                inject_recaptchav2_token_js = f"""
+                var g = document.getElementById('g-recaptcha-response');
+                if(g) {{ g.value = "{recaptchav2_token}"; }}
+                """
+                self._log("Inject ReCaptchaV2 Token via JS...")
+                self.page.run_js(inject_recaptchav2_token_js)
+                time.sleep(random.uniform(0.5, 1.0))
             
             
-            self._log("Submitting Login via JS...")
-            if not self.page.run_js(js_login):
-                raise BizLogicError("Login button missing")
+            self._log("Submitting Login...")
+            time.sleep(random.uniform(0.3, 0.8))
+            login_btn = self.page.ele('#btn-login')
+            self.mouse.human_click_ele(login_btn)
 
 
             self._log("Waiting for redirect...")
             self._log("Waiting for redirect...")
             self.page.wait.url_change('login-actions', exclude=True, timeout=45)
             self.page.wait.url_change('login-actions', exclude=True, timeout=45)
             
             
             time.sleep(3)
             time.sleep(3)
             if "login-actions" in self.page.url or "auth" in self.page.url:
             if "login-actions" in self.page.url or "auth" in self.page.url:
-                err = "Unknown Login Error"
-                if "Invalid username" in self.page.html:
-                    err = "Invalid Credentials"
                 self._save_screenshot("login_submit_fail")
                 self._save_screenshot("login_submit_fail")
-                raise BizLogicError(f"Login Failed: {err}")
+                raise BizLogicError(message="Login Failed! Invalid credentials or Captcha rejected.")
             
             
-            self._log("Waiting for dashboard...")
-            btn_selector = 'xpath://button[.//span[@data-testid="btn-create-new-travel-group"]]'
-            if not self.page.wait.ele_displayed(btn_selector, timeout=10):
-                raise BizLogicError(message=f"Waiting for selector={btn_selector} timeout")
-            html_content = self.page.html
-            groups = self._parse_travel_groups(html_content) 
-            target_city = apt_config['city'].lower()
+            self.page.wait.load_start()
+            time.sleep(5)
+            
+            groups = self._parse_travel_groups(self.page.html)
+            location = self.free_config.get('location')
             for g in groups:
             for g in groups:
-                if g['location'].lower() == target_city:
+                if g['location'] == location:
                     self.travel_group = g
                     self.travel_group = g
                     break
                     break
             
             
             if not self.travel_group:
             if not self.travel_group:
                 self._save_screenshot("group_not_found")
                 self._save_screenshot("group_not_found")
-                raise NotFoundError(f"Group not found for {target_city}")
+                raise NotFoundError(f"Group not found for {location}")
         
         
-            formgroup_id = self.travel_group.get('group_number')   
-            btn_selector = f'tag:button@@name=formGroupId@@value={formgroup_id}'            
-            self._log(f"Select group_id={formgroup_id} via JS...")
-            self.page.ele(btn_selector).click(by_js=True)
+            formgroup_id = self.travel_group.get('group_number')
+
+            btn_selector = f'tag:button@@name=formGroupId@@value={formgroup_id}'
+            self._log(f"Waiting for visible button to render: {formgroup_id}...")
+            
+            self.page.wait.eles_loaded(btn_selector, timeout=15)
+            
+            buttons = self.page.eles(btn_selector)
+            select_btn = None
+            for btn in reversed(buttons):
+                try:
+                    w, h = btn.rect.size
+                    if w > 0 and h > 0:
+                        select_btn = btn
+                        break
+                except Exception:
+                    continue
+            if not select_btn:
+                self._save_screenshot("visible_button_not_found")
+                raise BizLogicError(f"Can't find any visible Select button for group {formgroup_id}")
+            time.sleep(random.uniform(0.5, 1.2))
+            self.mouse.human_click_ele(select_btn)
             
             
             self._log("Waiting for url redirect...")
             self._log("Waiting for url redirect...")
             self.page.wait.url_change('travel-groups', exclude=True, timeout=45)
             self.page.wait.url_change('travel-groups', exclude=True, timeout=45)
@@ -291,7 +299,7 @@ class TlsPlugin(IVSPlg):
             self._log(f"Waiting for selector={btn_selector} to render...")
             self._log(f"Waiting for selector={btn_selector} to render...")
             if not self.page.wait.ele_displayed(btn_selector, timeout=15):
             if not self.page.wait.ele_displayed(btn_selector, timeout=15):
                 raise BizLogicError(message=f"Waiting for selector={btn_selector} timeout")
                 raise BizLogicError(message=f"Waiting for selector={btn_selector} timeout")
-            self.page.ele(btn_selector).click(by_js=True)
+            self.mouse.human_click_ele(self.page.ele(btn_selector))
 
 
             self._log("Waiting for url redirect...")
             self._log("Waiting for url redirect...")
             self.page.wait.url_change('service-level', exclude=True, timeout=45)
             self.page.wait.url_change('service-level', exclude=True, timeout=45)
@@ -309,6 +317,7 @@ class TlsPlugin(IVSPlg):
 
 
         except Exception as e:
         except Exception as e:
             self._log(f"Session Create Error: {e}")
             self._log(f"Session Create Error: {e}")
+            time.sleep(3600)
             self.cleanup()
             self.cleanup()
             raise e
             raise e
 
 
@@ -316,7 +325,6 @@ class TlsPlugin(IVSPlg):
         res = VSQueryResult()
         res = VSQueryResult()
         res.success = False
         res.success = False
         group_num = self.travel_group['group_number']
         group_num = self.travel_group['group_number']
-        apt_config = self.free_config.get('apt_config', {})
         interest_month = self.free_config.get("interest_month", time.strftime("%m-%Y"))
         interest_month = self.free_config.get("interest_month", time.strftime("%m-%Y"))
         
         
         target_date_obj = datetime.strptime(interest_month, "%m-%Y")
         target_date_obj = datetime.strptime(interest_month, "%m-%Y")
@@ -398,16 +406,9 @@ class TlsPlugin(IVSPlg):
 
 
         else:
         else:
             self._log(f"Already on '{target_month_text}'. Executing silent JS fetch...") 
             self._log(f"Already on '{target_month_text}'. Executing silent JS fetch...") 
-            url = f'https://visas-fr.tlscontact.com/en-us/{group_num}/workflow/appointment-booking'
-            params = {
-                'location': apt_config["code"],
-                'month': interest_month
-            }
-            
-            resp = self._perform_request("GET", url, params=params, retry_count=1)
-            html_content = resp.text
-            self._check_page_is_session_expired_or_invalid('Book your appointment', html_content)
-            all_slots = self._parse_appointment_slots(html_content)
+            resp = self._perform_request("GET", self.page.url, retry_count=1)
+            self._check_page_is_session_expired_or_invalid('Book your appointment', resp.text)
+            all_slots = self._parse_appointment_slots(resp.text)
 
 
         target_labels = self.free_config.get("target_labels", ["", "pta"])
         target_labels = self.free_config.get("target_labels", ["", "pta"])
         slots = [s for s in all_slots if s.get("label") in target_labels]
         slots = [s for s in all_slots if s.get("label") in target_labels]

+ 0 - 0
test/__init__.py


+ 48 - 46
tls_registration_bot.py

@@ -65,14 +65,12 @@ class TlsRegistrator:
 
 
         # 3. 反爬及稳定性配置
         # 3. 反爬及稳定性配置
         co.headless(False)
         co.headless(False)
-        co.set_argument('--lang=en-US')
         co.set_argument('--no-sandbox')
         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('--disable-dev-shm-usage')
         co.set_argument('--window-size=1920,1080')
         co.set_argument('--window-size=1920,1080')
         co.set_argument('--disable-blink-features=AutomationControlled')
         co.set_argument('--disable-blink-features=AutomationControlled')
         self.page = ChromiumPage(co)
         self.page = ChromiumPage(co)
-        self.page.run_cdp('Emulation.setLocaleOverride', locale='en-US')
         self.page.get(self.tls_url)
         self.page.get(self.tls_url)
         cf_bypasser = CloudflareBypasser(self.page, log=True)
         cf_bypasser = CloudflareBypasser(self.page, log=True)
         cf_bypasser.bypass()
         cf_bypasser.bypass()
@@ -143,43 +141,40 @@ class TlsRegistrator:
         btn_selector = '#submit'
         btn_selector = '#submit'
         if not self.page.wait.ele_displayed(btn_selector, timeout=3):
         if not self.page.wait.ele_displayed(btn_selector, timeout=3):
             register_btn = self.page.ele("tag:a@@href:registration")
             register_btn = self.page.ele("tag:a@@href:registration")
-            self.mouse.move_to(register_btn)
-            self.mouse.click(register_btn.rect.midpoint[0], register_btn.rect.midpoint[1], humanize=True)
+            self.mouse.human_click_ele(register_btn)
             time.sleep(3)
             time.sleep(3)
         if not self.page.wait.ele_displayed(btn_selector, timeout=10):
         if not self.page.wait.ele_displayed(btn_selector, timeout=10):
             raise BizLogicError(message=f"Can't find selector={btn_selector}")
             raise BizLogicError(message=f"Can't find selector={btn_selector}")
         time.sleep(random.uniform(0.5, 1))
         time.sleep(random.uniform(0.5, 1))
         self._log("正在填写邮箱和密码...")
         self._log("正在填写邮箱和密码...")
         email_input_e = self.page.ele('#email')
         email_input_e = self.page.ele('#email')
-        self.mouse.click(int(email_input_e.rect.midpoint[0]), int(email_input_e.rect.midpoint[1]), humanize=True)
+        self.mouse.human_click_ele(email_input_e)
         self.keyboard.type_text(email, humanize=True)
         self.keyboard.type_text(email, humanize=True)
         time.sleep(random.uniform(0.2, 0.5))
         time.sleep(random.uniform(0.2, 0.5))
         
         
         password_e = self.page.ele('#password')
         password_e = self.page.ele('#password')
-        self.mouse.click(int(password_e.rect.midpoint[0]), int(password_e.rect.midpoint[1]), humanize=True)
+        self.mouse.human_click_ele(password_e)
         self.keyboard.type_text(password, humanize=True)
         self.keyboard.type_text(password, humanize=True)
         time.sleep(random.uniform(0.2, 0.5))
         time.sleep(random.uniform(0.2, 0.5))
         
         
         confirm_password_e = self.page.ele('#confirm-password')
         confirm_password_e = self.page.ele('#confirm-password')
-        self.mouse.click(int(confirm_password_e.rect.midpoint[0]), int(confirm_password_e.rect.midpoint[1]), humanize=True)
+        self.mouse.human_click_ele(confirm_password_e)
         self.keyboard.type_text(password, humanize=True)
         self.keyboard.type_text(password, humanize=True)
         time.sleep(random.uniform(0.2, 0.5))
         time.sleep(random.uniform(0.2, 0.5))
 
 
         self._log("正在勾选必选条款...")
         self._log("正在勾选必选条款...")
         for checkbox_id in ['#terms-and-conditions', '#biometric-data', '#privacy-notice']:
         for checkbox_id in ['#terms-and-conditions', '#biometric-data', '#privacy-notice']:
             check_box_e = self.page.ele(checkbox_id).next()
             check_box_e = self.page.ele(checkbox_id).next()
-            mx, my = int(check_box_e.rect.midpoint[0]), int(check_box_e.rect.midpoint[1])
-            self.mouse.click(mx, my, humanize=True)
+            self.mouse.human_click_ele(check_box_e)
             time.sleep(random.uniform(0.3, 0.6))
             time.sleep(random.uniform(0.3, 0.6))
                 
                 
         self._log("提交注册...")
         self._log("提交注册...")
         btn_e = self.page.ele(btn_selector)
         btn_e = self.page.ele(btn_selector)
-        
         btn_e.scroll.to_see(center=True) 
         btn_e.scroll.to_see(center=True) 
-        time.sleep(1)
-        
+        time.sleep(random.uniform(0.3, 0.6))
+
         btn_mx, btn_my = int(btn_e.rect.midpoint[0]), int(btn_e.rect.midpoint[1])
         btn_mx, btn_my = int(btn_e.rect.midpoint[0]), int(btn_e.rect.midpoint[1])
-        self.mouse.click(btn_mx, btn_my, humanize=True)
+        self.mouse.move(btn_mx, btn_my, humanize=True)
         
         
         time.sleep(0.5)
         time.sleep(0.5)
         btn_e.click(by_js=True)
         btn_e.click(by_js=True)
@@ -232,7 +227,7 @@ class TlsRegistrator:
             if not date_str:
             if not date_str:
                 return
                 return
             ele = page.ele(selector)
             ele = page.ele(selector)
-            ele.scroll.to_see(center=True)  # 滚动到屏幕中间
+            ele.scroll.to_see(center=True)
             ele.click()
             ele.click()
             time.sleep(0.2)
             time.sleep(0.2)
             year, month, day = date_str.split('-')
             year, month, day = date_str.split('-')
@@ -249,33 +244,40 @@ class TlsRegistrator:
         btn_selector = '#btn-login'
         btn_selector = '#btn-login'
         if not self.page.wait.ele_displayed(btn_selector, timeout=3):
         if not self.page.wait.ele_displayed(btn_selector, timeout=3):
             login_btn = self.page.ele("tag:a@@href:login")
             login_btn = self.page.ele("tag:a@@href:login")
-            self.mouse.move_to(login_btn)
-            self.mouse.click(login_btn.rect.midpoint[0], login_btn.rect.midpoint[1], humanize=True)
+            self.mouse.human_click_ele(login_btn)
             time.sleep(3)
             time.sleep(3)
         if not self.page.wait.ele_displayed(btn_selector, timeout=10):
         if not self.page.wait.ele_displayed(btn_selector, timeout=10):
             raise BizLogicError(message=f"Can't find selector={btn_selector}")
             raise BizLogicError(message=f"Can't find selector={btn_selector}")
-        g_token = ""
+        recpatchav2_token = ""
         if self.page.ele('.g-recaptcha') or self.page.ele('xpath://iframe[contains(@src, "recaptcha")]'):
         if self.page.ele('.g-recaptcha') or self.page.ele('xpath://iframe[contains(@src, "recaptcha")]'):
             self._log("Solving ReCaptcha...")
             self._log("Solving ReCaptcha...")
-            g_token = self.solve_captcha(self.page.url, "ReCaptchaV2TaskProxyLess", "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0")
+            recpatchav2_token = self.solve_captcha(self.page.url, "ReCaptchaV2TaskProxyLess", "6LcDpXcfAAAAAM7wOEsF_38DNsL20tTvPTKxpyn0")
         
         
-        js_login = f"""
-        var u = document.getElementById('email-input-field');
-        if(u) {{ u.value = "{email}"; u.dispatchEvent(new Event('input', {{bubbles:true}})); }}
-        
-        var p = document.getElementById('password-input-field');
-        if(p) {{ p.value = "{password}"; p.dispatchEvent(new Event('input', {{bubbles:true}})); }}
+        input_ele = self.page.ele('#email-input-field')
+        self.mouse.human_click_ele(input_ele)
+        time.sleep(random.uniform(0.2, 0.6))
+        self.keyboard.type_text(email, humanize=True)
         
         
-        var g = document.getElementById('g-recaptcha-response');
-        if(g) {{ g.value = "{g_token}"; }}
+        time.sleep(random.uniform(0.5, 1.2)) 
+    
+        pwd_ele = self.page.ele('#password-input-field')
+        self.mouse.human_click_ele(pwd_ele)
+        time.sleep(random.uniform(0.2, 0.6))
+        self.keyboard.type_text(password, humanize=True)
         
         
-        var btn = document.getElementById('btn-login');
-        if(btn) {{ btn.click(); return true; }} else {{ return false; }}
-        """
+        if recpatchav2_token:
+            inject_recpatchav2_token_js = f"""
+            var g = document.getElementById('g-recaptcha-response');
+            if(g) {{ g.value = "{recpatchav2_token}"; }}
+            """
+            self._log("Inject ReCaptchaV2 Token via JS...")
+            self.page.run_js(inject_recpatchav2_token_js)
+            time.sleep(random.uniform(0.5, 1.0))
         
         
-        self._log("Submitting Login via JS...")
-        if not self.page.run_js(js_login):
-            raise BizLogicError(message="Login button missing")
+        self._log("Submitting Login...")
+        time.sleep(random.uniform(0.3, 0.8))
+        login_btn = self.page.ele('#btn-login')
+        self.mouse.human_click_ele(login_btn)
 
 
         self._log("Waiting for dashboard redirect...")
         self._log("Waiting for dashboard redirect...")
         self.page.wait.url_change('login-actions', exclude=True, timeout=45)
         self.page.wait.url_change('login-actions', exclude=True, timeout=45)
@@ -311,8 +313,8 @@ class TlsRegistrator:
         formgroup_id = travel_group.get('formGroupId')
         formgroup_id = travel_group.get('formGroupId')
         self._log(f"Waiting for group button to render: {formgroup_id}")
         self._log(f"Waiting for group button to render: {formgroup_id}")
         btn_selector = f'tag:a@@data-testid=btn-select-group'            
         btn_selector = f'tag:a@@data-testid=btn-select-group'            
-        self._log(f"Select group_id={formgroup_id} via JS...")
-        self.page.ele(btn_selector).click(by_js=True)
+        self._log(f"Select group_id={formgroup_id}...")
+        self.mouse.human_click_ele(self.page.ele(btn_selector))
         
         
         self._log("Waiting for url redirect...")
         self._log("Waiting for url redirect...")
         self.page.wait.url_change('travel-groups', exclude=True, timeout=45)
         self.page.wait.url_change('travel-groups', exclude=True, timeout=45)
@@ -326,7 +328,7 @@ class TlsRegistrator:
             btn_selector = 'tag:button@@data-testid=btn-max-number-of-applicants'
             btn_selector = 'tag:button@@data-testid=btn-max-number-of-applicants'
             if not self.page.ele(btn_selector):
             if not self.page.ele(btn_selector):
                 raise BizLogicError(message=f"Can't find selector={btn_selector}")
                 raise BizLogicError(message=f"Can't find selector={btn_selector}")
-        self.page.ele(btn_selector).click()
+        self.mouse.human_click_ele(self.page.ele(btn_selector))
         time.sleep(6)
         time.sleep(6)
         
         
         visa_type = self.account_detail.get("visa_type")
         visa_type = self.account_detail.get("visa_type")
@@ -458,24 +460,24 @@ if __name__ == "__main__":
     }
     }
     
     
     CAPSOLVER_KEY = "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A"
     CAPSOLVER_KEY = "CAP-5441DD341DD3CC2FAEF0BE6FE493EE9A"
-    TLS_URL =  "https://visas-fr.tlscontact.com/visa/cn/cnCNG2fr/home"
+    TLS_URL =  "https://visas-fr.tlscontact.com/en-us/country/cn/vac/cnCNG2fr"
     ACCOUNT_DETAIL = {
     ACCOUNT_DETAIL = {
-        "email": "lisi103@gmail-app.com",
+        "email": "lisi105@gmail-app.com",
         "pwd": "Visafly@111",
         "pwd": "Visafly@111",
         "location": "Chengdu",
         "location": "Chengdu",
         "visa_type": "Short stay (<90 days) - Tourism",
         "visa_type": "Short stay (<90 days) - Tourism",
         "travel_purpose": "Tourism / Private visit",
         "travel_purpose": "Tourism / Private visit",
-        "application_form_id": "FRA1CA20260420996",
+        "application_form_id": "FRA1CA20260421997",
         "last_name": "Song",
         "last_name": "Song",
-        "first_name": "Xiao",
+        "first_name": "Xiaohui",
         "gender": "Male",
         "gender": "Male",
-        "birthday": "1998-12-18",
+        "birthday": "1998-12-20",
         "nationality": "China",
         "nationality": "China",
         "province_residence": "Sichuan",
         "province_residence": "Sichuan",
         "passport_type": "Ordinary passport",
         "passport_type": "Ordinary passport",
-        "passport_no": "EJ8628293",
+        "passport_no": "EJ8628394",
         "phone_country_code": "86",
         "phone_country_code": "86",
-        "phone_number": "17386068917",
+        "phone_number": "17386068828",
         "departure_origin_date": "2026-05-26",
         "departure_origin_date": "2026-05-26",
         "arrival_schengen_area_date": "2026-05-26",
         "arrival_schengen_area_date": "2026-05-26",
         "departure_schengen_area_date": "2026-05-28"
         "departure_schengen_area_date": "2026-05-28"
@@ -484,10 +486,10 @@ if __name__ == "__main__":
     try:
     try:
         bot = TlsRegistrator(TLS_URL, proxy_config=PROXY_CONFIG, capsolver_key=CAPSOLVER_KEY, account_detail=ACCOUNT_DETAIL)
         bot = TlsRegistrator(TLS_URL, proxy_config=PROXY_CONFIG, capsolver_key=CAPSOLVER_KEY, account_detail=ACCOUNT_DETAIL)
         bot.init_browser()
         bot.init_browser()
-        now_utc = datetime.utcnow()
-        sent_at = now_utc.strftime("%Y-%m-%d %H:%M:%S")
-        bot.register() 
-        bot.activate(sent_at=sent_at)
+        # now_utc = datetime.utcnow()
+        # sent_at = now_utc.strftime("%Y-%m-%d %H:%M:%S")
+        # bot.register() 
+        # bot.activate(sent_at=sent_at)
         bot.make_account_useful()
         bot.make_account_useful()
     except Exception as e:
     except Exception as e:
         print(f'Exception Info={e}')
         print(f'Exception Info={e}')

+ 7 - 22
utils/mouse.py

@@ -167,31 +167,16 @@ class HumanMouse:
 
 
         self._dispatch_move(x, y)
         self._dispatch_move(x, y)
         
         
-    def move_to(self, ele, duration: float = 0.8) -> None:
+    def human_click_ele(self, element):
         """
         """
-        拟人化移动鼠标到指定元素上
+        拟人化点击元素
         
         
-        :param ele: DrissionPage 的 ChromiumElement 对象
-        :param duration: 移动耗时 (秒)。如果传入 None,则使用内置的 Fitts 物理定律自动计算耗时。
+        :param element: DrissionPage 的元素对象
+        :param button: 鼠标按键
+        :param click_count: 点击次数 (默认 1 次)
         """
         """
-        # 1. 确保元素在视口可见(模拟人眼要看到才能移过去)
-        ele.scroll.to_see()
-        time.sleep(random.uniform(0.1, 0.3)) # 看到元素后人类通常有短暂的反应时间
-
-        # 2. 获取元素的中心点和尺寸 (DrissionPage 的 rect 属性非常方便)
-        center_x, center_y = ele.rect.midpoint
-        width, height = ele.rect.size
-
-        # 3. 拟人化处理:人类很难每次都精准移动到绝对中心
-        # 我们在元素中心点 60% 的长宽范围内随机取一个落点
-        offset_x = random.uniform(-width * 0.3, width * 0.3)
-        offset_y = random.uniform(-height * 0.3, height * 0.3)
-        
-        target_x = center_x + offset_x
-        target_y = center_y + offset_y
-
-        # 4. 执行移动
-        self._move_humanized(target_x, target_y, custom_duration=duration)
+        mid_x, mid_y = element.rect.midpoint
+        self.click(mid_x, mid_y, humanize=True)
 
 
     def click(
     def click(
         self,
         self,