Hujiarui 2 өдөр өмнө
parent
commit
2b11120c40

+ 3 - 3
booker_order.py

@@ -96,7 +96,7 @@ class OrderBookerGCO:
             
     def _maintain_loop(self):
         self._log("Maintain loop started.")
-        heartbeat_interval = 60
+        heartbeat_interval = 30 
         while not self.m_stop_event.is_set():
             for _ in range(heartbeat_interval):
                 if self.m_stop_event.is_set():
@@ -119,7 +119,7 @@ class OrderBookerGCO:
                         t.instance.keep_alive()
                         if t.instance.health_check(): 
                             healthy_tasks.append(t)
-                            next_delay = random.randint(60, 180) 
+                            next_delay = random.randint(25, 35) 
                             t.next_remote_ping = now + next_delay
                             self._log(f"🛡️ Task={t.task_ref} keep-alive success. Next ping in {next_delay}s.")
                         else:
@@ -402,7 +402,7 @@ class OrderBookerGCO:
                             acceptable_routing_keys=acceptable_keys, 
                             source_queue=target_routing_key,
                             book_allowed=True,
-                            next_remote_ping=time.time() + random.randint(60, 180) 
+                            next_remote_ping=time.time() + random.randint(25, 35) 
                         )
                     )
                     queue_fail_key = f"vs:queue:failures:{target_routing_key}"

+ 54 - 20
plugins/tls_plugin.py

@@ -66,6 +66,7 @@ class TlsPlugin(IVSPlg):
         if not os.path.exists(self.root_workspace):
             os.makedirs(self.root_workspace)
             
+        self.last_refresh_time = time.time()
         self.tunnel = None
         self.session_create_time: float = 0
 
@@ -86,17 +87,40 @@ class TlsPlugin(IVSPlg):
         self.free_config = config.free_config or {}
         
     def keep_alive(self):
-        try:
-            self.page.refresh()
-            self.page.wait.load_start(timeout=2)
-            self.page.wait.doc_loaded()
-            time.sleep(random.uniform(1, 3))
-            self._check_page_is_session_expired_or_invalid('Book your appointment', html = self.page.html)
-            self.simulate_random_human_clicks()
-        except SessionExpiredOrInvalidError as e:
-            self.is_healthy = False
-        except Exception as e:
-            self._log(f"Unexpected error in keep_alive: {e}")
+        """
+        统一保活机制:
+        - 距离上次刷新超过 10 分钟:执行完整页面刷新并检查 Session。
+        - 否则:随机发送 Fetch 小请求保活。
+        """
+        if time.time() - self.last_refresh_time >= 60*10:
+            try:
+                self._log("Cut all connections...")
+                self.tunnel.cut_all_connections()
+                self._log("refresh page...")
+                self.page.refresh()
+                self.page.wait.load_start(timeout=2)
+                self.page.wait.doc_loaded()
+                time.sleep(random.uniform(1, 3))
+                self._check_page_is_session_expired_or_invalid('Book your appointment', html=self.page.html)
+                self.last_refresh_time = time.time() 
+                self._log("refresh page finished")
+            except Exception as e:
+                self._log(f"refresh page error: {str(e)}")
+                self.is_healthy = False 
+        else:
+            choice = random.choice(['home', 'travel_groups'])
+            headers = {}
+            if choice == 'home':
+                url = "https://visas-fr.tlscontact.com/"
+            elif choice == 'travel_groups':
+                url = "https://visas-fr.tlscontact.com/en-us/travel-groups"
+                headers = {"cache-control": "max-age=0"}
+            try:
+                self._log(f"send keep alive fetch request ({choice})")
+                self._perform_request("GET", url, headers=headers)
+            except Exception as e:
+                self._log(f"send keep alive fetch error: {str(e)}")
+                self.is_healthy = False
             
     def simulate_random_human_clicks(self, min_x=300, max_x=800, min_y=400, max_y=600, min_clicks=1, max_clicks=2):
         """
@@ -286,14 +310,17 @@ class TlsPlugin(IVSPlg):
             self._log(f"Navigating: {tls_url}")
             self.page.get(tls_url)
             time.sleep(5)
-            
+            if 'Attention Required! | Cloudflare' in self.page.title and 'Sorry, you have been blocked' in self.page.html:
+                self._log(f'Block by cloudflare, try refresh...')
+                self.page.refresh()
+                self.page.wait.load_start(timeout=2)
+                self.page.wait.doc_loaded()
             cf_bypasser = CloudflareBypasser(self.page, log=self.config.debug)
             if not cf_bypasser.bypass(max_retry=6):
                 raise BizLogicError("Cloudflare bypass timeout")
             time.sleep(3)
             cf_bypasser.handle_waiting_room()
             
-            # --- 初始化人类行为模拟工具 ---
             self._log("Init humanize tools...")
             self.mouse = HumanMouse(self.page, debug=self.config.debug)
             self.keyboard = HumanKeyboard(self.page)
@@ -308,7 +335,9 @@ class TlsPlugin(IVSPlg):
             has_submitted_login = False
             
             for step in range(max_steps):
-                self.page.wait.load_start()
+                self.page.wait.doc_loaded()
+                time.sleep(0.5)
+                
                 current_url = self.page.url
                 self._log(f"--- [Router Step {step+1}] Current URL: {current_url} ---")
                 
@@ -344,8 +373,10 @@ class TlsPlugin(IVSPlg):
                         self._log("State: Login Portal. Clicking login link...")
                         login_link = self.page.ele("tag:a@@href:login")
                         self.mouse.human_click_ele(login_link)
-                        time.sleep(3)
+                        
+                        self.page.wait.load_start(timeout=3)
                         continue
+
                     if self.page.ele("tag:svg@@data-testid=user-button", timeout=1):
                         self._log("State: Already login, logout now...")
                         user_btn = self.page.ele("tag:svg@@data-testid=user-button")
@@ -353,9 +384,10 @@ class TlsPlugin(IVSPlg):
                         time.sleep(1.5)
                         logout_btn = self.page.ele("#logout")
                         self.mouse.human_click_ele(logout_btn)
-                        time.sleep(1.5)
+                        
+                        self.page.wait.load_start(timeout=3)
                         self.page.get(tls_url)
-                        time.sleep(3)
+                        self.page.wait.load_start(timeout=3)
                         continue
                 
                 # 状态 4:真正的登录表单页
@@ -415,7 +447,8 @@ class TlsPlugin(IVSPlg):
                     login_btn = self.page.ele('tag:button@@text():Login')
                     self.mouse.human_click_ele(login_btn)
                     has_submitted_login = True
-                    time.sleep(3)
+                    
+                    self.page.wait.load_start(timeout=5)
                     continue
                 
                 # 状态 5:Travel Groups 页面
@@ -439,7 +472,7 @@ class TlsPlugin(IVSPlg):
                         if select_btn:
                             time.sleep(random.uniform(0.5, 1.2))
                             self.mouse.human_click_ele(select_btn)
-                            time.sleep(3)
+                            self.page.wait.load_start(timeout=3)
                             continue
                         else:
                             self._log("[WARN] Select button found but not visible.")
@@ -450,7 +483,8 @@ class TlsPlugin(IVSPlg):
                 if self.page.ele('#book-appointment-btn', timeout=1):
                     self._log("State: Intermediate Dashboard. Clicking Book Appointment button...")
                     self.mouse.human_click_ele(self.page.ele('#book-appointment-btn'))
-                    time.sleep(3)
+                    
+                    self.page.wait.load_start(timeout=3)
                     continue
                 
                 # 状态 7:登录失败校验 或 未知加载状态

+ 24 - 2
toolkit/mihomo_tunnel.py

@@ -5,6 +5,8 @@ import subprocess
 import yaml
 import tempfile
 import shutil
+import urllib.request
+
 
 class MihomoTunnel:
     """
