From 22122660dfabf4b22c3f879a71b4535fb61270e4 Mon Sep 17 00:00:00 2001 From: jjz <3082705704@qq.com> Date: Sat, 13 Sep 2025 15:19:39 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=BC=E5=A4=9A=E5=A4=9A=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E5=8A=A0=E5=AF=86=E5=8F=82=E6=95=B0=E7=99=BB=E5=BD=95+?= =?UTF-8?q?=E4=BA=AC=E4=B8=9C=E5=AE=A2=E6=9C=8D=E8=BD=AC=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Utils/Pdd/PddUtils.py | 222 +++++++++++++++++++++++++++++++++ WebSocket/BackendClient.py | 85 ++++++++++++- WebSocket/backend_singleton.py | 19 ++- config.py | 1 + 4 files changed, 319 insertions(+), 8 deletions(-) diff --git a/Utils/Pdd/PddUtils.py b/Utils/Pdd/PddUtils.py index 8716b3d..e6fd824 100644 --- a/Utils/Pdd/PddUtils.py +++ b/Utils/Pdd/PddUtils.py @@ -958,6 +958,33 @@ class PddListenerForGUI: self._log(f"❌ 监听启动过程中出现严重错误: {str(e)}", "ERROR") return False + async def start_with_login_params(self, store_id: str, login_params: str): + """使用后端下发的登录参数执行登录并启动监听""" + try: + self._log("🔵 [PDD] 收到后端登录参数,开始执行登录获取cookies", "INFO") + + # 1. 解析登录参数 + params_dict = self._parse_login_params(login_params) + if not params_dict: + self._log("❌ [PDD] 登录参数解析失败", "ERROR") + return False + + # 2. 执行登录获取Cookie + cookies = await self._execute_pdd_login(params_dict) + if not cookies: + self._log("❌ [PDD] 登录失败,无法获取cookies", "ERROR") + return False + + # 3. 使用获取的Cookie继续原有流程 + self._log("✅ [PDD] 登录成功,使用获取的cookies连接平台", "SUCCESS") + return await self.start_with_cookies(store_id, cookies) + + except Exception as e: + self._log(f"❌ [PDD] 使用登录参数启动失败: {str(e)}", "ERROR") + import traceback + self._log(f"错误详情: {traceback.format_exc()}", "DEBUG") + return False + async def start_with_cookies(self, store_id: str, cookies: str): """使用下发的cookies与store_id直接建立PDD平台WS并开始监听""" try: @@ -1077,6 +1104,201 @@ class PddListenerForGUI: self.main_thread.is_alive() and self.startup_success) + def _parse_login_params(self, login_params_str: str) -> dict: + """解析后端下发的登录参数""" + try: + import json + data = json.loads(login_params_str) + params = data.get("data", {}).get("login_params", {}) + self._log(f"✅ [PDD] 登录参数解析成功: username={params.get('username', 'N/A')}", "INFO") + return params + except Exception as e: + self._log(f"❌ [PDD] 解析登录参数失败: {e}", "ERROR") + return {} + + async def _execute_pdd_login(self, login_params: dict) -> str: + """使用后端参数执行拼多多登录""" + try: + # 1. 构造登录请求 + login_request = self._build_login_request(login_params) + if not login_request: + return "" + + # 2. 发送登录请求 + response_data = await self._send_login_request(login_request) + if not response_data: + return "" + + # 3. 处理登录响应 + if "需要验证图形验证码" in str(response_data): + self._log("⚠️ [PDD] 登录需要验证码,暂不支持自动处理", "WARNING") + return "" + elif response_data.get("success", False): + cookies = response_data.get("cookies", {}) + if cookies: + # 将cookies字典转换为字符串格式,与原有逻辑兼容 + import json + cookies_str = json.dumps(cookies) + self._log(f"✅ [PDD] 登录成功,获取到cookies: {len(cookies)} 个字段", "SUCCESS") + return cookies_str + else: + self._log("❌ [PDD] 登录成功但未获取到cookies", "ERROR") + return "" + else: + error_msg = response_data.get("errorMsg", "登录失败") + self._log(f"❌ [PDD] 登录失败: {error_msg}", "ERROR") + return "" + + except Exception as e: + self._log(f"❌ [PDD] 执行登录失败: {e}", "ERROR") + import traceback + self._log(f"错误详情: {traceback.format_exc()}", "DEBUG") + return "" + + def _build_login_request(self, login_params: dict) -> dict: + """构造登录请求体(复用pdd_login/login.py的结构)""" + try: + username = login_params.get("username", "") + password = login_params.get("password", "") # 后端已加密 + anti_content = login_params.get("anti_content", "") + risk_sign = login_params.get("risk_sign", "") + timestamp = login_params.get("timestamp", int(time.time() * 1000)) + + if not all([username, password, anti_content, risk_sign]): + self._log("❌ [PDD] 登录参数不完整", "ERROR") + return {} + + import random + + # 构造请求头 + headers = { + "authority": "mms.pinduoduo.com", + "accept": "*/*", + "accept-language": "zh-CN,zh;q=0.9", + "cache-control": "no-cache", + "content-type": "application/json;charset=UTF-8", + "origin": "https://mms.pinduoduo.com", + "pragma": "no-cache", + "referer": "https://mms.pinduoduo.com/", + "sec-ch-ua": '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"Windows"', + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-origin", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "anti-content": anti_content # 后端提供的关键参数 + } + + # 构造请求体(完全复用pdd_login/login.py的结构) + payload = { + "username": username, + "password": password, # 后端已加密 + "passwordEncrypt": True, + "verificationCode": "", + "mobileVerifyCode": "", + "sign": "", + "touchevent": { + "mobileInputEditStartTime": timestamp - random.randint(10000, 20000), + "mobileInputEditFinishTime": timestamp - random.randint(8000, 15000), + "mobileInputKeyboardEvent": "0|1|1|-6366-6942-142", + "passwordInputEditStartTime": timestamp - random.randint(5000, 10000), + "passwordInputEditFinishTime": timestamp - random.randint(3000, 8000), + "passwordInputKeyboardEvent": "0|1|1|195-154-755-477", + "captureInputEditStartTime": "", + "captureInputEditFinishTime": "", + "captureInputKeyboardEvent": "", + "loginButtonTouchPoint": "1263,586", + "loginButtonClickTime": timestamp - random.randint(100, 1000) + }, + "fingerprint": { + "innerHeight": 906, + "innerWidth": 1707, + "devicePixelRatio": 1.5, + "availHeight": 1027, + "availWidth": 1707, + "height": 1067, + "width": 1707, + "colorDepth": 24, + "locationHref": "https://mms.pinduoduo.com/login/sso?platform=wholesale&redirectUrl=htt", + "clientWidth": 1707, + "clientHeight": 906, + "offsetWidth": 1707, + "offsetHeight": 906, + "scrollWidth": 2882, + "scrollHeight": 906, + "navigator": { + "appCodeName": "Mozilla", + "appName": "Netscape", + "hardwareConcurrency": 16, + "language": "zh-CN", + "cookieEnabled": True, + "platform": "Win32", + "doNotTrack": True, + "ua": headers["user-agent"], + "vendor": "Google Inc.", + "product": "Gecko", + "productSub": "20030107", + "mimeTypes": "f5a1111231f589322da33fb59b56946b4043e092", + "plugins": "387b918f593d4d8d6bfa647c07e108afbd7a6223" + }, + "referer": "", + "timezoneOffset": -480 + }, + "riskSign": risk_sign, # 后端已加密 + "timestamp": timestamp, + "crawlerInfo": anti_content # 后端提供 + } + + self._log("✅ [PDD] 登录请求构造成功", "INFO") + return { + "url": "https://mms.pinduoduo.com/janus/api/auth", + "headers": headers, + "payload": payload + } + + except Exception as e: + self._log(f"❌ [PDD] 构造登录请求失败: {e}", "ERROR") + return {} + + async def _send_login_request(self, login_request: dict) -> dict: + """发送登录请求""" + try: + import requests + import asyncio + + url = login_request["url"] + headers = login_request["headers"] + payload = login_request["payload"] + + # 使用线程池执行同步请求 + def _send_request(): + response = requests.post(url, headers=headers, json=payload, timeout=30) + return response + + # 在事件循环中执行 + loop = asyncio.get_event_loop() + response = await loop.run_in_executor(None, _send_request) + + self._log(f"✅ [PDD] 登录请求发送完成,状态码: {response.status_code}", "INFO") + + if response.status_code == 200: + result = response.json() + cookies = response.cookies.get_dict() + return { + "success": result.get("success", False), + "errorMsg": result.get("errorMsg", ""), + "cookies": cookies, + "response_text": response.text + } + else: + self._log(f"❌ [PDD] 登录请求失败,HTTP状态码: {response.status_code}", "ERROR") + return {} + + except Exception as e: + self._log(f"❌ [PDD] 发送登录请求异常: {e}", "ERROR") + return {} + def get_status(self) -> Dict[str, Any]: return { "running": self.running, diff --git a/WebSocket/BackendClient.py b/WebSocket/BackendClient.py index ad7a470..1d14ff4 100644 --- a/WebSocket/BackendClient.py +++ b/WebSocket/BackendClient.py @@ -657,6 +657,67 @@ class BackendClient: except Exception as e: print(f"[PDD Transfer] 拼多多转接失败: {e}") + def _transfer_to_jd(self, customer_service_id: str, user_id: str, store_id: str): + """执行京东平台转接操作""" + try: + from Utils.JD.JdUtils import WebsocketManager as JDWSManager + jd_mgr = JDWSManager() + shop_key = f"京东:{store_id}" + entry = jd_mgr.get_connection(shop_key) + + if not entry: + print(f"[JD Transfer] 未找到京东连接: {shop_key}") + return + + platform_info = entry.get('platform', {}) + ws = platform_info.get('ws') + aid = platform_info.get('aid') + pin_zj = platform_info.get('pin_zj') + loop = platform_info.get('loop') + + print(f"[JD Transfer] 找到京东连接,准备执行转接: user_id={user_id}, cs_id={customer_service_id}") + print(f"[JD Transfer] 连接参数: has_ws={bool(ws)}, aid={aid}, pin_zj={pin_zj}, has_loop={bool(loop)}") + + if ws and aid and pin_zj and loop: + # 在事件循环中执行转接 + def transfer_in_loop(): + try: + # 导入FixJdCookie类来调用转接方法 + from Utils.JD.JdUtils import FixJdCookie + jd_utils = FixJdCookie() + + # 在事件循环中执行转接 + future = asyncio.run_coroutine_threadsafe( + jd_utils.transfer_customer(ws, aid, user_id, pin_zj, customer_service_id), + loop + ) + + # 等待转接结果 + try: + result = future.result(timeout=10) # 京东转接超时时间 + if result: + print(f"[JD Transfer] ✅ 转接成功: user_id={user_id} -> cs_id={customer_service_id}") + else: + print(f"[JD Transfer] ❌ 转接失败: user_id={user_id}") + except Exception as fe: + print(f"[JD Transfer] 转接执行失败: {fe}") + except Exception as e: + print(f"[JD Transfer] 转接过程异常: {e}") + + # 在新线程中执行转接操作 + import threading + transfer_thread = threading.Thread(target=transfer_in_loop, daemon=True) + transfer_thread.start() + + else: + print("[JD Transfer] 条件不足,未转接:", + { + 'has_ws': bool(ws), 'has_aid': bool(aid), 'has_pin_zj': bool(pin_zj), + 'has_loop': bool(loop) + }) + except Exception as e: + print(f"[JD Transfer] 京东转接失败: {e}") + def _handle_transfer(self, message: Dict[str, Any]): """处理转接消息""" # 新版转接消息格式: {"type": "transfer", "content": "客服ID", "receiver": {"id": "用户ID"}, "store_id": "店铺ID"} @@ -672,8 +733,7 @@ class BackendClient: platform_type = self._get_platform_by_store_id(store_id) if platform_type == "京东": - # 京东转接逻辑 - 待实现 - print(f"[JD Transfer] 京东平台转接功能待实现") + self._transfer_to_jd(customer_service_id, user_id, store_id) elif platform_type == "抖音": # 抖音转接逻辑 - 待实现 print(f"[DY Transfer] 抖音平台转接功能待实现") @@ -711,13 +771,26 @@ class BackendClient: self.get_store() def _handle_login(self, message: Dict[str, Any]): - """处理平台登录消息(新版:type=login, cookies, store_id, platform_name)""" + """处理平台登录消息(新版:type=login, cookies/login_params, store_id, platform_name)""" cookies = message.get('cookies', '') store_id = message.get('store_id', '') platform_name = message.get('platform_name', '') - print(f"收到登录指令: 平台={platform_name}, 店铺={store_id}, cookies_len={len(cookies) if cookies else 0}") - if self.login_callback: - self.login_callback(platform_name, store_id, cookies) + content = message.get('content', '') + data = message.get('data', {}) + + # 判断是拼多多登录参数还是普通Cookie + if platform_name == "拼多多" and content == "pdd_login_params" and data.get('login_params'): + # 拼多多登录参数模式 + import json + login_params_str = json.dumps({"data": data}) + print(f"收到拼多多登录参数: 平台={platform_name}, 店铺={store_id}, params_len={len(login_params_str)}") + if self.login_callback: + self.login_callback(platform_name, store_id, login_params_str) + else: + # 普通Cookie模式 + print(f"收到登录指令: 平台={platform_name}, 店铺={store_id}, cookies_len={len(cookies) if cookies else 0}") + if self.login_callback: + self.login_callback(platform_name, store_id, cookies) def _handle_error_message(self, message: Dict[str, Any]): """处理错误消息""" diff --git a/WebSocket/backend_singleton.py b/WebSocket/backend_singleton.py index bbd8ba5..b8ceed4 100644 --- a/WebSocket/backend_singleton.py +++ b/WebSocket/backend_singleton.py @@ -257,13 +257,19 @@ class WebSocketManager: except Exception as e: self._log(f"启动千牛平台监听失败: {e}", "ERROR") - def _start_pdd_listener(self, store_id: str, cookies: str): + def _start_pdd_listener(self, store_id: str, data: str): """启动拼多多平台监听""" try: def _runner(): try: listener = PDDListenerForGUI_WS(log_callback=self._log) - result = asyncio.run(listener.start_with_cookies(store_id=store_id, cookies=cookies)) + # 判断是登录参数还是Cookie + if self._is_pdd_login_params(data): + # 使用登录参数启动 + result = asyncio.run(listener.start_with_login_params(store_id=store_id, login_params=data)) + else: + # 使用Cookie启动(兼容旧方式) + result = asyncio.run(listener.start_with_cookies(store_id=store_id, cookies=data)) return result except Exception as e: self._log(f"拼多多监听器运行异常: {e}", "ERROR") @@ -312,6 +318,15 @@ class WebSocketManager: except Exception as send_e: self._log(f"失败状态下报拼多多平台连接状态也失败: {send_e}", "ERROR") + def _is_pdd_login_params(self, data: str) -> bool: + """判断是否为拼多多登录参数""" + try: + import json + parsed_data = json.loads(data) + return (parsed_data.get("data", {}).get("login_params") is not None) + except: + return False + def send_message(self, message: dict): """发送消息到后端""" if self.backend_client and self.backend_client.is_connected: diff --git a/config.py b/config.py index 40f3ace..7096687 100644 --- a/config.py +++ b/config.py @@ -10,6 +10,7 @@ import json # 用于将令牌保存为 JSON 格式 # 后端服务器配置 # BACKEND_HOST = "192.168.5.155" BACKEND_HOST = "test.shuidrop.com" +# BACKEND_PORT = "8000" BACKEND_PORT = "" BACKEND_BASE_URL = f"http://{BACKEND_HOST}:{BACKEND_PORT}" # BACKEND_WS_URL = f"ws://{BACKEND_HOST}:{BACKEND_PORT}"