Browse Source

feat: update

jerry 3 months ago
parent
commit
2a27b78d55
2 changed files with 82 additions and 25 deletions
  1. 2 2
      plugins/bls_plugin.py
  2. 80 23
      plugins/vfs_plugin2.py

+ 2 - 2
plugins/bls_plugin.py

@@ -70,9 +70,9 @@ class BlsPlugin(IVSPlg):
         
     def _log(self, message):
         if self.logger:
-            self.logger(f'[TlsPlugin] [{self.group_id}] {message}')
+            self.logger(f'[BlsPlugin] [{self.group_id}] {message}')
         else:
-            print(f'[TlsPlugin] [{self.group_id}] {message}')
+            print(f'[BlsPlugin] [{self.group_id}] {message}')
 
     def set_config(self, config: VSPlgConfig):
         self.config = config

+ 80 - 23
plugins/vfs_plugin2.py

@@ -435,53 +435,80 @@ class VfsPlugin2(IVSPlg):
     def _perform_request(self, method, url, headers=None, data=None, json_data=None, params=None, retry_count=0):
         """
         核心方法:在 DrissionPage 浏览器上下文中注入 JS 执行 fetch
+        并记录详细的 Traffic 日志用于分析
         """
         if not self.page:
             raise BizLogicError("Browser session not initialized")
 
-        # 1. 确保在正确的上下文 (VFS 登录页或 API 域名)
-        # create_session 已经打开了页面,这里通常不需要额外跳转
-        # 如果页面崩溃或跳转了,可能需要恢复
-        
-        # 2. 构造参数
+        # ---------------------------------------------------------
+        # 1. 预处理 URL (构造最终请求地址)
+        # ---------------------------------------------------------
+        req_url = url
         if params:
-            if '?' in url:
-                url += '&' + urllib.parse.urlencode(params)
-            else:
-                url += '?' + urllib.parse.urlencode(params)
+            # 确保引用了 urllib
+            import urllib.parse
+            sep = '&' if '?' in req_url else '?'
+            req_url += sep + urllib.parse.urlencode(params)
 
+        # ---------------------------------------------------------
+        # 2. 构造 Body 和 Fetch 选项
+        # ---------------------------------------------------------
+        final_headers = headers or {}
+        
         fetch_options = {
             "method": method.upper(),
-            "headers": headers or {},
+            "headers": final_headers,
             "credentials": "include" # 关键:带上浏览器 Cookie
         }
         
+        # 用于日志记录的 Body 内容(字符串形式)
+        log_body = "None"
+
         if json_data:
-            fetch_options['body'] = json.dumps(json_data)
+            json_str = json.dumps(json_data)
+            fetch_options['body'] = json_str
             fetch_options['headers']['Content-Type'] = 'application/json'
+            log_body = json_str
         elif data:
             if isinstance(data, dict):
-                fetch_options['body'] = urllib.parse.urlencode(data)
+                import urllib.parse
+                encoded_data = urllib.parse.urlencode(data)
+                fetch_options['body'] = encoded_data
                 fetch_options['headers']['Content-Type'] = 'application/x-www-form-urlencoded'
+                log_body = encoded_data
             else:
                 fetch_options['body'] = data
-
-        # 3. 注入 JS
+                log_body = str(data)
+
+        # ---------------------------------------------------------
+        # [日志] 记录请求数据
+        # ---------------------------------------------------------
+        self._log(f"┌── [TRAFFIC REQUEST] {method} {req_url}")
+        self._log(f"├── Headers: {json.dumps(final_headers)}")
+        self._log(f"└── Body: {log_body}")
+
+        # ---------------------------------------------------------
+        # 3. 注入 JS 执行 Fetch
+        # ---------------------------------------------------------
         js_script = f"""
-        const url = "{url}";
+        const url = "{req_url}";
         const options = {json.dumps(fetch_options)};
         
+        const startTime = Date.now();
+        
         return fetch(url, options)
             .then(async response => {{
                 const text = await response.text();
                 const headers = {{}};
                 response.headers.forEach((value, key) => headers[key] = value);
+                const endTime = Date.now();
                 
                 return {{
                     status: response.status,
                     body: text,
                     headers: headers,
-                    url: response.url
+                    url: response.url,
+                    duration: endTime - startTime
                 }};
             }})
             .catch(error => {{
@@ -489,48 +516,78 @@ class VfsPlugin2(IVSPlg):
                     status: 0,
                     body: error.toString(),
                     headers: {{}},
-                    url: url
+                    url: url,
+                    duration: Date.now() - startTime
                 }};
             }});
         """
         
