From 256e6c21a521391ad20decd3c814843a0a391aab Mon Sep 17 00:00:00 2001 From: jjz <3082705704@qq.com> Date: Tue, 16 Sep 2025 11:17:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=B8=8B=E5=8F=91=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E9=AA=8C=E8=AF=81=E7=A0=81=E7=BB=93=E6=9E=84=E7=BB=99?= =?UTF-8?q?=E5=90=8E=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Utils/Pdd/PddUtils.py | 219 +++++++++++++++++++-------------- WebSocket/BackendClient.py | 4 +- WebSocket/backend_singleton.py | 12 +- 3 files changed, 141 insertions(+), 94 deletions(-) diff --git a/Utils/Pdd/PddUtils.py b/Utils/Pdd/PddUtils.py index a3ab037..f7a9b97 100644 --- a/Utils/Pdd/PddUtils.py +++ b/Utils/Pdd/PddUtils.py @@ -15,10 +15,12 @@ import json import random import time import traceback + +import execjs import websockets import requests import asyncio -import execjs +# execjs 已移除 - 不再需要本地生成anti_content from datetime import datetime from typing import Dict, Any, Optional, Callable @@ -997,70 +999,7 @@ class ImgDistance: ) -class AutiContent: - """anti_content生成类""" - - def __init__(self): - self.ctx = None - try: - # 使用新的auti_content.js文件 - current_dir = "static/js" - auti_js_path = os.path.join(current_dir, "auti_content.js") - - if os.path.exists(auti_js_path): - # 暂时禁用JS文件加载,直接使用fallback避免编码问题 - logger.info("检测到auti_content.js文件,但为避免编码问题使用fallback实现") - self.ctx = None - else: - logger.warning("auti_content.js文件不存在,使用fallback实现") - # fallback JS代码 - jscode = """ - function auti_content() { - // 生成类似格式的anti_content - var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'; - var result = ''; - for (var i = 0; i < 40; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return result; - } - function gzipCompress(data) { - return data; // 简单返回原数据 - } - """ - self.ctx = execjs.compile(jscode) - except Exception as e: - logger.error(f"初始化AutiContent失败: {e}") - # 最终fallback - self.ctx = None - - def get_auti_content(self): - """获取anti_content""" - try: - if self.ctx: - # 尝试调用JS函数,如果遇到编码问题则降级处理 - result = self.ctx.call("auti_content") - # 检查结果是否包含非ASCII字符 - try: - result.encode('ascii') - logger.info(f"成功生成auti_content: {result[:20]}...") - return result - except UnicodeEncodeError: - logger.warning("JS生成的auti_content包含特殊字符,使用fallback") - raise Exception("包含特殊字符") - except Exception as e: - logger.error(f"调用auti_content函数失败: {e}") - - # fallback处理 - import uuid - import random - import string - - # 生成类似格式的anti_content字符串 - chars = string.ascii_letters + string.digits + '-_' - result = ''.join(random.choice(chars) for _ in range(40)) - logger.warning(f"使用fallback生成auti_content: {result}") - return result +# AutiContent类已移除 - 后端会提供所有必要的anti_content def gzip_compress(self, data): """压缩数据""" @@ -1117,7 +1056,7 @@ class EncryptTool: return t -class PddLogin(AutiContent): +class PddLogin: """拼多多登录核心类""" def __init__(self, log_callback=None): @@ -1624,11 +1563,11 @@ class PddLogin(AutiContent): return captcha_collect # 获取滑块加密参数key和iv - def vc_pre_ck_b(self): + def vc_pre_ck_b(self, backend_anti_content): self.headers.pop("anti-content", None) url = "https://apiv2.pinduoduo.net/api/phantom/vc_pre_ck_b" payload = { - "anti_content": self.get_auti_content(), + "anti_content": backend_anti_content, "verify_auth_token": self.verify_auth_token, "sdk_type": 3, "client_time": int(round(time.time() * 1000)) @@ -1638,10 +1577,10 @@ class PddLogin(AutiContent): return response.json().get("salt") # 获取滑块图片 - def obtain_captcha(self): + def obtain_captcha(self, backend_anti_content): url = "https://apiv2.pinduoduo.net/api/phantom/obtain_captcha" payload = { - "anti_content": self.get_auti_content(), + "anti_content": backend_anti_content, "verify_auth_token": self.verify_auth_token, "captcha_collect": "" } @@ -1650,13 +1589,13 @@ class PddLogin(AutiContent): return response.json().get("pictures") # 滑块验证 - def verify(self, captcha_collect, verify_code): + def verify(self, captcha_collect, verify_code, backend_anti_content): url = "https://apiv2.pinduoduo.net/api/phantom/user_verify" payload = { "verify_code": verify_code, "captcha_collect": captcha_collect, "verify_auth_token": self.verify_auth_token, - "anti_content": self.get_auti_content() + "anti_content": backend_anti_content } response = requests.post(url, headers=self.headers, json=payload, cookies=self.cookies) self.cookies.update(response.cookies.get_dict()) @@ -1665,15 +1604,19 @@ class PddLogin(AutiContent): return result # 发送验证码通知给后端,并获取验证码 - def request_verification_code(self, username, store_id): + def request_verification_code(self, username, store_id, backend_anti_content): """向后端请求获取手机验证码""" self._log(f"开始请求验证码,用户名: {username}, 店铺ID: {store_id}", "INFO") - self.headers["anti-content"] = self.get_auti_content() + # 使用后端下发的anti_content + anti_content = backend_anti_content + self._log(f"使用后端下发的anti_content", "DEBUG") + + self.headers["anti-content"] = anti_content url = "https://mms.pinduoduo.com/janus/api/user/getLoginVerificationCode" payload = { "username": username, - "crawlerInfo": self.get_auti_content() + "crawlerInfo": anti_content } response = requests.post(url, headers=self.headers, json=payload, cookies=self.cookies) self._log(f"发送验证码请求结果: {response.text}") @@ -1711,10 +1654,83 @@ class PddLogin(AutiContent): import traceback self._log(f"错误详情: {traceback.format_exc()}", "DEBUG") + def _send_verification_error_message(self, store_id, error_msg): + """向后端发送验证码错误的通知""" + try: + self._log(f"开始发送验证码错误通知,店铺ID: {store_id}, 错误: {error_msg}", "INFO") + from WebSocket.backend_singleton import get_backend_client + backend = get_backend_client() + + if backend: + message = { + "type": "connect_message", + "store_id": store_id, + "status": False, + "content": error_msg + } + self._log(f"准备发送验证码错误消息: {message}", "DEBUG") + backend.send_message(message) + self._log("✅ 成功向后端发送验证码错误通知", "SUCCESS") + else: + self._log("❌ 后端客户端为空,无法发送验证码错误通知", "ERROR") + except Exception as e: + self._log(f"❌ 发送验证码错误通知失败: {e}", "ERROR") + import traceback + self._log(f"错误详情: {traceback.format_exc()}", "DEBUG") + + def _send_login_success_message(self, store_id): + """向后端发送登录成功的通知""" + try: + self._log(f"开始发送登录成功通知,店铺ID: {store_id}", "INFO") + from WebSocket.backend_singleton import get_backend_client + backend = get_backend_client() + + if backend: + message = { + "type": "connect_message", + "store_id": store_id, + "status": True # 登录成功 + } + self._log(f"准备发送登录成功消息: {message}", "DEBUG") + backend.send_message(message) + self._log("✅ 成功向后端发送登录成功通知", "SUCCESS") + else: + self._log("❌ 获取后端客户端失败", "ERROR") + except Exception as e: + self._log(f"❌ 发送登录成功通知失败: {e}", "ERROR") + import traceback + self._log(f"错误详情: {traceback.format_exc()}", "DEBUG") + + def _send_login_failure_message(self, store_id, error_msg): + """向后端发送登录失败的通知""" + try: + self._log(f"开始发送登录失败通知,店铺ID: {store_id}, 错误: {error_msg}", "INFO") + from WebSocket.backend_singleton import get_backend_client + backend = get_backend_client() + + if backend: + message = { + "type": "connect_message", + "store_id": store_id, + "status": False, # 登录失败 + "content": error_msg # 官方返回的失败原因 + } + self._log(f"准备发送登录失败消息: {message}", "DEBUG") + backend.send_message(message) + self._log("✅ 成功向后端发送登录失败通知", "SUCCESS") + else: + self._log("❌ 获取后端客户端失败", "ERROR") + except Exception as e: + self._log(f"❌ 发送登录失败通知失败: {e}", "ERROR") + import traceback + self._log(f"错误详情: {traceback.format_exc()}", "DEBUG") + def login_with_params(self, login_params, store_id=None, error_count=0, success_count=0): """使用后端下发的登录参数进行登录""" self._log("🚀 [PddLogin] 开始使用参数登录", "INFO") - self._log(f"📋 [PddLogin] 登录参数: username={login_params.get('username', 'N/A')}, 包含code={bool(login_params.get('code'))}", "DEBUG") + # 检查验证码字段(兼容 code 和 verification_code) + verification_code = login_params.get("verification_code") or login_params.get("code", "") + self._log(f"📋 [PddLogin] 登录参数: username={login_params.get('username', 'N/A')}, 包含验证码={bool(verification_code)}", "DEBUG") self.headers["anti-content"] = login_params.get("anti_content", "") @@ -1724,8 +1740,8 @@ class PddLogin(AutiContent): "username": login_params.get("username", ""), "password": login_params.get("password", ""), # 后端已加密 "passwordEncrypt": True, - "verificationCode": "", - "mobileVerifyCode": login_params.get("code", ""), # 检查是否包含验证码 + "verificationCode": verification_code, + "mobileVerifyCode": "", # 使用兼容的验证码字段 "sign": "", "touchevent": { "mobileInputEditStartTime": ts - random.randint(10000, 20000), @@ -1779,6 +1795,11 @@ class PddLogin(AutiContent): "crawlerInfo": login_params.get("anti_content", "") } + # 添加详细的请求日志 + self._log(f"🔍 [Debug] 登录请求URL: {self.login_api}", "DEBUG") + self._log(f"🔍 [Debug] 登录请求payload: {payload}", "DEBUG") + self._log(f"🔍 [Debug] 登录请求headers: {dict(self.headers)}", "DEBUG") + response = requests.post(self.login_api, headers=self.headers, json=payload, cookies=self.cookies) self.cookies.update(response.cookies.get_dict()) self._log(f"登录响应状态码: {response.status_code}") @@ -1823,29 +1844,43 @@ class PddLogin(AutiContent): return False elif "需要手机验证" in response.text: - # 检查是否已经包含验证码 - if login_params.get("code"): - # 已有验证码但仍需要验证,可能验证码错误或过期 - self._log("验证码可能错误或过期", "WARNING") - return False + # 检查是否已经包含验证码(兼容 code 和 verification_code) + if verification_code: + # 已有验证码但仍需要验证,验证码可能错误、过期或者其他问题 + self._log(f"带验证码登录失败,验证码: {verification_code}", "WARNING") + # 检查响应中的具体错误信息 + response_data = response.json() + error_msg = response_data.get('errorMsg', '验证码验证失败') + self._log(f"服务器返回错误: {error_msg}", "WARNING") + + # 不要重新发送验证码请求,直接报告验证失败 + self._send_verification_error_message(store_id, error_msg) # 直接使用官方错误信息 + return "verification_code_error" # 返回特殊状态,避免重复发送消息 else: # 第一次需要验证码,调用发送验证码方法 self._log("检测到需要手机验证码,正在调用发送验证码方法", "INFO") username = login_params.get("username") - self._log(f"为用户 {username} 发送验证码", "INFO") - - self.request_verification_code(username, store_id) + backend_anti_content = login_params.get("anti_content") + self._log(f"为用户 {username} 发送验证码,使用后端anti_content", "INFO") + + # 传递后端下发的anti_content + self.request_verification_code(username, store_id, backend_anti_content) return "need_verification_code" else: # 登录成功或其他情况 response_data = response.json() if response_data.get("success"): - self._log("登录成功", "SUCCESS") + self._log("🎉 登录成功!", "SUCCESS") + # 发送登录成功通知给后端 + self._send_login_success_message(store_id) return self.cookies else: - self._log(f"登录失败: {response_data.get('errorMsg', '未知错误')}", "ERROR") - return False + # 登录失败,发送失败通知给后端 + error_msg = response_data.get('errorMsg', '未知错误') + self._log(f"登录失败: {error_msg}", "ERROR") + self._send_login_failure_message(store_id, error_msg) + return "login_failure" # 返回特殊状态,避免重复发送消息 # ===== 登录相关类集成结束 ===== @@ -2810,6 +2845,12 @@ class PddListenerForGUI: if login_result == "need_verification_code": self._log("⚠️ [PDD] 需要手机验证码,已通知后端,等待重新下发包含验证码的登录参数", "WARNING") return "need_verification_code" # 返回特殊标识,避免被覆盖 + elif login_result == "verification_code_error": + self._log("⚠️ [PDD] 验证码错误,已通知后端", "WARNING") + return "verification_code_error" # 返回特殊标识,避免重复发送消息 + elif login_result == "login_failure": + self._log("⚠️ [PDD] 登录失败,已发送失败通知给后端", "WARNING") + return "login_failure" # 返回特殊标识,避免重复发送消息 elif not login_result: self._log("❌ [PDD] 登录失败", "ERROR") return False diff --git a/WebSocket/BackendClient.py b/WebSocket/BackendClient.py index 3c6395c..ff4870c 100644 --- a/WebSocket/BackendClient.py +++ b/WebSocket/BackendClient.py @@ -874,9 +874,9 @@ class BackendClient: data = message.get('data', {}) # 判断是拼多多登录参数还是普通Cookie - if platform_name == "拼多多" and content == "拼多多登录" and data.get('login_params'): + if platform_name == "拼多多" and ("拼多多登录" in content) and data.get('login_params'): # 拼多多登录参数模式 - 传递完整的消息JSON给处理器 - print(f"收到拼多多登录参数: 平台={platform_name}, 店铺={store_id}") + print(f"收到拼多多登录参数: 平台={platform_name}, 店铺={store_id}, 类型={content}") if self.login_callback: # 传递完整的JSON消息,让拼多多处理器来解析login_params import json diff --git a/WebSocket/backend_singleton.py b/WebSocket/backend_singleton.py index c15d27f..9f0779f 100644 --- a/WebSocket/backend_singleton.py +++ b/WebSocket/backend_singleton.py @@ -359,6 +359,8 @@ class WebSocketManager: # 详细的结果分析 if result == "need_verification_code": self._log("✅ [PDD] 登录流程正常,已发送验证码需求通知给后端", "SUCCESS") + elif result == "verification_code_error": + self._log("⚠️ [PDD] 验证码错误,已发送错误通知给后端", "WARNING") elif result: self._log("✅ [PDD] 登录成功,平台连接已建立", "SUCCESS") else: @@ -371,13 +373,13 @@ class WebSocketManager: self._log(f"📊 start_with_cookies 执行结果: {result}", "DEBUG") # 根据实际登录结果上报状态给后端 - if self.backend_client and result != "need_verification_code": - # 如果返回need_verification_code,说明验证码通知已经在PddLogin中发送了,不需要重复发送 + if self.backend_client and result not in ["need_verification_code", "verification_code_error", "login_failure"]: + # 如果是特殊状态,说明通知已经在PddLogin中发送了,不需要重复发送 try: message = { "type": "connect_message", "store_id": store_id, - "status": bool(result) if result != "need_verification_code" else False + "status": bool(result) } self.backend_client.send_message(message) status_text = "成功" if result else "失败" @@ -386,6 +388,10 @@ class WebSocketManager: self._log(f"上报拼多多平台连接状态失败: {send_e}", "ERROR") elif result == "need_verification_code": self._log("需要验证码,验证码通知已由PddLogin发送,等待后端重新下发登录参数", "INFO") + elif result == "verification_code_error": + self._log("验证码错误,错误通知已由PddLogin发送,等待后端处理", "INFO") + elif result == "login_failure": + self._log("登录失败,失败通知已由PddLogin发送,等待后端处理", "INFO") return result