瀏覽代碼

feat: update

jerry 3 月之前
父節點
當前提交
c6bd7dd623
共有 8 個文件被更改,包括 516 次插入185 次删除
  1. 二進制
      cf_failed.png
  2. 0 30
      chrome_proxy_auth_plugin/background.js
  3. 0 20
      chrome_proxy_auth_plugin/manifest.json
  4. 3 3
      config/groups.json
  5. 92 2
      config/proxies.json
  6. 128 18
      plugins/ita_plugin.py
  7. 106 32
      plugins/tls_plugin2.py
  8. 187 80
      plugins/vfs_plugin2.py

二進制
cf_failed.png


+ 0 - 30
chrome_proxy_auth_plugin/background.js

@@ -1,30 +0,0 @@
-
-    var config = {
-        mode: "fixed_servers",
-        rules: {
-            singleProxy: {
-                scheme: "http",
-                host: "91.193.255.210",
-                port: parseInt(12323)
-            },
-            bypassList: ["localhost"]
-        }
-    };
-
-    chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});
-
-    function callbackFn(details) {
-        return {
-            authCredentials: {
-                username: "14ae212b29a2a",
-                password: "d160bc0854"
-            }
-        };
-    }
-
-    chrome.webRequest.onAuthRequired.addListener(
-        callbackFn,
-        {urls: ["<all_urls>"]},
-        ['blocking']
-    );
-    

+ 0 - 20
chrome_proxy_auth_plugin/manifest.json

@@ -1,20 +0,0 @@
-
-    {
-        "version": "1.0.0",
-        "manifest_version": 2,
-        "name": "Chrome Proxy Auth Extension",
-        "permissions": [
-            "proxy",
-            "tabs",
-            "unlimitedStorage",
-            "storage",
-            "<all_urls>",
-            "webRequest",
-            "webRequestBlocking"
-        ],
-        "background": {
-            "scripts": ["background.js"]
-        },
-        "minimum_chrome_version": "22.0.0"
-    }
-    

+ 3 - 3
config/groups.json