@@ -20,9 +22,9 @@ class MihomoTunnel:
         
         self.process = None
         self.local_port = 0
+        self.api_port = 0
         self.log_file_obj = None
         
-        # Create an isolated temporary directory for this instance
         self.temp_dir = tempfile.mkdtemp(prefix="mihomo_tunnel_")
         self.config_path = os.path.join(self.temp_dir, "config.yaml")
         self.log_path = os.path.join(self.temp_dir, "run.log")
@@ -34,12 +36,14 @@ class MihomoTunnel:
 
     def _generate_config(self):
         self.local_port = self._get_free_port()
+        self.api_port = self._get_free_port()
 
         config = {
             "allow-lan": False,
             "bind-address": "127.0.0.1",
             "log-level": "warning" if self.enable_log else "silent",
             "mixed-port": self.local_port,
+            "external-controller": f"127.0.0.1:{self.api_port}",
             "mode": "rule",
             "ipv6": False,
             "dns": {
@@ -105,6 +109,25 @@ class MihomoTunnel:
             self.stop()
             raise e
 
+    def cut_all_connections(self):
+        """
+        [新增方法]
+        通过 Mihomo API 强制切断当前代理池的所有活跃连接。
+        常用于检测到 IP 被封锁或切换代理线路时,立即打断正在卡死/挂起的旧请求。
+        """
+        if not self.process or self.process.poll() is not None:
+            return False
+            
+        try:
+            url = f"http://127.0.0.1:{self.api_port}/connections"
+            req = urllib.request.Request(url, method="DELETE")
+            with urllib.request.urlopen(req, timeout=3) as response:
+                return response.status in (200, 204)
+        except Exception as e:
+            if self.enable_log:
+                print(f"[MihomoTunnel] Failed to cut connections: {e}")
+            return False
+
     def stop(self):
         if self.process and self.process.poll() is None:
             self.process.terminate()
@@ -126,7 +149,6 @@ class MihomoTunnel:
     def __del__(self):
         self.stop()
 
-
 # ================= TEST SCRIPT =================
 if __name__ == "__main__":
     MIHOMO_EXE = "E:/coordinator/mihomo-windows-amd64-alpha-98aa7e6/mihomo-windows-amd64.exe" if os.name == 'nt' else "./mihomo"