-        if self.config.debug:
-            self._log(f"[Browser Fetch] {method} {url}")
-
         try:
             # run_js 直接返回 return 的对象
-            res_dict = self.page.run_js(js_script, timeout=30)
+            # 适当增加超时时间,防止网络慢导致 Python 侧报错
+            res_dict = self.page.run_js(js_script, timeout=60)
         except Exception as e:
+            self._log(f"[TRAFFIC ERROR] JS Execution failed: {e}")
             raise BizLogicError(f"Browser JS Execution Error: {e}")
 
         resp = BrowserResponse(res_dict)
         
+        # ---------------------------------------------------------
+        # [日志] 记录响应数据
+        # ---------------------------------------------------------
+        duration = res_dict.get('duration', 0)
+        # 截取过长的响应体,避免日志文件爆炸 (保留前 500 字符)
+        # 如果需要完整分析,可以去掉 [:500]
+        resp_preview = resp.text[:500] + "..." if len(resp.text) > 500 else resp.text
+        
+        self._log(f"┌── [TRAFFIC RESPONSE] Status: {resp.status_code} | Time: {duration}ms")
+        self._log(f"└── Body: {resp_preview}")
+
+        # ---------------------------------------------------------
         # 4. 统一处理状态码
+        # ---------------------------------------------------------
         if resp.status_code == 200:
             return resp
+            
         elif resp.status_code == 401:
             self.is_healthy = False
             raise SessionExpiredOrInvalidError(f"401 Unauthorized: {resp.text[:100]}")
+            
         elif resp.status_code == 403:
+            # 检查是否是 Cloudflare 拦截
             if "Just a moment" in resp.text or "cloudflare" in resp.text.lower():
-                self._log(f"HTTP 403 (Cloudflare) detected. Re-verifying (Try {retry_count+1}/3)...")
+                self._log(f"[TRAFFIC] HTTP 403 (Cloudflare) detected. Re-verifying (Try {retry_count+1}/3)...")
+                
                 if retry_count < 3:
+                    # 调用过盾逻辑
                     new_token = self._refresh_turnstile_token()
+                    
                     if new_token:
-                        self._log("In-page verification success. Retrying...")
+                        self._log("[TRAFFIC] In-page verification success. Retrying...")
+                        
+                        # 如果原请求包含验证码字段,更新它
                         if json_data and "captcha_api_key" in json_data:
                             json_data["captcha_api_key"] = new_token
+                            
+                        # 递归重试
                         return self._perform_request(method, url, headers, data, json_data, params, retry_count+1)
+            
+            # 如果不是 CF 或者重试耗尽
             raise PermissionDeniedError(f"HTTP 403 Forbidden: {resp.text[:100]}")
+            
         elif resp.status_code == 429:
             self.is_healthy = False
             raise RateLimiteddError(f"429 Rate Limit: {resp.text[:100]}")
+            
         elif resp.status_code == 0:
             raise BizLogicError(f"Network Error (Fetch Failed): {resp.text}")
+            
         else:
             # 允许 400 业务错误通过,交给上层解析 (例如登录失败)
             if url.endswith("/login") and resp.status_code == 400:
                 return resp
+            
+            # 其他错误视为业务逻辑异常
             raise BizLogicError(message=f"HTTP Error {resp.status_code}: {resp.text[:100]}")
 
     def _handle_cookie_banner(self):