@@ -235,11 +235,11 @@
     {
         "identifier": "VFS_GB_NL",
         "debug": false,
-        "enable": false,
+        "enable": true,
         "need_account": true,
         "local_account_pool": "gb_nl",
         "need_proxy": true,
-        "proxy_pool": "iproyal",
+        "proxy_pool": "proxy_cheap",
         "target_instances": 1,
         "account_login_interval": 30,
         "order_account_routing": "",
@@ -702,7 +702,7 @@
     {
         "identifier": "TLS_GB_FR",
         "debug": false,
-        "enable": true,
+        "enable": false,
         "need_account": true,
         "local_account_pool": "gb_fr",
         "need_proxy": true,

+ 92 - 2
config/proxies.json

@@ -259,7 +259,7 @@
     ],
     "proxy_cheap": [
         {
-            "id": 100029,
+            "id": 100001,
             "ip": "95.135.130.175",
             "lock_until": 0,
             "password": "hmuROCk1FDebCnL",
@@ -268,13 +268,103 @@
             "username": "GB6o2vBrXFjz4ya"
         },
         {
-            "id": 100030,
+            "id": 100002,
             "ip": "95.135.130.29",
             "lock_until": 0,
             "password": "WmqFTSvRvtxChIT",
             "port": 43740,
             "scheme": "http",
             "username": "JUcjydi0HKZzWC6"
+        },
+        {
+            "id": 100003,
+            "ip": "95.135.130.105",
+            "lock_until": 0,
+            "password": "a9udkCOGYZKGkLS",
+            "port": 47342,
+            "scheme": "http",
+            "username": "1mtj2c6xoLfrOLm"
+        },
+        {
+            "id": 100004,
+            "ip": "95.135.130.157",
+            "lock_until": 0,
+            "password": "q1UNK1gmiQdxJ1g",
+            "port": 42290,
+            "scheme": "http",
+            "username": "alxuf86deeI898d"
+        },
+        {
+            "id": 100005,
+            "ip": "95.135.130.167",
+            "lock_until": 0,
+            "password": "bmKmauMV5CuOCrh",
+            "port": 48333,
+            "scheme": "http",
+            "username": "gPaIdSyKsp2TnQ1"
+        },
+        {
+            "id": 100006,
+            "ip": "95.135.130.181",
+            "lock_until": 0,
+            "password": "0HU9RGLRTNNwecf",
+            "port": 46253,
+            "scheme": "http",
+            "username": "XpSiGAiz3zwAyi1"
+        },
+        {
+            "id": 100007,
+            "ip": "95.135.130.192",
+            "lock_until": 0,
+            "password": "anYmNO4luxcm22m",
+            "port": 41255,
+            "scheme": "http",
+            "username": "xRGDPswifGmmbog"
+        },
+        {
+            "id": 100008,
+            "ip": "95.135.130.203",
+            "lock_until": 0,
+            "password": "qXEZWLa74q8Awdx",
+            "port": 49012,
+            "scheme": "http",
+            "username": "jHATN4mVM6kcltO"
+        },
+        {
+            "id": 100009,
+            "ip": "95.135.130.206",
+            "lock_until": 0,
+            "password": "MnL8pmNAdpLo0h7",
+            "port": 47504,
+            "scheme": "http",
+            "username": "aLoTyl9YbSvdxbD"
+        },
+        {
+            "id": 100010,
+            "ip": "95.135.130.33",
+            "lock_until": 0,
+            "password": "slxIVWHqzZpkSPY",
+            "port": 44720,
+            "scheme": "http",
+            "username": "EnnPVteJalQvk7E"
+        },
+        {
+            "id": 100011,
+            "ip": "95.135.130.53",
+            "lock_until": 0,
+            "password": "5fS8EMqdto9JnO9",
+            "port": 44764,
+            "scheme": "http",
+            "username": "QeLVYhf0Pbz5BEe"
+        },
+        {
+            "id": 100012,
+            "ip": "95.135.130.66",
+            "lock_until": 0,
+            "password": "4HzLag9JYiPTZ5J",
+            "port": 46470,
+            "scheme": "http",
+            "username": "c1zHamB7LAOeNdb"
         }
     ],
     "local": [

+ 128 - 18
plugins/ita_plugin.py

@@ -1,6 +1,8 @@
 import time
 import json
 import random
+import uuid
+import shutil
 import re
 import os
 import base64
@@ -18,23 +20,73 @@ from toolkit.vs_cloud_api import VSCloudApi
 # ==========================================
 # 1. 辅助函数:代理插件 & 响应封装
 # ==========================================
-def create_proxy_auth_extension(ip, port, username, password, plugin_path="./chrome_proxy_auth_plugin"):
-    if not os.path.exists(plugin_path): os.makedirs(plugin_path)
+def create_proxy_auth_extension(ip, port, username, password, plugin_path):
+    """
+    创建一个 Chrome 插件来自动处理代理认证
+    """
+    if not os.path.exists(plugin_path):
+        os.makedirs(plugin_path)
+
+    # 1. manifest.json
     manifest_json = """
     {
-        "version": "1.0.0", "manifest_version": 2, "name": "Chrome Proxy Auth",
-        "permissions": ["proxy", "tabs", "unlimitedStorage", "storage", "<all_urls>", "webRequest", "webRequestBlocking"],
-        "background": {"scripts": ["background.js"]}, "minimum_chrome_version": "22.0.0"
-    }"""
+        "version": "1.0.0",
+        "manifest_version": 2,
+        "name": "Chrome Proxy Auth Extension",
+        "permissions": [
+            "proxy",
+            "tabs",
+            "unlimitedStorage",
+            "storage",
+            "<all_urls>",
+            "webRequest",
+            "webRequestBlocking"
+        ],
+        "background": {
+            "scripts": ["background.js"]
+        },
+        "minimum_chrome_version": "22.0.0"
+    }
+    """
+
+    # 2. background.js
     background_js = f"""
-    var config = {{mode: "fixed_servers", rules: {{singleProxy: {{scheme: "http", host: "{ip}", port: parseInt({port})}}, bypassList: ["localhost"]}}}};
+    var config = {{
+        mode: "fixed_servers",
+        rules: {{
+            singleProxy: {{
+                scheme: "http",
+                host: "{ip}",
+                port: parseInt({port})
+            }},
+            bypassList: ["localhost"]
+        }}
+    }};
+
     chrome.proxy.settings.set({{value: config, scope: "regular"}}, function() {{}});
-    chrome.webRequest.onAuthRequired.addListener(function(details) {{
-        return {{authCredentials: {{username: "{username}", password: "{password}"}}}};
-    }}, {{urls: ["<all_urls>"]}}, ['blocking']);
+
+    function callbackFn(details) {{
+        return {{
+            authCredentials: {{
+                username: "{username}",
+                password: "{password}"
+            }}
+        }};
+    }}
+
+    chrome.webRequest.onAuthRequired.addListener(
+        callbackFn,
+        {{urls: ["<all_urls>"]}},
+        ['blocking']
+    );
     """
-    with open(os.path.join(plugin_path, "manifest.json"), "w") as f: f.write(manifest_json)
-    with open(os.path.join(plugin_path, "background.js"), "w") as f: f.write(background_js)
+
+    with open(os.path.join(plugin_path, "manifest.json"), "w") as f:
+        f.write(manifest_json)
+    
+    with open(os.path.join(plugin_path, "background.js"), "w") as f:
+        f.write(background_js)
+
     return os.path.abspath(plugin_path)
 
 class BrowserResponse:
@@ -64,11 +116,25 @@ class ItaPlugin(IVSPlg):
         self.is_healthy = True
         self.logger = None
         self.page: Optional[ChromiumPage] = None
-        self.session_create_time: float = 0
         
         # Prenotami 特有配置
         self._service_id = 0 
         self._host = 'https://prenotami.esteri.it'
+        
+                
+        # --- [核心修改] 并发隔离与资源管理 ---
+        # 生成唯一实例 ID
+        self.instance_id = uuid.uuid4().hex[:8]
+        self.root_workspace = os.path.abspath(os.path.join("temp_browser_data", f"{self.group_id}_{self.instance_id}"))
+        # 定义子目录:代理插件目录 & 浏览器用户数据目录
+        self.proxy_ext_path = os.path.join(self.root_workspace, "proxy_ext")
+        self.user_data_path = os.path.join(self.root_workspace, "user_data")
+        
+        # 确保根目录存在 (子目录由具体逻辑创建)
+        if not os.path.exists(self.root_workspace):
+            os.makedirs(self.root_workspace)
+        
+        self.session_create_time: float = 0
 
     def get_group_id(self) -> str:
         return self.group_id
@@ -116,11 +182,21 @@ class ItaPlugin(IVSPlg):
         co = ChromiumOptions()
         co.auto_port()
         
+        # --- [关键配置] 设置独立的用户数据目录 ---
+        # 这样每个实例的 Cache, Cookies, LocalStorage 都是完全隔离的
+        # 同时也防止了多进程争抢同一个 Default 文件夹导致的崩溃
+        co.set_user_data_path(self.user_data_path)
+        
         if self.config.proxy and self.config.proxy.ip:
             p = self.config.proxy
             if p.username and p.password:
                 self._log(f"Configuring authenticated proxy: {p.ip}:{p.port}")
-                co.add_extension(create_proxy_auth_extension(p.ip, p.port, p.username, p.password))
+                # [关键调用] 生成该实例独享的插件
+                plugin_path = create_proxy_auth_extension(
+                    p.ip, p.port, p.username, p.password, 
+                    self.proxy_ext_path # 传入唯一路径
+                )
+                co.add_extension(plugin_path)
             else:
                 co.set_proxy(f"{p.scheme}://{p.ip}:{p.port}")
 
@@ -170,9 +246,7 @@ class ItaPlugin(IVSPlg):
 
         except Exception as e:
             self._log(f"Create Session Failed: {e}")
-            if self.page:
-                self.page.quit()
-                self.page = None
+            self.cleanup()
             raise e
 
     def _handle_login_captcha(self):
@@ -558,4 +632,40 @@ class ItaPlugin(IVSPlg):
                         'remain': remain
                     })
         except: pass
-        return slots
+        return slots
+    
+    # --- 资源清理核心方法 ---
+    def cleanup(self):
+        """
+        销毁浏览器并彻底删除临时文件
+        """
+        # 1. 关闭浏览器
+        if self.page:
+            try:
+                self.page.quit() # 这会关闭 Chrome 进程
+            except Exception:
+                pass # 忽略已关闭的错误
+            self.page = None
+        
+        # 2. 删除文件
+        # 注意:Chrome 关闭后可能需要几百毫秒释放文件锁,稍微等待
+        if os.path.exists(self.root_workspace):
+            for _ in range(3):
+                try:
+                    time.sleep(0.2)
+                    shutil.rmtree(self.root_workspace, ignore_errors=True)
+                    break
+                except Exception as e:
+                    # 如果删除失败(通常是Windows文件占用),重试
+                    if self.logger: self.logger(f"Cleanup retry: {e}")
+                    time.sleep(0.5)
+            
+            # 如果依然存在,打印警告(虽然 ignore_errors=True 会掩盖报错,但可以 check exists)
+            if os.path.exists(self.root_workspace) and self.logger:
+                 self.logger(f"[WARN] Failed to fully remove workspace: {self.root_workspace}")
+                 
+    def __del__(self):
+        """
+        析构函数:当对象被垃圾回收时自动调用
+        """
+        self.cleanup()

+ 106 - 32
plugins/tls_plugin2.py

@@ -3,6 +3,8 @@ import json
 import random
 import re
 import os
+import uuid
+import shutil
 from datetime import datetime
 from typing import List, Dict, Optional, Any, Callable
 from urllib.parse import urljoin, urlparse, urlencode
@@ -15,41 +17,73 @@ from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, AvailabilityStatu
 from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
 from toolkit.vs_cloud_api import VSCloudApi
 
-# --- 辅助函数:创建代理插件 ---
-def create_proxy_auth_extension(ip, port, username, password, plugin_path="./chrome_proxy_auth_plugin"):
+def create_proxy_auth_extension(ip, port, username, password, plugin_path):
+    """
+    创建一个 Chrome 插件来自动处理代理认证
+    """
     if not os.path.exists(plugin_path):
         os.makedirs(plugin_path)
 
+    # 1. manifest.json
     manifest_json = """
     {
         "version": "1.0.0",
         "manifest_version": 2,
         "name": "Chrome Proxy Auth Extension",
-        "permissions": ["proxy", "tabs", "unlimitedStorage", "storage", "<all_urls>", "webRequest", "webRequestBlocking"],
-        "background": {"scripts": ["background.js"]},
+        "permissions": [
+            "proxy",
+            "tabs",
+            "unlimitedStorage",
+            "storage",
+            "<all_urls>",
+            "webRequest",
+            "webRequestBlocking"
+        ],
+        "background": {
+            "scripts": ["background.js"]
+        },
         "minimum_chrome_version": "22.0.0"
     }
     """
+
+    # 2. background.js
     background_js = f"""
     var config = {{
         mode: "fixed_servers",
         rules: {{
-            singleProxy: {{scheme: "http", host: "{ip}", port: parseInt({port})}},
+            singleProxy: {{
+                scheme: "http",
+                host: "{ip}",
+                port: parseInt({port})
+            }},
             bypassList: ["localhost"]
         }}
     }};
+
     chrome.proxy.settings.set({{value: config, scope: "regular"}}, function() {{}});
+
     function callbackFn(details) {{
-        return {{authCredentials: {{username: "{username}", password: "{password}"}}}};
+        return {{
+            authCredentials: {{
+                username: "{username}",
+                password: "{password}"
+            }}
+        }};
     }}
+
     chrome.webRequest.onAuthRequired.addListener(
-        callbackFn, {{urls: ["<all_urls>"]}}, ['blocking']
+        callbackFn,
+        {{urls: ["<all_urls>"]}},
+        ['blocking']
     );
     """
+
     with open(os.path.join(plugin_path, "manifest.json"), "w") as f:
         f.write(manifest_json)
+    
     with open(os.path.join(plugin_path, "background.js"), "w") as f:
         f.write(background_js)
+
     return os.path.abspath(plugin_path)
 
 
@@ -89,8 +123,20 @@ class TlsPlugin2(IVSPlg):
         self.page: Optional[ChromiumPage] = None
         
         self.travel_group: Optional[Dict] = None
+        
+        # --- [核心修改] 并发隔离与资源管理 ---
+        # 生成唯一实例 ID
+        self.instance_id = uuid.uuid4().hex[:8]
+        self.root_workspace = os.path.abspath(os.path.join("temp_browser_data", f"{self.group_id}_{self.instance_id}"))
+        # 定义子目录:代理插件目录 & 浏览器用户数据目录
+        self.proxy_ext_path = os.path.join(self.root_workspace, "proxy_ext")
+        self.user_data_path = os.path.join(self.root_workspace, "user_data")
+        
+        # 确保根目录存在 (子目录由具体逻辑创建)
+        if not os.path.exists(self.root_workspace):
+            os.makedirs(self.root_workspace)
+        
         self.session_create_time: float = 0
-        self.real_ip: str = "0.0.0.0"
 
     def get_group_id(self) -> str:
         return self.group_id
@@ -131,15 +177,25 @@ class TlsPlugin2(IVSPlg):
         """
         全浏览器会话创建:过盾 -> JS注入登录 -> 原生跳转
         """
-        self._log("Initializing Browser Session (Full Browser Mode)...")
+        self._log(f"Initializing Session (ID: {self.instance_id})...")
         co = ChromiumOptions()
         co.auto_port()
         
+        # --- [关键配置] 设置独立的用户数据目录 ---
+        # 这样每个实例的 Cache, Cookies, LocalStorage 都是完全隔离的
+        # 同时也防止了多进程争抢同一个 Default 文件夹导致的崩溃
+        co.set_user_data_path(self.user_data_path)
+        
         if self.config.proxy and self.config.proxy.ip:
             p = self.config.proxy
             if p.username and p.password:
                 self._log(f"Proxy: {p.ip}:{p.port} (Auth)")
-                co.add_extension(create_proxy_auth_extension(p.ip, p.port, p.username, p.password))
+                # [关键调用] 生成该实例独享的插件
+                plugin_path = create_proxy_auth_extension(
+                    p.ip, p.port, p.username, p.password, 
+                    self.proxy_ext_path # 传入唯一路径
+                )
+                co.add_extension(plugin_path)
             else:
                 co.set_proxy(f"{p.scheme}://{p.ip}:{p.port}")
 
@@ -233,15 +289,15 @@ class TlsPlugin2(IVSPlg):
                     self.travel_group = g
                     break
             
-            if not self.travel_group: raise NotFoundError(f"Group not found for {target_city}")
+            if not self.travel_group:
+                raise NotFoundError(f"Group not found for {target_city}")
             
             self.session_create_time = time.time()
-            self.real_ip = self._get_realnetwork_ip()
             self._log(f"Session Ready. Group: {self.travel_group['group_number']}")
 
         except Exception as e:
             self._log(f"Session Create Error: {e}")
-            if self.page: self.page.quit(); self.page = None
+            self.cleanup()
             raise e
 
     def query(self) -> VSQueryResult:
@@ -550,24 +606,6 @@ class TlsPlugin2(IVSPlg):
             self._log(f"Error during firewall refresh: {e}")
             return False
 
-    def _get_realnetwork_ip(self):
-        """新标签页获取 IP,规避 CORS"""
-        try:
-            tab = self.page.new_tab("https://api.ipify.org/?format=json")
-            if tab.ele('tag:pre'):
-                json_text = tab.ele('tag:pre').text
-            else:
-                json_text = tab.ele('tag:body').text
-            ip = json.loads(json_text)['ip']
-            tab.close()
-            return ip
-        except Exception:
-            # 尝试清理
-            try:
-                if self.page.tabs_count > 1: self.page.close_tabs(self.page.tabs[-1])
-            except: pass
-            return "0.0.0.0"
-
     def _solve_recaptcha(self, params) -> str:
         """调用 VSCloudApi 解决 ReCaptcha"""
         key = params.get("apiToken")
@@ -672,4 +710,40 @@ class TlsPlugin2(IVSPlg):
             if s_date <= curr_date <= e_date:
                 valid_dates.append(date_str)
         random.shuffle(valid_dates)
-        return valid_dates
+        return valid_dates
+    
+    # --- 资源清理核心方法 ---
+    def cleanup(self):
+        """
+        销毁浏览器并彻底删除临时文件
+        """
+        # 1. 关闭浏览器
+        if self.page:
+            try:
+                self.page.quit() # 这会关闭 Chrome 进程
+            except Exception:
+                pass # 忽略已关闭的错误
+            self.page = None
+        
+        # 2. 删除文件
+        # 注意:Chrome 关闭后可能需要几百毫秒释放文件锁,稍微等待
+        if os.path.exists(self.root_workspace):
+            for _ in range(3):
+                try:
+                    time.sleep(0.2)
+                    shutil.rmtree(self.root_workspace, ignore_errors=True)
+                    break
+                except Exception as e:
+                    # 如果删除失败(通常是Windows文件占用),重试
+                    if self.logger: self.logger(f"Cleanup retry: {e}")
+                    time.sleep(0.5)
+            
+            # 如果依然存在,打印警告(虽然 ignore_errors=True 会掩盖报错,但可以 check exists)
+            if os.path.exists(self.root_workspace) and self.logger:
+                 self.logger(f"[WARN] Failed to fully remove workspace: {self.root_workspace}")
+                 
+    def __del__(self):
+        """
+        析构函数:当对象被垃圾回收时自动调用
+        """
+        self.cleanup()

+ 187 - 80
plugins/vfs_plugin2.py

@@ -5,6 +5,8 @@ import time
 import json
 import random
 import base64
+import uuid
+import shutil
 import re
 import urllib.parse
 from datetime import datetime
@@ -21,7 +23,9 @@ from cryptography.hazmat.backends import default_backend
 
 from vs_plg import IVSPlg 
 from vs_types import VSPlgConfig, VSQueryResult, VSBookResult, DateAvailability, AvailabilityStatus, NotFoundError, PermissionDeniedError, RateLimiteddError, SessionExpiredOrInvalidError, BizLogicError 
-from toolkit.vs_cloud_api import VSCloudApi 
+from toolkit.vs_cloud_api import VSCloudApi
+from utils.cloudflare_bypass_for_scraping import CloudflareBypasser
+
 
 # ----------------- 静态常量与辅助数据 -----------------
 
@@ -35,11 +39,36 @@ t92towriKoH75BhiazY0mghm4LjmAWrV0u/GNpV3tk9bxbtHEXGaFmxCJqjg+7x6
 GQIDAQAB
 -----END PUBLIC KEY-----"""
 
-# (Country Map 省略以节省篇幅,请保持原样)
 COUNTRY_MAP = {
     "afghanistan": "AFG", "albania": "ALB", "algeria": "DZA", "andorra": "AND",  "angola": "AGO",
-    "china": "CHN", "united kingdom": "GBR", "netherlands": "NLD", 
-    # ... 请保留你原来的完整映射 ...
+    "antigua and barbuda": "ATG", "argentina": "ARG", "armenia": "ARM", "australia": "AUS", "austria": "AUT",
+    "azerbaijan": "AZE", "bahamas": "BHS", "bahrain": "BHR", "bangladesh": "BGD", "barbados": "BRB", "belarus": "BLR",
+    "belgium": "BEL", "belize": "BLZ", "benin": "BEN", "bhutan": "BTN", "bolivia": "BOL", "bosnia and herzegovina": "BIH",
+    "botswana": "BWA", "brazil": "BRA", "brunei": "BRN", "bulgaria": "BGR", "burkina faso": "BFA", "burundi": "BDI",
+    "cabo verde": "CPV", "cambodia": "KHM", "cameroon": "CMR", "canada": "CAN", "central african republic": "CAF",
+    "chad": "TCD", "chile": "CHL", "china": "CHN", "colombia": "COL", "comoros": "COM", "congo (brazzaville)": "COG",
+    "congo (kinshasa)": "COD", "costa rica": "CRI", "croatia": "HRV", "cuba": "CUB", "cyprus": "CYP", "czech republic": "CZE",
+    "denmark": "DNK", "djibouti": "DJI", "dominica": "DMA", "dominican republic": "DOM", "ecuador": "ECU", "egypt": "EGY",
+    "el salvador": "SLV", "equatorial guinea": "GNQ", "eritrea": "ERI", "estonia": "EST", "eswatini": "SWZ", "ethiopia": "ETH",
+    "fiji": "FJI", "finland": "FIN", "france": "FRA", "gabon": "GAB", "gambia": "GMB", "georgia": "GEO", "germany": "DEU",
+    "ghana": "GHA", "greece": "GRC", "grenada": "GRD", "guatemala": "GTM", "guinea": "GIN", "guinea-bissau": "GNB", "guyana": "GUY",
+    "haiti": "HTI", "honduras": "HND", "hungary": "HUN", "iceland": "ISL", "india": "IND", "indonesia": "IDN", "iran": "IRN",
+    "iraq": "IRQ", "ireland": "IRL", "israel": "ISR", "italy": "ITA", "jamaica": "JAM", "japan": "JPN", "jordan": "JOR",
+    "kazakhstan": "KAZ", "kenya": "KEN", "kiribati": "KIR", "korea, north": "PRK", "korea, south": "KOR", "kuwait": "KWT",
+    "kyrgyzstan": "KGZ", "laos": "LAO", "latvia": "LVA", "lebanon": "LBN", "lesotho": "LSO", "liberia": "LBR", "libya": "LBY",
+    "liechtenstein": "LIE", "lithuania": "LTU", "luxembourg": "LUX", "madagascar": "MDG", "malawi": "MWI", "malaysia": "MYS",
+    "maldives": "MDV", "mali": "MLI", "malta": "MLT", "marshall islands": "MHL", "mauritania": "MRT", "mauritius": "MUS",
+    "mexico": "MEX", "micronesia": "FSM", "moldova": "MDA", "monaco": "MCO", "mongolia": "MNG", "montenegro": "MNE", "morocco": "MAR",
+    "mozambique": "MOZ", "myanmar": "MMR", "namibia": "NAM", "nauru": "NRU", "nepal": "NPL", "netherlands": "NLD", "new zealand": "NZL",
+    "nicaragua": "NIC", "niger": "NER", "nigeria": "NGA", "north macedonia": "MKD", "norway": "NOR", "oman": "OMN", "pakistan": "PAK",
+    "palau": "PLW", "panama": "PAN", "papua new guinea": "PNG", "paraguay": "PRY", "peru": "PER", "philippines": "PHL", "poland": "POL",
+    "portugal": "PRT", "qatar": "QAT", "romania": "ROU", "russia": "RUS", "rwanda": "RWA", "saudi arabia": "SAU", "senegal": "SEN",
+    "serbia": "SRB", "seychelles": "SYC", "sierra leone": "SLE", "singapore": "SGP", "slovakia": "SVK", "slovenia": "SVN",
+    "solomon islands": "SLB", "somalia": "SOM", "south africa": "ZAF", "spain": "ESP", "sri lanka": "LKA", "sudan": "SDN",
+    "suriname": "SUR", "sweden": "SWE", "switzerland": "CHE", "syria": "SYR", "tajikistan": "TJK", "tanzania": "TZA", "thailand": "THA",
+    "timor-leste": "TLS", "togo": "TGO", "tonga": "TON", "tunisia": "TUN", "turkey": "TUR", "turkmenistan": "TKM", "uganda": "UGA",
+    "ukraine": "UKR", "united arab emirates": "ARE", "united kingdom": "GBR", "united states": "USA", "uruguay": "URY", "uzbekistan": "UZB",
+    "vanuatu": "VUT", "venezuela": "VEN", "vietnam": "VNM", "yemen": "YEM", "zambia": "ZMB", "zimbabwe": "ZWE"
 }
 
 def get_country_iso3(name: str) -> str:
@@ -52,7 +81,7 @@ def to_yyyymmdd(data_str: str, date_str_format: str, target_format: str="%Y-%m-%
     except:
         return data_str
 
-def create_proxy_auth_extension(ip, port, username, password, plugin_path="./chrome_proxy_auth_plugin"):
+def create_proxy_auth_extension(ip, port, username, password, plugin_path):
     """
     创建一个 Chrome 插件来自动处理代理认证
     """
@@ -167,6 +196,19 @@ class VfsPlugin2(IVSPlg):
             VFS_PUBLIC_KEY_PEM.encode(),
             backend=default_backend()
         )
+        
+        # --- [核心修改] 并发隔离与资源管理 ---
+        # 生成唯一实例 ID
+        self.instance_id = uuid.uuid4().hex[:8]
+        self.root_workspace = os.path.abspath(os.path.join("temp_browser_data", f"{self.group_id}_{self.instance_id}"))
+        # 定义子目录:代理插件目录 & 浏览器用户数据目录
+        self.proxy_ext_path = os.path.join(self.root_workspace, "proxy_ext")
+        self.user_data_path = os.path.join(self.root_workspace, "user_data")
+        
+        # 确保根目录存在 (子目录由具体逻辑创建)
+        if not os.path.exists(self.root_workspace):
+            os.makedirs(self.root_workspace)
+            
         self.session_create_time: float = 0
 
     def get_group_id(self) -> str:
@@ -210,49 +252,45 @@ class VfsPlugin2(IVSPlg):
         使用 DrissionPage 创建会话:
         1. 启动浏览器
         2. 导航到登录页
-        3. 自动过盾并提取 Token
+        3. 自动过盾并提取 Token (集成 CloudflareBypasser)
         4. JS fetch 登录
         """
-        self._log("Initializing Browser Session...")
+        self._log(f"Initializing Session (ID: {self.instance_id})...")
         
         # 0. 配置浏览器
         co = ChromiumOptions()
-        co.auto_port() # 自动分配端口
+        co.auto_port() 
+        
+        # --- [关键配置] 设置独立的用户数据目录 ---
+        # 这样每个实例的 Cache, Cookies, LocalStorage 都是完全隔离的
+        # 同时也防止了多进程争抢同一个 Default 文件夹导致的崩溃
+        co.set_user_data_path(self.user_data_path)
         
+        # 代理配置
         if self.config.proxy and self.config.proxy.ip:
             p = self.config.proxy
-        
-        # 情况 A: 有账号密码 -> 使用插件方案
-        if p.username and p.password:
-            self._log(f"Configuring authenticated proxy: {p.ip}:{p.port}")
-            plugin_path = create_proxy_auth_extension(
-                ip=p.ip,
-                port=p.port,
-                username=p.username,
-                password=p.password
-            )
-            co.add_extension(plugin_path)
-        
-        # 情况 B: 无账号密码 (IP白名单模式) -> 直接设置
-        else:
-            self._log(f"Configuring standard proxy: {p.ip}:{p.port}")
-            co.set_proxy(f"{p.scheme}://{p.ip}:{p.port}")
+            if p.username and p.password:
+                self._log(f"Configuring authenticated proxy: {p.ip}:{p.port}")
+                # [关键调用] 生成该实例独享的插件
+                plugin_path = create_proxy_auth_extension(
+                    p.ip, p.port, p.username, p.password, 
+                    self.proxy_ext_path # 传入唯一路径
+                )
+                co.add_extension(plugin_path)
+            else:
+                self._log(f"Configuring standard proxy: {p.ip}:{p.port}")
+                co.set_proxy(f"{p.scheme}://{p.ip}:{p.port}")
             
-        # 无头模式 (生产环境建议 True, 调试 False)
-        # co.headless(True) 
-        co.headless(False) # 调试时设为 False 方便观察
-        
-        # 反爬参数
+        co.headless(False) 
         co.set_argument('--no-sandbox')
         co.set_argument('--disable-gpu')
         co.set_argument('--window-size=1920,1080')
-        # 禁用自动化特征
         co.set_argument('--disable-blink-features=AutomationControlled')
 
         try:
             self.page = ChromiumPage(co)
             
-            # 1. 导航到登录页面 (建立 Context)
+            # 1. 导航到登录页面
             mission = self.free_config.get("mission_code", "")
             country = self.free_config.get("country_code", "")
             lang = self.free_config.get("language", "en")
@@ -265,37 +303,64 @@ class VfsPlugin2(IVSPlg):
             
             self.page.get(login_page_url)
             
-            # 2. 等待 Cloudflare 验证通过
-            # DrissionPage 会自动处理 Turnstile,我们只需要等待结果出现
-            # 通常 CF 的 widget 会生成一个 hidden input name="cf-turnstile-response"
-            self._log("Waiting for Cloudflare challenge...")
+            # -------------------------------------------------------------
+            # [核心修改] 2. 智能 Cloudflare 过盾逻辑
+            # -------------------------------------------------------------
+            self._log("Handling Cloudflare challenge...")
             
-            # 最多等待 30 秒
+            # 初始化过盾助手
+            cf_bypasser = CloudflareBypasser(self.page, log=self.config.debug)
             cf_token = ""
-            for _ in range(10):
-                # 间隔 1 秒
+            
+            # 循环检测 (40秒超时)
+            for i in range(40):
                 time.sleep(1)
+                
+                # A. 优先处理 Cookie 遮挡 (VFS 必须步骤)
+                # 如果不关掉 cookie banner,验证码可能点不到
                 self._handle_cookie_banner()
-                # 尝试从 DOM 获取 Token
+                
+                # B. 尝试从 DOM 获取 Token (无感验证可能自动通过)
                 try:
-                    # 检查是否有 cf-turnstile-response 元素且有值
-                    ele = self.page.ele('xpath://input[@name="cf-turnstile-response"]')
+                    ele = self.page.ele('@name=cf-turnstile-response')
                     if ele and ele.value:
                         cf_token = ele.value
-                        self._log("Cloudflare Turnstile token extracted from DOM.")
+                        self._log("Cloudflare Turnstile token extracted.")
                         break
                 except:
                     pass
                 
-                # 也可以检查是否已经看到了登录框 (id="mat-input-0" 或 form)
-                if self.page.ele('xpath://form'):
+                # C. 如果前 3 秒没自动出 Token,开始尝试点击
+                if i > 2:
+                    try:
+                        # 开启 DFS 深度搜索模式 (防止 Shadow DOM 嵌套太深找不到)
+                        # 在第 10 秒后开启深度搜索,前期用快速搜索
+                        use_dfs = (i > 10)
+                        
+                        cf_bypasser.click_verification_button(is_dfs=use_dfs)
+                    except Exception as e:
+                        # 点击错误忽略,继续下一轮
+                        pass
+                
+                # D. 检查是否已经看到了登录框 (有时候 Token 提取慢了,但页面已经变了)
+                if self.page.ele('tag:form') or self.page.ele('#mat-input-0'):
                     self._log("Login form detected.")
-                    # 即使 form 出来了,有时候 token 还在生成,稍微再等一下
+                    # 继续尝试提取一次 Token,如果实在没有也不要死循环
+                    if i > 5 and not cf_token:
+                        self._log("Form visible but token not found yet...")
+            
+            # -------------------------------------------------------------
+            
+            if not cf_token:
+                # 最后尝试一次强取
+                try:
+                    cf_token = self.page.ele('@name=cf-turnstile-response').value
+                except:
+                    pass
             
-            # 如果没拿到 token,尝试直接继续,或者报错
-            # 注意:有些 VFS 页面可能没有显式的 turnstile,而是隐式的
             if not cf_token:
-                self._log("[WARN] Could not extract Turnstile token. Trying to proceed anyway...")
+                self._log("[WARN] Could not extract Turnstile token.")
+                raise BizLogicError(f"Could not extract Turnstile token.")
 
             # 3. 准备登录 API 参数
             email = self.config.account.username
@@ -309,8 +374,7 @@ class VfsPlugin2(IVSPlg):
             headers = self._get_common_headers(with_auth=False)
             headers.update({
                 "clientsource": client_src,
-                "orangex": orange_src,
-                # DrissionPage fetch 不需要 content-type,json参数会自动加
+                "orangex": orange_src
             })
             
             data = {
@@ -320,7 +384,7 @@ class VfsPlugin2(IVSPlg):
                 "countrycode": country,
                 "languageCode": "en-US",
                 "captcha_version": "cloudflare-v1",
-                "captcha_api_key": cf_token  # 填入提取到的 Token
+                "captcha_api_key": cf_token 
             }
             
             self._log("Sending Login Request via Browser Fetch...")
@@ -335,6 +399,8 @@ class VfsPlugin2(IVSPlg):
             # 分支 2: OTP
             elif resp_json.get("enableOTPAuthentication"):
                 self._log("Login requires OTP.")
+                # 注意:_submit_login_otp 内部也会调用 _refresh_turnstile_token
+                # 所以这里旧的 cf_token 其实用处不大,传过去也没事
                 otp = self._read_otp_email()
                 self._submit_login_otp(cf_token, otp)
             
@@ -342,7 +408,6 @@ class VfsPlugin2(IVSPlg):
                 raise BizLogicError(f"Login failed: {resp.text[:200]}")
 
             self.session_create_time = time.time()
-            # 获取真实IP (用于日志)
             try:
                 self.real_ip = self._get_realnetwork_ip()
             except:
@@ -350,9 +415,7 @@ class VfsPlugin2(IVSPlg):
                 
         except Exception as e:
             self._log(f"Create Session Failed: {e}")
-            if self.page:
-                self.page.quit()
-                self.page = None
+            self.cleanup()
             raise e
 
     def query(self) -> VSQueryResult:
@@ -372,7 +435,7 @@ class VfsPlugin2(IVSPlg):
             result.availability_status = AvailabilityStatus.NoneAvailable
             result.visa_type = apt_config.get("visa_type", "")
             result.city = apt_config.get("city", "")
-            
+            result.routing_key = apt_config.get("routing_key", "")
             if earliest_date:
                 result.success = True
                 if "WaitList" in earliest_date:
@@ -757,12 +820,11 @@ class VfsPlugin2(IVSPlg):
     
     def _refresh_turnstile_token(self) -> str:
         """
-        强制刷新 Cloudflare Turnstile 并获取新 Token (增强版)
+        强制刷新 Cloudflare Turnstile 并获取新 Token (集成 CloudflareBypasser 版)
         """
         self._log("Refreshing Cloudflare Turnstile token...")
         
-        # 1. JS 强制重置
-        # 加上 try-catch 防止页面没有 turnstile 对象导致崩溃
+        # 1. JS 强制重置 (保持不变)
         js_reset = """
         try {
             var input = document.querySelector('input[name="cf-turnstile-response"]');
@@ -774,32 +836,41 @@ class VfsPlugin2(IVSPlg):
         """
         self.page.run_js(js_reset)
         
-        # 2. 轮询等待 (增加到 30 秒)
-        # 策略:检测 Token -> 如果没有且有 iframe -> 点击 iframe 触发验证
-        for i in range(60): # 60 * 0.5s = 30s
+        # 2. 初始化过盾助手
+        # 假设 CloudflareBypasser 类已在当前文件中定义
+        cf_bypasser = CloudflareBypasser(self.page, log=self.config.debug)
+        
+        # 3. 轮询等待 (30秒)
+        for i in range(60): 
             time.sleep(0.5)
             
-            # A. 尝试直接获取 Token (使用 JS 获取更稳定)
-            token = self.page.run_js('return document.querySelector("input[name=\'cf-turnstile-response\']")?.value')
-            if token:
-                self._log("Turnstile token refreshed successfully.")
-                return token
+            # A. 检查 Token 是否已生成
+            # 使用 DrissionPage 的方式获取 value 比较稳定
+            try:
+                ele = self.page.ele('@name=cf-turnstile-response')
+                if ele and ele.value:
+                    self._log("Turnstile token refreshed successfully.")
+                    return ele.value
+            except:
+                pass
             
-            # B. 如果等待了 3 秒还没结果,尝试寻找 iframe 并点击
-            # Cloudflare 有时需要用户点一下 "Verify you are human"
-            if i > 6 and (i % 5 == 0): # 每隔 2.5 秒尝试点一次
+            # B. 尝试点击验证框
+            # 策略:前2秒等待,之后开始尝试点击
+            if i > 4: 
+                # [重要] VFS 经常有 Cookie 弹窗遮挡,先尝试清理一下
+                self._handle_cookie_banner()
+                
                 try:
-                    # 查找包含 turnstile 或 cloudflare 的 iframe
-                    # VFS 页面通常只有一个
-                    cf_iframe = self.page.ele('xpath://iframe[contains(@src, "turnstile") or contains(@src, "cloudflare")]')
-                    if cf_iframe:
-                        # 尝试点击 iframe 的中心位置
-                        # self._log("Clicking Cloudflare widget to activate...")
-                        cf_iframe.click(by_js=True) 
-                except Exception:
+                    # 使用 CloudflareBypasser 的高级点击逻辑
+                    # is_dfs=True 表示如果普通搜索找不到,就递归搜索 iframe (更耗时但更强)
+                    # 我们在尝试 10 次 (5秒) 后开启 DFS 模式
+                    use_dfs = (i > 14)
+                    
+                    cf_bypasser.click_verification_button(is_dfs=use_dfs)
+                except Exception as e:
+                    # 点击过程报错不要中断主循环
                     pass
         
-        # 如果超时,为了调试,打印一下当前页面源码的一部分或截图(可选)
         raise BizLogicError("Failed to refresh Cloudflare Turnstile token (Timeout)")
 
     # -------------------------------------------------------------
@@ -1373,4 +1444,40 @@ class VfsPlugin2(IVSPlg):
                 curr = curr.replace(year=curr.year + 1, month=1)
             else:
                 curr = curr.replace(month=curr.month + 1)
-        return months
+        return months
+    
+    # --- 资源清理核心方法 ---
+    def cleanup(self):
+        """
+        销毁浏览器并彻底删除临时文件
+        """
+        # 1. 关闭浏览器
+        if self.page:
+            try:
+                self.page.quit() # 这会关闭 Chrome 进程
+            except Exception:
+                pass # 忽略已关闭的错误
+            self.page = None
+        
+        # 2. 删除文件
+        # 注意:Chrome 关闭后可能需要几百毫秒释放文件锁,稍微等待
+        if os.path.exists(self.root_workspace):
+            for _ in range(3):
+                try:
+                    time.sleep(0.2)
+                    shutil.rmtree(self.root_workspace, ignore_errors=True)
+                    break
+                except Exception as e:
+                    # 如果删除失败(通常是Windows文件占用),重试
+                    if self.logger: self.logger(f"Cleanup retry: {e}")
+                    time.sleep(0.5)
+            
+            # 如果依然存在,打印警告(虽然 ignore_errors=True 会掩盖报错,但可以 check exists)
+            if os.path.exists(self.root_workspace) and self.logger:
+                 self.logger(f"[WARN] Failed to fully remove workspace: {self.root_workspace}")
+                 
+    def __del__(self):
+        """
+        析构函数:当对象被垃圾回收时自动调用
+        """
+        self.cleanup()