From faef5d2316496b6465a89455d72554a55af881a5 Mon Sep 17 00:00:00 2001 From: jjz <3082705704@qq.com> Date: Tue, 16 Sep 2025 09:05:11 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=9E=E7=8E=B0=E4=B8=8B?= =?UTF-8?q?=E5=8F=91=E9=9C=80=E8=A6=81=E9=AA=8C=E8=AF=81=E7=A0=81=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E7=BB=99=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 | 2044 +++++++++++++++++++++++++++++--- WebSocket/BackendClient.py | 12 +- WebSocket/backend_singleton.py | 103 +- config.py | 12 +- 4 files changed, 1942 insertions(+), 229 deletions(-) diff --git a/Utils/Pdd/PddUtils.py b/Utils/Pdd/PddUtils.py index e3a1a64..a3ab037 100644 --- a/Utils/Pdd/PddUtils.py +++ b/Utils/Pdd/PddUtils.py @@ -22,9 +22,1834 @@ import execjs from datetime import datetime from typing import Dict, Any, Optional, Callable +# 新增导入,用于登录功能 +import PIL, numpy as np, cv2 +from Crypto.PublicKey import RSA +from Crypto.Cipher import PKCS1_v1_5, AES +from Crypto.Util.Padding import pad +from pathlib import Path +from loguru import logger + from Utils.message_models import PlatformMessage +# ===== 登录相关类集成开始 ===== + +class Track: + """滑块轨迹生成类""" + + @staticmethod + def get_track(distance): + distance = int(distance) + random.randint(7, 11) + 0.8 + track_list = [ + [ + 0.8, + 178.4, + 1733388719067 + ], + [ + 4.8, + 179.4, + 1733388719169 + ], + [ + 6.8, + 179.4, + 1733388719184 + ], + [ + 8.8, + 179.4, + 1733388719250 + ], + [ + 8.8, + 179.4, + 1733388719266 + ], + [ + 9.8, + 180.4, + 1733388719762 + ], + [ + 10.8, + 180.4, + 1733388719914 + ], + [ + 11.8, + 180.4, + 1733388719930 + ], + [ + 11.8, + 180.4, + 1733388719954 + ], + [ + 12.8, + 180.4, + 1733388719982 + ], + [ + 12.8, + 180.4, + 1733388720030 + ], + [ + 13.8, + 180.4, + 1733388720058 + ], + [ + 14.8, + 180.4, + 1733388720090 + ], + [ + 15.8, + 180.4, + 1733388720110 + ], + [ + 16.8, + 180.4, + 1733388720170 + ], + [ + 17.8, + 180.4, + 1733388720202 + ], + [ + 19.8, + 180.4, + 1733388720218 + ], + [ + 22.8, + 181.4, + 1733388720258 + ], + [ + 23.8, + 181.4, + 1733388720319 + ], + [ + 24.8, + 181.4, + 1733388720428 + ], + [ + 24.8, + 182.4, + 1733388720458 + ], + [ + 26.8, + 182.4, + 1733388720478 + ], + [ + 28.8, + 182.4, + 1733388720495 + ], + [ + 30.8, + 182.4, + 1733388720511 + ], + [ + 32.8, + 183.4, + 1733388720527 + ], + [ + 33.8, + 183.4, + 1733388720558 + ], + [ + 35.8, + 183.4, + 1733388720574 + ], + [ + 36.8, + 184.4, + 1733388720591 + ], + [ + 38.8, + 184.4, + 1733388720619 + ], + [ + 39.8, + 184.4, + 1733388720662 + ], + [ + 40.8, + 184.4, + 1733388720686 + ], + [ + 41.8, + 184.4, + 1733388720710 + ], + [ + 42.8, + 184.4, + 1733388720742 + ], + [ + 43.8, + 184.4, + 1733388720758 + ], + [ + 45.8, + 184.4, + 1733388720783 + ], + [ + 46.8, + 184.4, + 1733388720798 + ], + [ + 48.8, + 184.4, + 1733388720831 + ], + [ + 49.8, + 184.4, + 1733388720850 + ], + [ + 50.8, + 184.4, + 1733388720874 + ], + [ + 52.8, + 184.4, + 1733388720922 + ], + [ + 53.8, + 184.4, + 1733388720958 + ], + [ + 55.8, + 184.4, + 1733388720974 + ], + [ + 55.8, + 184.4, + 1733388720990 + ], + [ + 56.8, + 184.4, + 1733388721010 + ], + [ + 57.8, + 184.4, + 1733388721051 + ], + [ + 58.8, + 184.4, + 1733388721074 + ], + [ + 58.8, + 185.4, + 1733388721090 + ], + [ + 59.8, + 185.4, + 1733388721106 + ], + [ + 60.8, + 185.4, + 1733388721214 + ], + [ + 60.8, + 185.4, + 1733388721270 + ], + [ + 61.8, + 185.4, + 1733388721361 + ], + [ + 63.8, + 186.4, + 1733388721374 + ], + [ + 68.8, + 186.4, + 1733388721390 + ], + [ + 71.8, + 187.4, + 1733388721406 + ], + [ + 72.8, + 187.4, + 1733388721422 + ], + [ + 73.8, + 187.4, + 1733388721442 + ], + [ + 74.8, + 187.4, + 1733388721602 + ], + [ + 75.8, + 187.4, + 1733388721650 + ], + [ + 79.8, + 187.4, + 1733388721674 + ], + [ + 81.8, + 187.4, + 1733388721690 + ], + [ + 83.8, + 188.4, + 1733388721726 + ], + [ + 83.8, + 188.4, + 1733388722055 + ], + [ + 91.8, + 188.4, + 1733388722241 + ], + [ + 92.8, + 188.4, + 1733388722346 + ], + [ + 92.8, + 188.4, + 1733388722494 + ], + [ + 94.8, + 188.4, + 1733388722511 + ], + [ + 97.8, + 188.4, + 1733388722527 + ], + [ + 101.8, + 189.4, + 1733388722543 + ], + [ + 102.8, + 189.4, + 1733388722566 + ], + [ + 103.8, + 189.4, + 1733388722666 + ], + [ + 104.8, + 189.4, + 1733388722722 + ], + [ + 106.8, + 190.4, + 1733388722742 + ], + [ + 108.8, + 190.4, + 1733388722759 + ], + [ + 112.8, + 190.4, + 1733388722775 + ], + [ + 115.8, + 191.4, + 1733388722791 + ], + [ + 116.8, + 191.4, + 1733388722807 + ], + [ + 118.8, + 192.4, + 1733388722911 + ], + [ + 119.8, + 192.4, + 1733388722931 + ], + [ + 120.8, + 192.4, + 1733388722962 + ], + [ + 123.8, + 192.4, + 1733388722987 + ], + [ + 124.8, + 192.4, + 1733388723006 + ], + [ + 125.8, + 192.4, + 1733388723023 + ], + [ + 127.8, + 192.4, + 1733388723098 + ], + [ + 128.8, + 192.4, + 1733388723186 + ], + [ + 129.8, + 193.4, + 1733388723258 + ], + [ + 132.8, + 193.4, + 1733388723278 + ], + [ + 135.8, + 193.4, + 1733388723294 + ], + [ + 137.8, + 194.4, + 1733388723318 + ], + [ + 138.8, + 194.4, + 1733388723562 + ], + [ + 140.8, + 194.4, + 1733388723582 + ], + [ + 144.8, + 195.4, + 1733388723599 + ], + [ + 145.8, + 195.4, + 1733388723622 + ], + [ + 147.8, + 196.4, + 1733388723662 + ], + [ + 148.8, + 196.4, + 1733388723678 + ], + [ + 149.8, + 196.4, + 1733388723703 + ], + [ + 152.8, + 196.4, + 1733388723719 + ], + [ + 153.8, + 196.4, + 1733388723774 + ], + [ + 154.8, + 196.4, + 1733388723846 + ], + [ + 155.8, + 196.4, + 1733388723886 + ], + [ + 157.8, + 196.4, + 1733388723903 + ], + [ + 161.8, + 197.4, + 1733388723918 + ], + [ + 166.8, + 197.4, + 1733388723934 + ], + [ + 167.8, + 197.4, + 1733388723998 + ], + [ + 168.8, + 197.4, + 1733388724026 + ], + [ + 168.8, + 197.4, + 1733388724123 + ], + [ + 171.8, + 198.4, + 1733388724139 + ], + [ + 175.8, + 199.4, + 1733388724154 + ], + [ + 177.8, + 199.4, + 1733388724170 + ], + [ + 179.8, + 199.4, + 1733388724186 + ], + [ + 181.8, + 199.4, + 1733388724202 + ], + [ + 184.8, + 199.4, + 1733388724218 + ], + [ + 185.8, + 199.4, + 1733388724234 + ], + [ + 186.8, + 199.4, + 1733388724255 + ], + [ + 187.8, + 199.4, + 1733388724278 + ], + [ + 188.8, + 199.4, + 1733388724294 + ], + [ + 188.8, + 199.4, + 1733388724326 + ], + [ + 189.8, + 199.4, + 1733388724374 + ], + [ + 191.8, + 199.4, + 1733388724399 + ], + [ + 192.8, + 200.4, + 1733388724444 + ], + [ + 193.8, + 200.4, + 1733388724486 + ], + [ + 194.8, + 200.4, + 1733388724834 + ], + [ + 195.8, + 200.4, + 1733388724850 + ], + [ + 196.8, + 200.4, + 1733388724866 + ], + [ + 198.8, + 200.4, + 1733388724882 + ], + [ + 199.8, + 200.4, + 1733388724898 + ], + [ + 200.8, + 201.4, + 1733388725242 + ], + [ + 204.8, + 201.4, + 1733388725276 + ], + [ + 206.8, + 201.4, + 1733388725301 + ], + [ + 207.8, + 201.4, + 1733388725332 + ], + [ + 208.8, + 201.4, + 1733388725522 + ], + [ + 211.8, + 201.4, + 1733388725547 + ], + [ + 212.8, + 201.4, + 1733388725586 + ], + [ + 212.8, + 201.4, + 1733388725838 + ], + [ + 214.8, + 201.4, + 1733388725854 + ], + [ + 219.8, + 202.4, + 1733388725878 + ], + [ + 220.8, + 202.4, + 1733388725894 + ], + [ + 222.8, + 202.4, + 1733388726427 + ], + [ + 227.8, + 202.4, + 1733388726446 + ], + [ + 228.8, + 202.4, + 1733388726462 + ], + [ + 230.8, + 203.4, + 1733388726642 + ], + [ + 232.8, + 203.4, + 1733388726658 + ], + [ + 234.8, + 203.4, + 1733388726675 + ], + [ + 236.8, + 203.4, + 1733388726690 + ], + [ + 237.8, + 203.4, + 1733388726742 + ], + [ + 238.8, + 203.4, + 1733388726934 + ], + [ + 240.8, + 203.4, + 1733388726950 + ], + [ + 243.8, + 203.4, + 1733388726966 + ], + [ + 245.8, + 203.4, + 1733388726982 + ], + [ + 247.8, + 203.4, + 1733388727118 + ], + [ + 248.8, + 203.4, + 1733388727174 + ], + [ + 248.8, + 204.4, + 1733388727190 + ], + [ + 250.8, + 204.4, + 1733388727214 + ], + [ + 252.8, + 204.4, + 1733388727286 + ], + [ + 252.8, + 204.4, + 1733388727302 + ], + [ + 253.8, + 204.4, + 1733388727322 + ], + [ + 254.8, + 204.4, + 1733388727358 + ], + [ + 256.8, + 204.4, + 1733388727398 + ] + ] + # 检查value是否在轨迹的x值中 + for trajectory in track_list: + if trajectory[0] == distance: + # 如果找到,截取从轨迹开始到该点的子数组 + return [t for t in track_list if t[0] <= distance] + + # 如果value不在x值中,找到最接近value的x值 + closest_x = None + min_diff = float('inf') + for trajectory in track_list: + if abs(trajectory[0] - distance) < min_diff: + min_diff = abs(trajectory[0] - distance) + closest_x = trajectory[0] + + # 截取从轨迹开始到最接近的x值的子数组 + result = [t for t in track_list if t[0] <= closest_x] + result[-1][0] = distance + return result + + +class ImgDistance: + """滑块图像识别距离计算类""" + + def __init__(self, bg, tp): + self.bg = bg + self.tp = tp + + @staticmethod + def imshow(img, winname='test', delay=0): + """cv2展示图片""" + cv2.imshow(winname, img) + cv2.waitKey(delay) + cv2.destroyAllWindows() + + @staticmethod + def pil_to_cv2(img): + """ + pil转cv2图片 + :param img: pil图像, + :return: cv2图像, + """ + img = cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR) + return img + + @staticmethod + def bytes_to_cv2(img): + """ + 二进制图片转cv2 + :param img: 二进制图片数据, + :return: cv2图像, + """ + # 将图片字节码bytes, 转换成一维的numpy数组到缓存中 + img_buffer_np = np.frombuffer(img, dtype=np.uint8) + # 从指定的内存缓存中读取一维numpy数据, 并把数据转换(解码)成图像矩阵格式 + img_np = cv2.imdecode(img_buffer_np, 1) + return img_np + + def cv2_open(self, img, flag=None): + """ + 统一输出图片格式为cv2图像, + :param img: + :param flag: 颜色空间转换类型, default: None + eg: cv2.COLOR_BGR2GRAY(灰度图) + :return: cv2图像, + """ + if isinstance(img, bytes): + img = self.bytes_to_cv2(img) + elif isinstance(img, (str, Path)): + img = cv2.imread(str(img)) + elif isinstance(img, np.ndarray): + img = img + elif isinstance(img, PIL.Image.Image): + img = self.pil_to_cv2(img) + else: + raise ValueError(f'输入的图片类型无法解析: {type(img)}') + if flag is not None: + img = cv2.cvtColor(img, flag) + return img + + def get_distance(self, bg, tp, im_show=False, save_path=None): + """ + :param bg: 背景图路径或Path对象或图片二进制 + eg: 'assets/bg.jpg' + Path('assets/bg.jpg') + :param tp: 缺口图路径或Path对象或图片二进制 + eg: 'assets/tp.jpg' + Path('assets/tp.jpg') + :param im_show: 是否显示结果, ; default: False + :param save_path: 保存路径, ; default: None + :return: 缺口位置 + """ + # 读取图片 + bg_img = self.cv2_open(bg) + tp_gray = self.cv2_open(tp, flag=cv2.COLOR_BGR2GRAY) + + # 金字塔均值漂移 + bg_shift = cv2.pyrMeanShiftFiltering(bg_img, 5, 50) + + # 边缘检测 + tp_gray = cv2.Canny(tp_gray, 255, 255) + bg_gray = cv2.Canny(bg_shift, 255, 255) + + # 目标匹配 + result = cv2.matchTemplate(bg_gray, tp_gray, cv2.TM_CCOEFF_NORMED) + # 解析匹配结果 + min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(result) + + distance = max_loc[0] + if save_path or im_show: + # 需要绘制的方框高度和宽度 + tp_height, tp_width = tp_gray.shape[:2] + # 矩形左上角点位置 + x, y = max_loc + # 矩形右下角点位置 + _x, _y = x + tp_width, y + tp_height + # 绘制矩形 + bg_img = self.cv2_open(bg) + cv2.rectangle(bg_img, (x, y), (_x, _y), (0, 0, 255), 2) + # 保存缺口识别结果到背景图 + if save_path: + cv2.imwrite(save_path, bg_img) + # 显示缺口识别结果 + if im_show: + self.imshow(bg_img) + return max_loc[0] + + @staticmethod + def decrypt_img(imgstr): + res = [] + s = [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 3, -1, 20, -1, 17, + 8, + -1, 30, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 12, 22, 10, -1, -1, 15, 14, 6, -1, 5, -1, -1, 7, + 18, + -1, 25, 9, -1, 28, -1, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 21, -1, 31, 13, 16, -1, 26, -1, 27, + -1, 0, + 19, -1, 11, 4, -1, -1, 23, -1, 29, -1, -1, -1, -1, -1, -1] + for r in range(0, len(imgstr), 8): + o = s[ord(imgstr[r])] + i = s[ord(imgstr[r + 1])] + a = s[ord(imgstr[r + 2])] + c = s[ord(imgstr[r + 3])] + u = s[ord(imgstr[r + 4])] + d = s[ord(imgstr[r + 5])] + f = s[ord(imgstr[r + 6])] + p = (31 & o) << 3 | (31 & i) >> 2 + h = (3 & i) << 6 | (31 & a) << 1 | (31 & c) >> 4 + m = (15 & c) << 4 | (31 & u) >> 1 + v = (1 & u) << 7 | (31 & d) << 2 | (31 & f) >> 3 + g = (7 & f) << 5 | 31 & s[ord(imgstr[r + 7])] + res.append(chr(((31 & p) << 3 | h >> 5))) + res.append(chr((31 & h) << 3 | m >> 5)) + res.append(chr((31 & m) << 3 | v >> 5)) + res.append(chr((31 & v) << 3 | g >> 5)) + res.append(chr((31 & g) << 3 | p >> 5)) + y = ''.join(res) + for c in ['#', '@?', '*&%', '<$|>']: + y = y.replace(c, '') + return y.replace('data:image/png;base64,', '') + + def main(self, save_path=None): + return self.get_distance( + bg=base64.urlsafe_b64decode(self.decrypt_img(self.bg)), + tp=base64.urlsafe_b64decode(self.decrypt_img(self.tp)), + save_path=save_path + ) + + +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 + + def gzip_compress(self, data): + """压缩数据""" + try: + if self.ctx: + result = self.ctx.call("gzipCompress", data) + logger.info("成功压缩数据") + return result + except Exception as e: + logger.error(f"调用gzipCompress函数失败: {e}") + + # fallback处理:简单返回原数据 + logger.warning("使用fallback处理,返回原数据") + return data + + +class EncryptTool: + """加密工具类""" + + @staticmethod + def aes_encrypt(text, key, iv): + cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8')) + encrypted_text = cipher.encrypt(pad(text.encode('utf-8'), AES.block_size)) + return base64.b64encode(encrypted_text).decode('utf-8') + + @staticmethod + def rsa_encrypt(data): + public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6zHXNom934tsG9SC73vAUv99bIuSRVaLsuTMY+OL6aS6eB7AuNoU+m9gPCrI7aFrT7CSiKTJ47DNwCNZO52AlLzvB6TjdUwuIXWpinE8VCsYAZgOCrx+mK9Sy0OuwnqNj5D2wUdGoN0nxbl1q2akeAa18A/iBpiXx0SZQexbEowIDAQAB" + rsa_key = RSA.import_key(base64.b64decode(public_key)) # 导入读取到的公钥 + cipher = PKCS1_v1_5.new(rsa_key) # 生成对象 + cipher_text = base64.b64encode(cipher.encrypt(data.encode(encoding="utf-8"))) + return cipher_text.decode("utf-8") + + @staticmethod + def get_key_iv(salt): + t = {'aes_key': r"bN3%cH2$H1@*jCo$", 'aes_iv': r"gl3-w^dN)3#h6E1%"} + if not salt or 9 != len(salt): + return t + else: + n = salt[:1] + r = salt[1:] + o = r[:4] + i = r[4:] + a = list(i) + s = t['aes_key'][:8] if n in ["a", "b"] else t['aes_iv'][:8] + c = 'aes_key' if n in ["a", "b"] else 'aes_iv' + l = '' + if n == 'c': + t[c] = s + r + elif n == 'd': + for u in range(4): + l += a[int(o[u])] + t[c] = s + l + i + return t + + +class PddLogin(AutiContent): + """拼多多登录核心类""" + + def __init__(self, log_callback=None): + super().__init__() + self.login_api = "https://mms.pinduoduo.com/janus/api/auth" + self.headers = { + "authority": "apiv2.pinduoduo.net", + "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": "cross-site", + "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" + } + self.verify_auth_token = None + self.cookies = dict() + self.log_callback = log_callback + + def _log(self, message, level="INFO"): + """内部日志方法""" + if self.log_callback: + self.log_callback(message, level) + else: + print(f"[{level}] {message}") + + # 滑块参数生成 + def captcha_collect(self, salt, track_list): + ts = int(round(time.time() * 1000)) + captcha_collect_data = json.dumps({ + "v": "a", + "ts": ts + random.randint(5700, 5950), + "t1": ts, + "t2": ts + random.randint(15700, 19950), + "tp": 3, + "ua": self.headers["user-agent"], + "rf": "", + "platform": 1, + "hl": "000000000001010", + "sc": { + "w": 1536, + "h": 824 + }, + "ihs": 1, + "imageSize": { + "width": 272, + "height": 198 + }, + "del": [ + [ + 0.8, + 177.4, + 1733388719033 + ] + ], + "mel": track_list, + "uel": [track_list[-1]], + "mell": [ + [ + [ + 250.8, + 187.4, + 1733388711350 + ], + [ + 248.8, + 187.4, + 1733388711366 + ], + [ + 240.8, + 187.4, + 1733388711382 + ], + [ + 233.8, + 186.4, + 1733388711398 + ], + [ + 225.8, + 185.4, + 1733388711414 + ], + [ + 222.8, + 184.4, + 1733388711430 + ], + [ + 219.8, + 184.4, + 1733388711446 + ], + [ + 216.8, + 184.4, + 1733388711462 + ], + [ + 208.8, + 183.4, + 1733388711478 + ], + [ + 202.8, + 182.4, + 1733388711494 + ], + [ + 192.8, + 182.4, + 1733388711511 + ], + [ + 168.8, + 180.4, + 1733388711534 + ], + [ + 160.8, + 180.4, + 1733388711550 + ], + [ + 159.8, + 180.4, + 1733388711630 + ], + [ + 156.8, + 180.4, + 1733388711670 + ], + [ + 152.8, + 180.4, + 1733388711686 + ], + [ + 146.8, + 180.4, + 1733388711702 + ], + [ + 136.8, + 180.4, + 1733388711718 + ], + [ + 128.8, + 180.4, + 1733388711734 + ], + [ + 124.8, + 180.4, + 1733388711750 + ], + [ + 118.8, + 180.4, + 1733388711766 + ], + [ + 110.8, + 180.4, + 1733388711782 + ], + [ + 100.8, + 180.4, + 1733388711798 + ], + [ + 91.8, + 180.4, + 1733388711814 + ], + [ + 83.8, + 180.4, + 1733388711830 + ], + [ + 75.8, + 180.4, + 1733388711846 + ], + [ + 70.8, + 180.4, + 1733388711862 + ], + [ + 68.8, + 180.4, + 1733388711878 + ], + [ + 66.8, + 180.4, + 1733388711910 + ], + [ + 64.8, + 180.4, + 1733388711926 + ], + [ + 58.8, + 180.4, + 1733388711942 + ], + [ + 51.8, + 180.4, + 1733388711959 + ], + [ + 42.8, + 180.4, + 1733388711975 + ], + [ + 35.8, + 180.4, + 1733388711994 + ], + [ + 33.8, + 180.4, + 1733388712054 + ], + [ + 32.8, + 180.4, + 1733388712278 + ], + [ + 31.8, + 182.4, + 1733388712295 + ], + [ + 30.8, + 182.4, + 1733388712310 + ], + [ + 28.8, + 182.4, + 1733388712479 + ], + [ + 27.8, + 182.4, + 1733388712502 + ], + [ + 26.8, + 182.4, + 1733388712518 + ], + [ + 24.8, + 182.4, + 1733388712550 + ], + [ + 26.8, + 182.4, + 1733388712886 + ], + [ + 31.8, + 181.4, + 1733388712902 + ], + [ + 35.8, + 180.4, + 1733388712918 + ], + [ + 37.8, + 179.4, + 1733388712934 + ], + [ + 38.8, + 179.4, + 1733388712950 + ], + [ + 44.8, + 179.4, + 1733388712966 + ], + [ + 55.8, + 179.4, + 1733388712986 + ], + [ + 88.8, + 178.4, + 1733388712999 + ], + [ + 164.8, + 172.4, + 1733388713014 + ], + [ + 255.8, + 165.4, + 1733388713030 + ], + [ + 346.8, + 157.4, + 1733388713046 + ], + [ + 360.8, + 161.4, + 1733388715672 + ], + [ + 340.8, + 161.4, + 1733388715687 + ], + [ + 320.8, + 164.4, + 1733388715702 + ], + [ + 287.8, + 168.4, + 1733388715718 + ], + [ + 240.8, + 176.4, + 1733388715735 + ], + [ + 184.8, + 182.4, + 1733388715750 + ], + [ + 144.8, + 184.4, + 1733388715766 + ], + [ + 112.8, + 186.4, + 1733388715782 + ], + [ + 98.8, + 186.4, + 1733388715798 + ], + [ + 95.8, + 186.4, + 1733388715926 + ], + [ + 93.8, + 186.4, + 1733388715942 + ], + [ + 89.8, + 187.4, + 1733388715958 + ], + [ + 79.8, + 188.4, + 1733388715983 + ], + [ + 57.8, + 192.4, + 1733388715999 + ], + [ + 44.8, + 192.4, + 1733388716015 + ], + [ + 36.8, + 192.4, + 1733388716030 + ], + [ + 11.8, + 188.4, + 1733388716073 + ], + [ + -0.2, + 184.4, + 1733388716134 + ], + [ + -1.2, + 184.4, + 1733388716191 + ], + [ + -2.2, + 184.4, + 1733388716206 + ], + [ + -2.2, + 182.4, + 1733388716318 + ], + [ + -0.2, + 181.4, + 1733388716342 + ], + [ + 0.8, + 179.4, + 1733388716878 + ], + [ + 2.8, + 177.4, + 1733388716894 + ], + [ + 3.8, + 177.4, + 1733388716910 + ], + [ + 4.8, + 176.4, + 1733388716926 + ], + [ + 3.8, + 176.4, + 1733388717299 + ], + [ + 2.8, + 176.4, + 1733388717646 + ], + [ + 1.8, + 176.4, + 1733388717662 + ], + [ + 0.8, + 176.4, + 1733388717699 + ], + [ + -0.2, + 176.4, + 1733388717838 + ], + [ + -0.2, + 177.4, + 1733388718115 + ], + [ + 0.8, + 177.4, + 1733388718490 + ] + ], + track_list + ] + }, separators=(',', ':')) + captcha_collect_gzip = self.gzip_compress(captcha_collect_data) + aes_key_iv = EncryptTool.get_key_iv(salt=salt) + captcha_collect = EncryptTool.aes_encrypt(captcha_collect_gzip, aes_key_iv['aes_key'], aes_key_iv['aes_iv']) + return captcha_collect + + # 获取滑块加密参数key和iv + def vc_pre_ck_b(self): + self.headers.pop("anti-content", None) + url = "https://apiv2.pinduoduo.net/api/phantom/vc_pre_ck_b" + payload = { + "anti_content": self.get_auti_content(), + "verify_auth_token": self.verify_auth_token, + "sdk_type": 3, + "client_time": int(round(time.time() * 1000)) + } + response = requests.post(url, headers=self.headers, json=payload, cookies=self.cookies) + self.cookies.update(response.cookies.get_dict()) + return response.json().get("salt") + + # 获取滑块图片 + def obtain_captcha(self): + url = "https://apiv2.pinduoduo.net/api/phantom/obtain_captcha" + payload = { + "anti_content": self.get_auti_content(), + "verify_auth_token": self.verify_auth_token, + "captcha_collect": "" + } + response = requests.post(url, headers=self.headers, json=payload, cookies=self.cookies) + self.cookies.update(response.cookies.get_dict()) + return response.json().get("pictures") + + # 滑块验证 + def verify(self, captcha_collect, verify_code): + 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() + } + response = requests.post(url, headers=self.headers, json=payload, cookies=self.cookies) + self.cookies.update(response.cookies.get_dict()) + result = response.json().get("result") + self._log(f"滑块验证结果: {response.text}") + return result + + # 发送验证码通知给后端,并获取验证码 + def request_verification_code(self, username, store_id): + """向后端请求获取手机验证码""" + self._log(f"开始请求验证码,用户名: {username}, 店铺ID: {store_id}", "INFO") + + self.headers["anti-content"] = self.get_auti_content() + url = "https://mms.pinduoduo.com/janus/api/user/getLoginVerificationCode" + payload = { + "username": username, + "crawlerInfo": self.get_auti_content() + } + response = requests.post(url, headers=self.headers, json=payload, cookies=self.cookies) + self._log(f"发送验证码请求结果: {response.text}") + + # 发送消息给后端,告知需要验证码 + self._log("准备向后端发送验证码需求通知", "INFO") + self._send_verification_needed_message(store_id) + + # 这里需要等待后端重新下发包含验证码的登录参数 + # 实际实现中这个方法会在接收到新的登录参数后被重新调用 + return None + + def _send_verification_needed_message(self, store_id): + """向后端发送需要验证码的通知""" + try: + self._log(f"开始发送验证码需求通知,店铺ID: {store_id}", "INFO") + from WebSocket.backend_singleton import get_backend_client + backend = get_backend_client() + self._log(f"获取到后端客户端: {backend is not None}", "DEBUG") + + if backend: + message = { + "type": "connect_message", + "store_id": store_id, + "status": False, + "content": "需要验证码" + } + 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") + + self.headers["anti-content"] = login_params.get("anti_content", "") + + # 直接使用后端提供的参数构建登录请求 + ts = login_params.get("timestamp", int(round(time.time() * 1000))) + payload = { + "username": login_params.get("username", ""), + "password": login_params.get("password", ""), # 后端已加密 + "passwordEncrypt": True, + "verificationCode": "", + "mobileVerifyCode": login_params.get("code", ""), # 检查是否包含验证码 + "sign": "", + "touchevent": { + "mobileInputEditStartTime": ts - random.randint(10000, 20000), + "mobileInputEditFinishTime": ts - random.randint(8000, 15000), + "mobileInputKeyboardEvent": "0|1|1|-6366-6942-142", + "passwordInputEditStartTime": ts - random.randint(5000, 10000), + "passwordInputEditFinishTime": ts - random.randint(3000, 8000), + "passwordInputKeyboardEvent": "0|1|1|195-154-755-477", + "captureInputEditStartTime": "", + "captureInputEditFinishTime": "", + "captureInputKeyboardEvent": "", + "loginButtonTouchPoint": "1263,586", + "loginButtonClickTime": ts - 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": self.headers.get("user-agent"), + "vendor": "Google Inc.", + "product": "Gecko", + "productSub": "20030107", + "mimeTypes": "f5a1111231f589322da33fb59b56946b4043e092", + "plugins": "387b918f593d4d8d6bfa647c07e108afbd7a6223" + }, + "referer": "", + "timezoneOffset": -480 + }, + "riskSign": login_params.get("risk_sign", ""), # 后端已加密 + "timestamp": ts, + "crawlerInfo": login_params.get("anti_content", "") + } + + 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}") + self._log(f"登录响应内容: {response.text}") + + # 检查响应内容 + if "需要手机验证" in response.text: + self._log("✅ 检测到需要手机验证的响应", "INFO") + elif "需要验证图形验证码" in response.text: + self._log("✅ 检测到需要图形验证码的响应", "INFO") + else: + self._log("⚠️ 未检测到验证码相关响应", "WARNING") + + if "需要验证图形验证码" in response.text: + self.verify_auth_token = response.json().get("result").get("verifyAuthToken") + + salt = self.vc_pre_ck_b() # 获取生成aes key和iv 的密文值 + pictures = self.obtain_captcha() # 获取验证码图片 + distance = round((ImgDistance(bg=pictures[0], tp=pictures[1]).main() * (272 / 320)) + (48.75 / 2), 2) # 计算距离 + + track_list = Track.get_track(distance=distance) # 生成轨迹 + captcha_collect = self.captcha_collect(salt=salt, track_list=track_list) # 生成captcha_collect参数 + result = self.verify(captcha_collect=captcha_collect, verify_code=distance) # 检验 + + if result: + self.cookies.update({ + "msfe-pc-cookie-captcha-token": self.verify_auth_token + }) + self.headers["verifyauthtoken"] = self.verify_auth_token + success_count += 1 + # 如果滑块成功 success_count 计数一次 成功8次还是显示验证码则失败 返回False + if success_count < 8: + return self.login_with_params(login_params=login_params, store_id=store_id, success_count=success_count) + else: + return False + else: + # 如果滑块失败 error_count 计数一次 失败8次则返回False + error_count += 1 + if error_count < 8: + return self.login_with_params(login_params=login_params, store_id=store_id, error_count=error_count) + else: + return False + + elif "需要手机验证" in response.text: + # 检查是否已经包含验证码 + if login_params.get("code"): + # 已有验证码但仍需要验证,可能验证码错误或过期 + self._log("验证码可能错误或过期", "WARNING") + return False + else: + # 第一次需要验证码,调用发送验证码方法 + self._log("检测到需要手机验证码,正在调用发送验证码方法", "INFO") + username = login_params.get("username") + self._log(f"为用户 {username} 发送验证码", "INFO") + + self.request_verification_code(username, store_id) + return "need_verification_code" + + else: + # 登录成功或其他情况 + response_data = response.json() + if response_data.get("success"): + self._log("登录成功", "SUCCESS") + return self.cookies + else: + self._log(f"登录失败: {response_data.get('errorMsg', '未知错误')}", "ERROR") + return False + +# ===== 登录相关类集成结束 ===== + + # 定义持久化数据类 - 参考京东的WebsocketManager class WebsocketManager: _instance = None @@ -963,27 +2788,47 @@ class PddListenerForGUI: """使用后端下发的登录参数执行登录并启动监听""" try: self._log("🔵 [PDD] 收到后端登录参数,开始执行登录获取cookies", "INFO") + self._log(f"🔍 [PDD] 登录参数内容: {login_params[:100]}...", "DEBUG") # 1. 解析登录参数 + self._log("🔄 [PDD] 开始解析登录参数", "DEBUG") params_dict = self._parse_login_params(login_params) if not params_dict: self._log("❌ [PDD] 登录参数解析失败", "ERROR") return False + self._log(f"✅ [PDD] 登录参数解析成功,用户名: {params_dict.get('username', 'N/A')}", "DEBUG") - # 2. 执行登录获取Cookie - cookies = await self._execute_pdd_login(params_dict) - if not cookies: - self._log("❌ [PDD] 登录失败,无法获取cookies", "ERROR") + # 2. 使用新的PddLogin类执行登录 + self._log("🔄 [PDD] 开始创建PddLogin实例", "DEBUG") + pdd_login = PddLogin(log_callback=self.log_callback) + self._log("✅ [PDD] PddLogin实例创建成功", "DEBUG") + + self._log("🔄 [PDD] 开始执行登录", "DEBUG") + login_result = pdd_login.login_with_params(params_dict, store_id) + self._log(f"📊 [PDD] 登录结果: {login_result}", "DEBUG") + + if login_result == "need_verification_code": + self._log("⚠️ [PDD] 需要手机验证码,已通知后端,等待重新下发包含验证码的登录参数", "WARNING") + return "need_verification_code" # 返回特殊标识,避免被覆盖 + elif not login_result: + self._log("❌ [PDD] 登录失败", "ERROR") + return False + elif isinstance(login_result, dict): + # 登录成功,获取到cookies + self._log("✅ [PDD] 登录成功,使用获取的cookies连接平台", "SUCCESS") + + # 将cookies字典转换为字符串格式,与原有逻辑兼容 + import json + cookies_str = json.dumps(login_result) + return await self.start_with_cookies(store_id, cookies_str) + else: + self._log("❌ [PDD] 登录返回未知结果", "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") + self._log(f"🔍 [PDD] 异常详细信息: {traceback.format_exc()}", "ERROR") return False async def start_with_cookies(self, store_id: str, cookies: str): @@ -1117,188 +2962,7 @@ class PddListenerForGUI: 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 { diff --git a/WebSocket/BackendClient.py b/WebSocket/BackendClient.py index fe51a7c..3c6395c 100644 --- a/WebSocket/BackendClient.py +++ b/WebSocket/BackendClient.py @@ -874,12 +874,14 @@ class BackendClient: data = message.get('data', {}) # 判断是拼多多登录参数还是普通Cookie - if platform_name == "拼多多" and content == "pdd_login_params" and data.get('login_params'): - # 拼多多登录参数模式 - 修改为使用硬编码Cookie - print(f"收到拼多多登录参数,改为使用硬编码Cookie: 平台={platform_name}, 店铺={store_id}") + if platform_name == "拼多多" and content == "拼多多登录" and data.get('login_params'): + # 拼多多登录参数模式 - 传递完整的消息JSON给处理器 + print(f"收到拼多多登录参数: 平台={platform_name}, 店铺={store_id}") if self.login_callback: - # ✨ 传递硬编码Cookie标识,而不是实际的登录参数 - self.login_callback(platform_name, store_id, "use_hardcoded_cookie") + # 传递完整的JSON消息,让拼多多处理器来解析login_params + import json + full_message = json.dumps(message) + self.login_callback(platform_name, store_id, full_message) else: # 普通Cookie模式 print(f"收到登录指令: 平台={platform_name}, 店铺={store_id}, cookies_len={len(cookies) if cookies else 0}") diff --git a/WebSocket/backend_singleton.py b/WebSocket/backend_singleton.py index e806501..c15d27f 100644 --- a/WebSocket/backend_singleton.py +++ b/WebSocket/backend_singleton.py @@ -342,29 +342,72 @@ class WebSocketManager: def _start_pdd_listener(self, store_id: str, data: str): """启动拼多多平台监听""" try: - # ✨ 硬编码的Cookie,用于测试(当被风控时使用) - HARDCODED_PDD_COOKIES = "api_uid=CiDokWi36GiOnwCiu5avAg==; _nano_fp=Xpmyn5CJXq9YnqdqXC_Ly~7gxE5k6VgTed8man~4; rckk=B73W46PsHlKfDlYcQnDDh3VH6AR0y5pD; _bee=B73W46PsHlKfDlYcQnDDh3VH6AR0y5pD; ru1k=9579959b-bb08-4a69-8d4c-80e676165fb8; _f77=9579959b-bb08-4a69-8d4c-80e676165fb8; ru2k=d8fdc7ca-cdfe-4ac1-ae4b-4951a8b6c0ab; _a42=d8fdc7ca-cdfe-4ac1-ae4b-4951a8b6c0ab; newUserTag171811253=1; mms_b84d1838=3616,3523,3660,3614,3599,3621,3588,3254,3532,3642,3474,3475,3477,3479,3482,1202,1203,1204,1205,3417; windows_app_shop_token_23=eyJ0IjoiYmR3aXYxZXdBTmNta2FvZDN2Z1VQNFNOZU9wUDBDZkpTcG9Ma3hobXI2L3Urc3gxQmNXTXROQmVCTkZVWlR1QSIsInYiOjEsInMiOjIzLCJtIjoxMDk5MDk5NjksInUiOjE3MTcxODYxOH0; PASS_ID=1-peFP+4sYgkULHQrtW2LfnT4LZed0EiOrHLkU0toMWbW2brIU6TD3GNe2ilb2IhsBl0j3jVDrTV7tvU3V+mTxAQ_109909969_171718618; x-visit-time=1757922212039; JSESSIONID=E6DAF348C24B047239E896D07A1BE141" - def _runner(): try: + self._log("🚀 开始创建拼多多监听器实例", "DEBUG") listener = PDDListenerForGUI_WS(log_callback=self._log) + self._log("✅ 拼多多监听器实例创建成功", "DEBUG") - # ✨ 检查是否需要使用硬编码Cookie(特殊标识:data包含"use_hardcoded_cookie") - if "use_hardcoded_cookie" in data.lower(): - self._log("🔧 检测到硬编码Cookie标识,使用预设Cookie进行拼多多登录(跳过风控)", "INFO") - result = asyncio.run(listener.start_with_cookies(store_id=store_id, cookies=HARDCODED_PDD_COOKIES)) # 判断是登录参数还是Cookie - elif self._is_pdd_login_params(data): + if self._is_pdd_login_params(data): # 使用登录参数启动 self._log("📋 使用登录参数启动拼多多监听器", "INFO") + self._log("🔄 开始执行 start_with_login_params", "DEBUG") result = asyncio.run(listener.start_with_login_params(store_id=store_id, login_params=data)) + self._log(f"📊 start_with_login_params 执行结果: {result}", "DEBUG") + + # 详细的结果分析 + if result == "need_verification_code": + self._log("✅ [PDD] 登录流程正常,已发送验证码需求通知给后端", "SUCCESS") + elif result: + self._log("✅ [PDD] 登录成功,平台连接已建立", "SUCCESS") + else: + self._log("❌ [PDD] 登录失败", "ERROR") else: # 使用Cookie启动(兼容旧方式) self._log("🍪 使用Cookie启动拼多多监听器", "INFO") + self._log("🔄 开始执行 start_with_cookies", "DEBUG") result = asyncio.run(listener.start_with_cookies(store_id=store_id, cookies=data)) + self._log(f"📊 start_with_cookies 执行结果: {result}", "DEBUG") + + # 根据实际登录结果上报状态给后端 + if self.backend_client and result != "need_verification_code": + # 如果返回need_verification_code,说明验证码通知已经在PddLogin中发送了,不需要重复发送 + try: + message = { + "type": "connect_message", + "store_id": store_id, + "status": bool(result) if result != "need_verification_code" else False + } + self.backend_client.send_message(message) + status_text = "成功" if result else "失败" + self._log(f"上报拼多多平台连接状态{status_text}: {message}", "SUCCESS" if result else "ERROR") + except Exception as send_e: + self._log(f"上报拼多多平台连接状态失败: {send_e}", "ERROR") + elif result == "need_verification_code": + self._log("需要验证码,验证码通知已由PddLogin发送,等待后端重新下发登录参数", "INFO") + return result + except Exception as e: self._log(f"拼多多监听器运行异常: {e}", "ERROR") + import traceback + self._log(f"异常详情: {traceback.format_exc()}", "DEBUG") + # 异常情况下上报失败状态 + if self.backend_client: + try: + # 截取异常信息前100个字符,避免消息过长 + error_msg = str(e)[:100] if len(str(e)) > 100 else str(e) + message = { + "type": "connect_message", + "store_id": store_id, + "status": False, + "content": f"登录异常: {error_msg}" + } + self.backend_client.send_message(message) + self._log(f"异常情况下上报拼多多平台连接失败状态: {message}", "ERROR") + except Exception as send_e: + self._log(f"异常情况下上报状态也失败: {send_e}", "ERROR") return False # 在新线程中启动监听器 @@ -378,23 +421,7 @@ class WebSocketManager: 'store_id': store_id, } - # 更新监听器状态 - if f"拼多多:{store_id}" in self.platform_listeners: - self.platform_listeners[f"拼多多:{store_id}"]['status'] = 'success' - - # 上报连接状态给后端 - if self.backend_client: - try: - self.backend_client.send_message({ - "type": "connect_message", - "store_id": store_id, - "status": True - }) - self._log("已上报拼多多平台连接状态: 成功", "INFO") - except Exception as e: - self._log(f"上报拼多多平台连接状态失败: {e}", "WARNING") - - self._log("已启动拼多多平台监听", "SUCCESS") + self._log("拼多多平台监听线程已启动,等待登录结果...", "INFO") except Exception as e: self._log(f"启动拼多多平台监听失败: {e}", "ERROR") @@ -407,16 +434,36 @@ class WebSocketManager: "store_id": store_id, "status": False }) + self._log(f"启动失败情况下上报拼多多平台连接状态失败", "ERROR") except Exception as send_e: - self._log(f"失败状态下报拼多多平台连接状态也失败: {send_e}", "ERROR") + self._log(f"启动失败情况下上报状态也失败: {send_e}", "ERROR") def _is_pdd_login_params(self, data: str) -> bool: """判断是否为拼多多登录参数""" try: + self._log(f"🔍 [DEBUG] 检查是否为登录参数,数据长度: {len(data)}", "DEBUG") + self._log(f"🔍 [DEBUG] 数据前100字符: {data[:100]}", "DEBUG") + import json parsed_data = json.loads(data) - return (parsed_data.get("data", {}).get("login_params") is not None) - except: + self._log(f"🔍 [DEBUG] JSON解析成功,键: {list(parsed_data.keys())}", "DEBUG") + + login_params = parsed_data.get("data", {}).get("login_params", {}) + self._log(f"🔍 [DEBUG] login_params存在: {bool(login_params)}", "DEBUG") + + if not login_params: + self._log("🔍 [DEBUG] login_params为空,返回False", "DEBUG") + return False + + # 检查必需的登录参数字段 + required_fields = ["username", "password", "anti_content", "risk_sign", "timestamp"] + has_all_fields = all(field in login_params for field in required_fields) + self._log(f"🔍 [DEBUG] 包含所有必需字段: {has_all_fields}", "DEBUG") + self._log(f"🔍 [DEBUG] 现有字段: {list(login_params.keys())}", "DEBUG") + + return has_all_fields + except Exception as e: + self._log(f"🔍 [DEBUG] 解析失败: {e}", "DEBUG") return False def send_message(self, message: dict): diff --git a/config.py b/config.py index c92b3c6..35d69b7 100644 --- a/config.py +++ b/config.py @@ -8,13 +8,13 @@ import os # 用于路径与目录操作(写入用户配置目录) import json # 用于将令牌保存为 JSON 格式 # 后端服务器配置 -# BACKEND_HOST = "192.168.5.155" -BACKEND_HOST = "shuidrop.com" +BACKEND_HOST = "192.168.5.197" +# BACKEND_HOST = "shuidrop.com" # BACKEND_HOST = "test.shuidrop.com" -# BACKEND_PORT = "8000" -BACKEND_PORT = "" -# BACKEND_WS_URL = f"ws://{BACKEND_HOST}:{BACKEND_PORT}" -BACKEND_WS_URL = f"wss://{BACKEND_HOST}" +BACKEND_PORT = "8000" +# BACKEND_PORT = "" +BACKEND_WS_URL = f"ws://{BACKEND_HOST}:{BACKEND_PORT}" +# BACKEND_WS_URL = f"wss://{BACKEND_HOST}" # WebSocket配置 WS_CONNECT_TIMEOUT = 16.0