实现下发需要验证码结构给后端

This commit is contained in:
jjz
2025-09-16 11:17:30 +08:00
parent faef5d2316
commit 256e6c21a5
3 changed files with 141 additions and 94 deletions

View File

@@ -15,10 +15,12 @@ import json
import random import random
import time import time
import traceback import traceback
import execjs
import websockets import websockets
import requests import requests
import asyncio import asyncio
import execjs # execjs 已移除 - 不再需要本地生成anti_content
from datetime import datetime from datetime import datetime
from typing import Dict, Any, Optional, Callable from typing import Dict, Any, Optional, Callable
@@ -997,70 +999,7 @@ class ImgDistance:
) )
class AutiContent: # AutiContent类已移除 - 后端会提供所有必要的anti_content
"""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
def gzip_compress(self, data): def gzip_compress(self, data):
"""压缩数据""" """压缩数据"""
@@ -1117,7 +1056,7 @@ class EncryptTool:
return t return t
class PddLogin(AutiContent): class PddLogin:
"""拼多多登录核心类""" """拼多多登录核心类"""
def __init__(self, log_callback=None): def __init__(self, log_callback=None):
@@ -1624,11 +1563,11 @@ class PddLogin(AutiContent):
return captcha_collect return captcha_collect
# 获取滑块加密参数key和iv # 获取滑块加密参数key和iv
def vc_pre_ck_b(self): def vc_pre_ck_b(self, backend_anti_content):
self.headers.pop("anti-content", None) self.headers.pop("anti-content", None)
url = "https://apiv2.pinduoduo.net/api/phantom/vc_pre_ck_b" url = "https://apiv2.pinduoduo.net/api/phantom/vc_pre_ck_b"
payload = { payload = {
"anti_content": self.get_auti_content(), "anti_content": backend_anti_content,
"verify_auth_token": self.verify_auth_token, "verify_auth_token": self.verify_auth_token,
"sdk_type": 3, "sdk_type": 3,
"client_time": int(round(time.time() * 1000)) "client_time": int(round(time.time() * 1000))
@@ -1638,10 +1577,10 @@ class PddLogin(AutiContent):
return response.json().get("salt") 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" url = "https://apiv2.pinduoduo.net/api/phantom/obtain_captcha"
payload = { payload = {
"anti_content": self.get_auti_content(), "anti_content": backend_anti_content,
"verify_auth_token": self.verify_auth_token, "verify_auth_token": self.verify_auth_token,
"captcha_collect": "" "captcha_collect": ""
} }
@@ -1650,13 +1589,13 @@ class PddLogin(AutiContent):
return response.json().get("pictures") 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" url = "https://apiv2.pinduoduo.net/api/phantom/user_verify"
payload = { payload = {
"verify_code": verify_code, "verify_code": verify_code,
"captcha_collect": captcha_collect, "captcha_collect": captcha_collect,
"verify_auth_token": self.verify_auth_token, "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) response = requests.post(url, headers=self.headers, json=payload, cookies=self.cookies)
self.cookies.update(response.cookies.get_dict()) self.cookies.update(response.cookies.get_dict())
@@ -1665,15 +1604,19 @@ class PddLogin(AutiContent):
return result 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._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" url = "https://mms.pinduoduo.com/janus/api/user/getLoginVerificationCode"
payload = { payload = {
"username": username, "username": username,
"crawlerInfo": self.get_auti_content() "crawlerInfo": anti_content
} }
response = requests.post(url, headers=self.headers, json=payload, cookies=self.cookies) response = requests.post(url, headers=self.headers, json=payload, cookies=self.cookies)
self._log(f"发送验证码请求结果: {response.text}") self._log(f"发送验证码请求结果: {response.text}")
@@ -1711,10 +1654,83 @@ class PddLogin(AutiContent):
import traceback import traceback
self._log(f"错误详情: {traceback.format_exc()}", "DEBUG") 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): def login_with_params(self, login_params, store_id=None, error_count=0, success_count=0):
"""使用后端下发的登录参数进行登录""" """使用后端下发的登录参数进行登录"""
self._log("🚀 [PddLogin] 开始使用参数登录", "INFO") 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", "") self.headers["anti-content"] = login_params.get("anti_content", "")
@@ -1724,8 +1740,8 @@ class PddLogin(AutiContent):
"username": login_params.get("username", ""), "username": login_params.get("username", ""),
"password": login_params.get("password", ""), # 后端已加密 "password": login_params.get("password", ""), # 后端已加密
"passwordEncrypt": True, "passwordEncrypt": True,
"verificationCode": "", "verificationCode": verification_code,
"mobileVerifyCode": login_params.get("code", ""), # 检查是否包含验证码 "mobileVerifyCode": "", # 使用兼容的验证码字段
"sign": "", "sign": "",
"touchevent": { "touchevent": {
"mobileInputEditStartTime": ts - random.randint(10000, 20000), "mobileInputEditStartTime": ts - random.randint(10000, 20000),
@@ -1779,6 +1795,11 @@ class PddLogin(AutiContent):
"crawlerInfo": login_params.get("anti_content", "") "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) response = requests.post(self.login_api, headers=self.headers, json=payload, cookies=self.cookies)
self.cookies.update(response.cookies.get_dict()) self.cookies.update(response.cookies.get_dict())
self._log(f"登录响应状态码: {response.status_code}") self._log(f"登录响应状态码: {response.status_code}")
@@ -1823,29 +1844,43 @@ class PddLogin(AutiContent):
return False return False
elif "需要手机验证" in response.text: elif "需要手机验证" in response.text:
# 检查是否已经包含验证码 # 检查是否已经包含验证码(兼容 code 和 verification_code
if login_params.get("code"): if verification_code:
# 已有验证码但仍需要验证,可能验证码错误过期 # 已有验证码但仍需要验证,验证码可能错误过期或者其他问题
self._log("验证码可能错误或过期", "WARNING") self._log(f"验证码登录失败,验证码: {verification_code}", "WARNING")
return False # 检查响应中的具体错误信息
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: else:
# 第一次需要验证码,调用发送验证码方法 # 第一次需要验证码,调用发送验证码方法
self._log("检测到需要手机验证码,正在调用发送验证码方法", "INFO") self._log("检测到需要手机验证码,正在调用发送验证码方法", "INFO")
username = login_params.get("username") username = login_params.get("username")
self._log(f"为用户 {username} 发送验证码", "INFO") backend_anti_content = login_params.get("anti_content")
self._log(f"为用户 {username} 发送验证码使用后端anti_content", "INFO")
self.request_verification_code(username, store_id)
# 传递后端下发的anti_content
self.request_verification_code(username, store_id, backend_anti_content)
return "need_verification_code" return "need_verification_code"
else: else:
# 登录成功或其他情况 # 登录成功或其他情况
response_data = response.json() response_data = response.json()
if response_data.get("success"): if response_data.get("success"):
self._log("登录成功", "SUCCESS") self._log("🎉 登录成功", "SUCCESS")
# 发送登录成功通知给后端
self._send_login_success_message(store_id)
return self.cookies return self.cookies
else: 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": if login_result == "need_verification_code":
self._log("⚠️ [PDD] 需要手机验证码,已通知后端,等待重新下发包含验证码的登录参数", "WARNING") self._log("⚠️ [PDD] 需要手机验证码,已通知后端,等待重新下发包含验证码的登录参数", "WARNING")
return "need_verification_code" # 返回特殊标识,避免被覆盖 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: elif not login_result:
self._log("❌ [PDD] 登录失败", "ERROR") self._log("❌ [PDD] 登录失败", "ERROR")
return False return False

View File

@@ -874,9 +874,9 @@ class BackendClient:
data = message.get('data', {}) data = message.get('data', {})
# 判断是拼多多登录参数还是普通Cookie # 判断是拼多多登录参数还是普通Cookie
if platform_name == "拼多多" and content == "拼多多登录" and data.get('login_params'): if platform_name == "拼多多" and ("拼多多登录" in content) and data.get('login_params'):
# 拼多多登录参数模式 - 传递完整的消息JSON给处理器 # 拼多多登录参数模式 - 传递完整的消息JSON给处理器
print(f"收到拼多多登录参数: 平台={platform_name}, 店铺={store_id}") print(f"收到拼多多登录参数: 平台={platform_name}, 店铺={store_id}, 类型={content}")
if self.login_callback: if self.login_callback:
# 传递完整的JSON消息让拼多多处理器来解析login_params # 传递完整的JSON消息让拼多多处理器来解析login_params
import json import json

View File

@@ -359,6 +359,8 @@ class WebSocketManager:
# 详细的结果分析 # 详细的结果分析
if result == "need_verification_code": if result == "need_verification_code":
self._log("✅ [PDD] 登录流程正常,已发送验证码需求通知给后端", "SUCCESS") self._log("✅ [PDD] 登录流程正常,已发送验证码需求通知给后端", "SUCCESS")
elif result == "verification_code_error":
self._log("⚠️ [PDD] 验证码错误,已发送错误通知给后端", "WARNING")
elif result: elif result:
self._log("✅ [PDD] 登录成功,平台连接已建立", "SUCCESS") self._log("✅ [PDD] 登录成功,平台连接已建立", "SUCCESS")
else: else:
@@ -371,13 +373,13 @@ class WebSocketManager:
self._log(f"📊 start_with_cookies 执行结果: {result}", "DEBUG") self._log(f"📊 start_with_cookies 执行结果: {result}", "DEBUG")
# 根据实际登录结果上报状态给后端 # 根据实际登录结果上报状态给后端
if self.backend_client and result != "need_verification_code": if self.backend_client and result not in ["need_verification_code", "verification_code_error", "login_failure"]:
# 如果返回need_verification_code说明验证码通知已经在PddLogin中发送了不需要重复发送 # 如果是特殊状态,说明通知已经在PddLogin中发送了不需要重复发送
try: try:
message = { message = {
"type": "connect_message", "type": "connect_message",
"store_id": store_id, "store_id": store_id,
"status": bool(result) if result != "need_verification_code" else False "status": bool(result)
} }
self.backend_client.send_message(message) self.backend_client.send_message(message)
status_text = "成功" if result else "失败" status_text = "成功" if result else "失败"
@@ -386,6 +388,10 @@ class WebSocketManager:
self._log(f"上报拼多多平台连接状态失败: {send_e}", "ERROR") self._log(f"上报拼多多平台连接状态失败: {send_e}", "ERROR")
elif result == "need_verification_code": elif result == "need_verification_code":
self._log("需要验证码验证码通知已由PddLogin发送等待后端重新下发登录参数", "INFO") self._log("需要验证码验证码通知已由PddLogin发送等待后端重新下发登录参数", "INFO")
elif result == "verification_code_error":
self._log("验证码错误错误通知已由PddLogin发送等待后端处理", "INFO")
elif result == "login_failure":
self._log("登录失败失败通知已由PddLogin发送等待后端处理", "INFO")
return result return result