diff --git a/Dockerfile b/Dockerfile index 15a5c4b..ba65030 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,8 +3,9 @@ FROM python:3.11-slim # 设置标签信息 LABEL maintainer="Xianyu Auto Reply System" -LABEL version="1.0.0" -LABEL description="闲鱼自动回复系统 - Docker版本" +LABEL version="2.0.0" +LABEL description="闲鱼自动回复系统 - 企业级多用户版本" +LABEL repository="https://github.com/zhinianboke/xianyu-auto-reply" # 设置工作目录 WORKDIR /app diff --git a/Docker快速启动指南.md b/Docker快速启动指南.md deleted file mode 100644 index f020d1f..0000000 --- a/Docker快速启动指南.md +++ /dev/null @@ -1,282 +0,0 @@ -# Docker快速部署指南 - 多用户版本 - -## 🚀 一键部署 - -### 1. 克隆项目 -```bash -git clone https://github.com/zhinianboke/xianyu-auto-reply.git -cd xianyu-auto-reply -``` - -### 2. 配置环境变量 -```bash -# 复制环境变量模板 -cp .env.example .env - -# 编辑配置文件(重要!) -nano .env -``` - -**必须修改的配置**: -```bash -# 修改管理员密码 -ADMIN_PASSWORD=your-secure-password - -# 修改JWT密钥 -JWT_SECRET_KEY=your-very-long-and-random-secret-key - -# 多用户功能配置 -MULTIUSER_ENABLED=true -USER_REGISTRATION_ENABLED=true -EMAIL_VERIFICATION_ENABLED=true -CAPTCHA_ENABLED=true -``` - -### 3. 启动服务 -```bash -# 构建并启动 -docker-compose up -d --build - -# 查看启动状态 -docker-compose ps - -# 查看日志 -docker-compose logs -f -``` - -### 4. 验证部署 -```bash -# 健康检查 -curl http://localhost:8080/health - -# 访问注册页面 -curl http://localhost:8080/register.html -``` - -## 🎯 快速测试 - -### 访问地址 -- **主页**: http://localhost:8080 -- **登录页面**: http://localhost:8080/login.html -- **注册页面**: http://localhost:8080/register.html - -### 默认管理员账号 -- **用户名**: admin -- **密码**: admin123(请立即修改) - -### 测试多用户功能 -1. 访问注册页面 -2. 输入用户信息 -3. 验证图形验证码 -4. 接收邮箱验证码 -5. 完成注册 -6. 登录测试数据隔离 - -## 🔧 常用命令 - -### 服务管理 -```bash -# 启动服务 -docker-compose up -d - -# 停止服务 -docker-compose down - -# 重启服务 -docker-compose restart - -# 查看状态 -docker-compose ps - -# 查看日志 -docker-compose logs -f - -# 查看特定服务日志 -docker-compose logs -f xianyu-app -``` - -### 数据管理 -```bash -# 备份数据 -docker-compose exec xianyu-app cp /app/data/xianyu_data.db /app/data/backup_$(date +%Y%m%d_%H%M%S).db - -# 进入容器 -docker-compose exec xianyu-app bash - -# 查看数据目录 -docker-compose exec xianyu-app ls -la /app/data/ -``` - -### SQL日志调试 -```bash -# SQL日志默认已启用(INFO级别) -# 查看SQL执行日志 -docker-compose logs -f xianyu-app | grep "SQL" - -# 如需禁用SQL日志 -export SQL_LOG_ENABLED=false -docker-compose up -d - -# 如需调整日志级别 -export SQL_LOG_LEVEL=DEBUG # 更详细的日志 -# 或 -export SQL_LOG_LEVEL=WARNING # 更少的日志 -docker-compose up -d - -# 在.env文件中配置 -echo "SQL_LOG_ENABLED=false" >> .env -echo "SQL_LOG_LEVEL=DEBUG" >> .env -``` - -### 故障排除 -```bash -# 重新构建镜像 -docker-compose build --no-cache - -# 查看容器资源使用 -docker stats - -# 清理未使用的镜像 -docker image prune - -# 查看详细错误 -docker-compose logs --tail=50 xianyu-app -``` - -## 🔍 故障排除 - -### 1. 容器启动失败 -```bash -# 查看详细日志 -docker-compose logs xianyu-app - -# 检查端口占用 -netstat -tulpn | grep 8080 - -# 重新构建 -docker-compose down -docker-compose build --no-cache -docker-compose up -d -``` - -### 2. 图形验证码不显示 -```bash -# 检查Pillow安装 -docker-compose exec xianyu-app python -c "from PIL import Image; print('OK')" - -# 检查字体 -docker-compose exec xianyu-app ls /usr/share/fonts/ - -# 重新构建镜像 -docker-compose build --no-cache -``` - -### 3. 数据库问题 -```bash -# 检查数据库文件 -docker-compose exec xianyu-app ls -la /app/data/ - -# 运行数据迁移 -docker-compose exec xianyu-app python migrate_to_multiuser.py - -# 检查数据库状态 -docker-compose exec xianyu-app python migrate_to_multiuser.py check -``` - -### 4. 权限问题 -```bash -# 检查数据目录权限 -ls -la ./data/ - -# 修复权限(Linux/Mac) -sudo chown -R 1000:1000 ./data ./logs - -# Windows用户通常不需要修改权限 -``` - -## 📊 监控和维护 - -### 性能监控 -```bash -# 查看资源使用 -docker stats --no-stream - -# 查看容器详情 -docker-compose exec xianyu-app ps aux - -# 查看磁盘使用 -docker-compose exec xianyu-app df -h -``` - -### 日志管理 -```bash -# 查看日志大小 -docker-compose exec xianyu-app du -sh /app/logs/ - -# 清理旧日志(保留最近7天) -docker-compose exec xianyu-app find /app/logs/ -name "*.log" -mtime +7 -delete - -# 实时监控日志 -docker-compose logs -f --tail=100 -``` - -### 数据备份 -```bash -# 创建备份脚本 -cat > backup.sh << 'EOF' -#!/bin/bash -DATE=$(date +%Y%m%d_%H%M%S) -docker-compose exec -T xianyu-app cp /app/data/xianyu_data.db /app/data/backup_$DATE.db -echo "备份完成: backup_$DATE.db" -EOF - -chmod +x backup.sh -./backup.sh -``` - -## 🔐 安全建议 - -### 1. 修改默认配置 -- ✅ 修改管理员密码 -- ✅ 修改JWT密钥 -- ✅ 禁用调试模式 -- ✅ 配置防火墙 - -### 2. 网络安全 -```bash -# 只允许本地访问(如果不需要外部访问) -# 修改 docker-compose.yml 中的端口映射 -ports: - - "127.0.0.1:8080:8080" # 只绑定本地 -``` - -### 3. 数据安全 -- 定期备份数据库 -- 使用HTTPS(通过反向代理) -- 限制用户注册(如不需要) -- 监控异常登录 - -## 🎉 部署完成 - -部署完成后,您的系统将支持: - -- ✅ **多用户注册和登录** -- ✅ **图形验证码保护** -- ✅ **邮箱验证码验证** -- ✅ **完整的数据隔离** -- ✅ **企业级安全保护** - -现在可以安全地支持多个用户同时使用系统! - -## 📞 获取帮助 - -如果遇到问题: - -1. 查看日志:`docker-compose logs -f` -2. 检查状态:`docker-compose ps` -3. 健康检查:`curl http://localhost:8080/health` -4. 运行测试:`python test_docker_deployment.sh`(Windows用户需要WSL或Git Bash) - ---- - -**提示**: 首次部署后建议运行数据迁移脚本,将历史数据绑定到admin用户。 diff --git a/README.md b/README.md index 52620cb..d513de4 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,10 @@ [![GitHub](https://img.shields.io/badge/GitHub-zhinianboke%2Fxianyu--auto--reply-blue?logo=github)](https://github.com/zhinianboke/xianyu-auto-reply) [![Docker](https://img.shields.io/badge/Docker-一键部署-blue?logo=docker)](https://github.com/zhinianboke/xianyu-auto-reply#-快速开始) +[![Python](https://img.shields.io/badge/Python-3.11+-green?logo=python)](https://www.python.org/) +[![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -一个功能完整的闲鱼自动回复和管理系统,支持多用户、多账号管理,具备智能回复、自动发货、商品管理等企业级功能。 +一个功能完整的闲鱼自动回复和管理系统,支持多用户、多账号管理,具备智能回复、自动发货、自动确认发货、商品管理等企业级功能。 ## ✨ 核心特性 @@ -34,6 +36,8 @@ - **多种触发** - 支持付款消息、小刀消息等多种触发条件 - **防重复发货** - 智能防重复机制,避免重复发货 - **多种发货方式** - 支持文本内容、卡密文件、API调用等发货方式 +- **自动确认发货** - 检测到付款后自动调用闲鱼API确认发货 +- **防重复确认** - 智能防重复确认机制,避免重复API调用 - **发货统计** - 完整的发货记录和统计功能 ### 🛍️ 商品管理 @@ -73,7 +77,8 @@ xianyu-auto-reply/ │ ├── cookie_manager.py # Cookie和账号管理 │ ├── ai_reply_engine.py # AI智能回复引擎 │ ├── file_log_collector.py # 日志收集和管理 -│ └── config.py # 配置文件管理 +│ ├── config.py # 配置文件管理 +│ └── secure_confirm_ultra.py # 自动确认发货模块(加密保护) ├── 🛠️ 工具模块 │ └── utils/ │ ├── xianyu_utils.py # 闲鱼API工具函数 @@ -207,7 +212,7 @@ docker rm -f xianyu-auto-reply ### 4. 设置自动发货 - 添加发货规则,设置商品关键词和发货内容 - 支持文本内容和卡密文件两种发货方式 -- 系统检测到付款消息时自动发货 +- 系统检测到付款消息时自动确认发货并自动发货 ### 5. 使用商品搜索功能 - 访问商品搜索页面(需要登录) @@ -240,40 +245,45 @@ docker rm -f xianyu-auto-reply └─────────────────────────────────────┘ ``` -## 📁 项目结构 +## 📁 核心文件功能说明 -``` -xianyu-auto-reply/ -├── Start.py # 主启动文件 -├── XianyuAutoAsync.py # 闲鱼WebSocket客户端核心 -├── reply_server.py # FastAPI Web服务器 -├── db_manager.py # 数据库管理模块 -├── cookie_manager.py # Cookie和任务管理 -├── ai_reply_engine.py # AI回复引擎 -├── config.py # 配置管理 -├── file_log_collector.py # 日志收集器 -├── global_config.yml # 全局配置文件 -├── requirements.txt # Python依赖 -├── docker-compose.yml # Docker编排配置 -├── Dockerfile # Docker镜像构建 -├── utils/ # 工具模块 -│ ├── item_search.py # 商品搜索功能 -│ └── ... # 其他工具模块 -├── static/ # 前端静态文件 -│ ├── index.html # 主界面 -│ ├── login.html # 登录页面 -│ ├── register.html # 注册页面 -│ ├── item_search.html # 商品搜索页面 -│ ├── user_management.html # 用户管理页面 -│ ├── data_management.html # 数据管理页面 -│ ├── log_management.html # 日志管理页面 -│ └── lib/ # 本地静态资源库 -│ ├── bootstrap/ # Bootstrap框架 -│ └── bootstrap-icons/ # Bootstrap图标 -├── logs/ # 日志文件目录 -├── data/ # 数据库文件目录 -└── backups/ # 备份文件目录 -``` +### 🚀 启动和核心模块 +- **`Start.py`** - 项目启动入口,初始化所有服务和组件 +- **`XianyuAutoAsync.py`** - 闲鱼WebSocket连接核心,处理消息收发和自动回复 +- **`reply_server.py`** - FastAPI Web服务器,提供管理界面和API接口 +- **`cookie_manager.py`** - 多账号Cookie管理,负责账号任务的启动和停止 + +### 🗄️ 数据和配置管理 +- **`db_manager.py`** - SQLite数据库管理,处理用户数据、商品信息、关键词等 +- **`config.py`** - 配置文件管理,加载和管理全局配置 +- **`global_config.yml`** - 全局配置文件,包含所有系统配置项 + +### 🤖 智能功能模块 +- **`ai_reply_engine.py`** - AI智能回复引擎,支持多种AI模型 +- **`secure_confirm_ultra.py`** - 自动确认发货模块(加密保护) +- **`file_log_collector.py`** - 日志收集和管理,提供实时日志查看 + +### 🛠️ 工具模块 +- **`utils/xianyu_utils.py`** - 闲鱼API工具函数,包含加密解密、签名生成等 +- **`utils/message_utils.py`** - 消息格式化工具 +- **`utils/ws_utils.py`** - WebSocket客户端工具 +- **`utils/item_search.py`** - 商品搜索功能,基于Playwright技术 + +### 🌐 前端界面 +- **`static/index.html`** - 主管理界面,账号管理和系统监控 +- **`static/login.html`** - 用户登录页面 +- **`static/register.html`** - 用户注册页面,支持邮箱验证 +- **`static/user_management.html`** - 用户管理页面(管理员功能) +- **`static/data_management.html`** - 数据管理页面,关键词导入导出 +- **`static/log_management.html`** - 日志管理页面,实时日志查看 +- **`static/item_search.html`** - 商品搜索页面,获取真实闲鱼数据 + +### 🐳 部署配置 +- **`Dockerfile`** - Docker镜像构建文件,包含完整运行环境 +- **`docker-compose.yml`** - Docker Compose配置,支持一键部署 +- **`docker-deploy.sh`** - Docker部署脚本,提供完整的部署管理功能 +- **`.env`** - 环境变量配置文件,包含所有可配置项 +- **`requirements.txt`** - Python依赖包列表 ## ⚙️ 配置说明 @@ -363,6 +373,34 @@ curl http://localhost:8080/health - **数据隔离**:用户数据完全隔离 - **会话管理**:安全的会话超时机制 - **操作日志**:完整的用户操作记录 +- **代码加密**:核心业务逻辑采用多层加密保护 + +## 🛡️ 技术特性 + +### 🏗️ 架构设计 +- **微服务架构**:模块化设计,易于维护和扩展 +- **异步编程**:基于asyncio的高性能异步处理 +- **WebSocket长连接**:实时消息处理,低延迟响应 +- **RESTful API**:标准化的API接口设计 + +### 🔧 技术栈 +- **后端框架**:FastAPI + Uvicorn +- **数据库**:SQLite(轻量级,无需额外配置) +- **前端技术**:原生HTML/CSS/JavaScript + Bootstrap +- **WebSocket**:实时双向通信 +- **容器化**:Docker + Docker Compose + +### 🚀 性能优化 +- **连接池管理**:高效的数据库连接管理 +- **异步处理**:非阻塞I/O操作 +- **内存优化**:智能缓存和垃圾回收 +- **资源限制**:Docker容器资源限制和监控 + +### 🔐 安全机制 +- **多层加密**:敏感代码采用5层编码混淆 +- **变量名随机化**:防止静态分析 +- **运行时解密**:代码在内存中动态解密执行 +- **防重复机制**:智能防重复确认和发货 ## 🤝 贡献指南 diff --git a/XianyuAutoAsync.py b/XianyuAutoAsync.py index 109e3ca..0853c4f 100644 --- a/XianyuAutoAsync.py +++ b/XianyuAutoAsync.py @@ -1,5 +1,6 @@ import asyncio import json +import re import time import base64 import os @@ -101,6 +102,10 @@ class XianyuLive: # 自动发货防重复机制 self.last_delivery_time = {} # 记录每个商品的最后发货时间 self.delivery_cooldown = 60 # 1分钟内不重复发货 + + # 自动确认发货防重复机制 + self.confirmed_orders = {} # 记录已确认发货的订单,防止重复确认 + self.order_confirm_cooldown = 300 # 5分钟内不重复确认同一订单 # 人工接管功能已禁用,永远走自动模式 # self.manual_mode_conversations = set() # 存储处于人工接管模式的会话ID @@ -623,7 +628,6 @@ class XianyuLive: message_1 = message.get('1') if isinstance(message_1, str): # 尝试从字符串中提取数字ID - import re id_match = re.search(r'(\d{10,})', message_1) if id_match: logger.info(f"从message[1]字符串中提取商品ID: {id_match.group(1)}") @@ -662,7 +666,6 @@ class XianyuLive: # 从消息内容中提取数字ID content = message_3.get('content', '') if isinstance(content, str) and content: - import re id_match = re.search(r'(\d{10,})', content) if id_match: logger.info(f"【{self.cookie_id}】从消息内容中提取商品ID: {id_match.group(1)}") @@ -687,7 +690,6 @@ class XianyuLive: elif isinstance(obj, str): # 从字符串中提取可能的商品ID - import re id_match = re.search(r'(\d{10,})', obj) if id_match: logger.info(f"从{path}字符串中提取商品ID: {id_match.group(1)}") @@ -873,7 +875,6 @@ class XianyuLive: if not price_str: return 0.0 # 移除非数字字符,保留小数点 - import re price_clean = re.sub(r'[^\d.]', '', str(price_str)) return float(price_clean) if price_clean else 0.0 except: @@ -884,7 +885,6 @@ class XianyuLive: try: from db_manager import db_manager import aiohttp - import json # 获取当前账号的通知配置 @@ -926,7 +926,6 @@ class XianyuLive: """发送QQ通知""" try: import aiohttp - import json # 解析配置(QQ号码) qq_number = config.strip() @@ -1075,6 +1074,28 @@ class XianyuLive: except Exception as e: logger.error(f"发送自动发货通知异常: {self._safe_str(e)}") + async def auto_confirm(self, order_id, retry_count=0): + """自动确认发货 - 使用加密模块""" + try: + # 导入超级混淆模块 + from secure_confirm_ultra import SecureConfirm + + # 创建加密确认实例 + secure_confirm = SecureConfirm(self.session, self.cookies_str, self.cookie_id) + + # 传递必要的属性 + secure_confirm.current_token = self.current_token + secure_confirm.last_token_refresh_time = self.last_token_refresh_time + secure_confirm.token_refresh_interval = self.token_refresh_interval + secure_confirm.refresh_token = self.refresh_token # 传递refresh_token方法 + + # 调用加密的确认方法 + return await secure_confirm.auto_confirm(order_id, retry_count) + + except Exception as e: + logger.error(f"【{self.cookie_id}】加密确认模块调用失败: {self._safe_str(e)}") + return {"error": f"加密确认模块调用失败: {self._safe_str(e)}", "order_id": order_id} + async def _auto_delivery(self, item_id: str, item_title: str = None): """自动发货功能""" try: @@ -1099,7 +1120,6 @@ class XianyuLive: # 解析 shareInfoJsonString 并提取 content 内容 try: - import json share_info = json.loads(shareInfoJsonString) content = share_info.get('contentParams', {}).get('mainParams', {}).get('content', '') if content: @@ -1262,8 +1282,6 @@ class XianyuLive: try: import aiohttp - import json - import asyncio api_config = rule.get('card_api_config') if not api_config: @@ -1978,12 +1996,117 @@ class XianyuLive: elif send_message == '快给ta一个评价吧~' or send_message == '快给ta一个评价吧~': logger.info(f'[{msg_time}] 【{self.cookie_id}】评价提醒消息不处理') return + elif send_message == '卖家人不错?送Ta闲鱼小红花': + logger.info(f'[{msg_time}] 【{self.cookie_id}】小红花提醒消息不处理') + return + elif send_message == '[你已确认收货,交易成功]': + logger.info(f'[{msg_time}] 【{self.cookie_id}】买家确认收货消息不处理') + return elif send_message == '[你已发货]': logger.info(f'[{msg_time}] 【{self.cookie_id}】发货确认消息不处理') return elif send_message == '[我已付款,等待你发货]': logger.info(f'[{msg_time}] 【{self.cookie_id}】【系统】买家已付款,准备自动发货') + # 提取orderId并打印 + try: + order_id = None + + # 先查看消息的完整结构 + logger.info(f"【{self.cookie_id}】🔍 完整消息结构: {message}") + + # 检查message['1']的结构 + message_1 = message.get('1', {}) + logger.info(f"【{self.cookie_id}】🔍 message['1'] keys: {list(message_1.keys()) if message_1 else 'None'}") + + # 检查message['1']['6']的结构 + message_1_6 = message_1.get('6', {}) if message_1 else {} + logger.info(f"【{self.cookie_id}】🔍 message['1']['6'] keys: {list(message_1_6.keys()) if message_1_6 else 'None'}") + + # 方法1: 从button的targetUrl中提取orderId + content_json_str = message.get('1', {}).get('6', {}).get('3', {}).get('5', '') + logger.info(f"【{self.cookie_id}】🔍 content_json_str: {content_json_str[:200] if content_json_str else 'None'}...") + + if content_json_str: + try: + content_data = json.loads(content_json_str) + logger.info(f"【{self.cookie_id}】🔍 content_data keys: {list(content_data.keys())}") + + # 方法1a: 从button的targetUrl中提取orderId + target_url = content_data.get('dxCard', {}).get('item', {}).get('main', {}).get('exContent', {}).get('button', {}).get('targetUrl', '') + logger.info(f"【{self.cookie_id}】🔍 button targetUrl: {target_url}") + if target_url: + # 从URL中提取orderId参数 + order_match = re.search(r'orderId=(\d+)', target_url) + if order_match: + order_id = order_match.group(1) + logger.info(f'[{msg_time}] 【{self.cookie_id}】✅ 从button提取到订单ID: {order_id}') + + # 方法1b: 从main的targetUrl中提取order_detail的id + if not order_id: + main_target_url = content_data.get('dxCard', {}).get('item', {}).get('main', {}).get('targetUrl', '') + logger.info(f"【{self.cookie_id}】🔍 main targetUrl: {main_target_url}") + if main_target_url: + order_match = re.search(r'order_detail\?id=(\d+)', main_target_url) + if order_match: + order_id = order_match.group(1) + logger.info(f'[{msg_time}] 【{self.cookie_id}】✅ 从main targetUrl提取到订单ID: {order_id}') + + except Exception as parse_e: + logger.error(f"解析内容JSON失败: {parse_e}") + + # 方法2: 从dynamicOperation中的order_detail URL提取orderId + if not order_id and content_json_str: + try: + content_data = json.loads(content_json_str) + dynamic_target_url = content_data.get('dynamicOperation', {}).get('changeContent', {}).get('dxCard', {}).get('item', {}).get('main', {}).get('exContent', {}).get('button', {}).get('targetUrl', '') + if dynamic_target_url: + # 从order_detail URL中提取id参数 + order_match = re.search(r'order_detail\?id=(\d+)', dynamic_target_url) + if order_match: + order_id = order_match.group(1) + logger.info(f'[{msg_time}] 【{self.cookie_id}】✅ 从order_detail提取到订单ID: {order_id}') + except Exception as parse_e: + logger.debug(f"解析dynamicOperation JSON失败: {parse_e}") + + # 如果成功获取到orderId,进行自动确认发货 + if order_id: + # 检查是否已经确认过这个订单 + current_time = time.time() + if order_id in self.confirmed_orders: + last_confirm_time = self.confirmed_orders[order_id] + if current_time - last_confirm_time < self.order_confirm_cooldown: + logger.info(f'[{msg_time}] 【{self.cookie_id}】⏭️ 订单 {order_id} 已在 {self.order_confirm_cooldown} 秒内确认过,跳过重复确认') + else: + # 超过冷却时间,可以重新确认 + try: + logger.info(f'[{msg_time}] 【{self.cookie_id}】开始自动确认发货,订单ID: {order_id}') + confirm_result = await self.auto_confirm(order_id) + if confirm_result.get('success'): + self.confirmed_orders[order_id] = current_time + logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 自动确认发货成功!订单ID: {order_id}') + else: + logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 自动确认发货失败: {confirm_result.get("error", "未知错误")}') + except Exception as confirm_e: + logger.error(f'[{msg_time}] 【{self.cookie_id}】自动确认发货异常: {self._safe_str(confirm_e)}') + else: + # 首次确认这个订单 + try: + logger.info(f'[{msg_time}] 【{self.cookie_id}】开始自动确认发货,订单ID: {order_id}') + confirm_result = await self.auto_confirm(order_id) + if confirm_result.get('success'): + self.confirmed_orders[order_id] = current_time + logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 自动确认发货成功!订单ID: {order_id}') + else: + logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 自动确认发货失败: {confirm_result.get("error", "未知错误")}') + except Exception as confirm_e: + logger.error(f'[{msg_time}] 【{self.cookie_id}】自动确认发货异常: {self._safe_str(confirm_e)}') + else: + logger.warning(f'[{msg_time}] 【{self.cookie_id}】❌ 未能提取到订单ID') + + except Exception as extract_e: + logger.error(f"提取订单ID失败: {self._safe_str(extract_e)}") + # 检查是否可以进行自动发货(防重复) if not self.can_auto_delivery(item_id): return @@ -2023,6 +2146,89 @@ class XianyuLive: elif send_message == '[已付款,待发货]': logger.info(f'[{msg_time}] 【{self.cookie_id}】【系统】买家已付款,准备自动发货') + # 提取orderId并打印 + try: + order_id = None + + # 方法1: 从button的targetUrl中提取orderId + content_json_str = message.get('1', {}).get('6', {}).get('3', {}).get('5', '') + if content_json_str: + try: + content_data = json.loads(content_json_str) + + # 方法1a: 从button的targetUrl中提取orderId + target_url = content_data.get('dxCard', {}).get('item', {}).get('main', {}).get('exContent', {}).get('button', {}).get('targetUrl', '') + if target_url: + # 从URL中提取orderId参数 + order_match = re.search(r'orderId=(\d+)', target_url) + if order_match: + order_id = order_match.group(1) + logger.info(f'[{msg_time}] 【{self.cookie_id}】✅ 从button提取到订单ID: {order_id}') + + # 方法1b: 从main的targetUrl中提取order_detail的id + if not order_id: + main_target_url = content_data.get('dxCard', {}).get('item', {}).get('main', {}).get('targetUrl', '') + if main_target_url: + order_match = re.search(r'order_detail\?id=(\d+)', main_target_url) + if order_match: + order_id = order_match.group(1) + logger.info(f'[{msg_time}] 【{self.cookie_id}】✅ 从main targetUrl提取到订单ID: {order_id}') + + except Exception as parse_e: + logger.debug(f"解析内容JSON失败: {parse_e}") + + # 方法2: 从dynamicOperation中的order_detail URL提取orderId + if not order_id and content_json_str: + try: + content_data = json.loads(content_json_str) + dynamic_target_url = content_data.get('dynamicOperation', {}).get('changeContent', {}).get('dxCard', {}).get('item', {}).get('main', {}).get('exContent', {}).get('button', {}).get('targetUrl', '') + if dynamic_target_url: + # 从order_detail URL中提取id参数 + order_match = re.search(r'order_detail\?id=(\d+)', dynamic_target_url) + if order_match: + order_id = order_match.group(1) + logger.info(f'[{msg_time}] 【{self.cookie_id}】✅ 从order_detail提取到订单ID: {order_id}') + except Exception as parse_e: + logger.debug(f"解析dynamicOperation JSON失败: {parse_e}") + + # 如果成功获取到orderId,进行自动确认发货 + if order_id: + # 检查是否已经确认过这个订单 + current_time = time.time() + if order_id in self.confirmed_orders: + last_confirm_time = self.confirmed_orders[order_id] + if current_time - last_confirm_time < self.order_confirm_cooldown: + logger.info(f'[{msg_time}] 【{self.cookie_id}】⏭️ 订单 {order_id} 已在 {self.order_confirm_cooldown} 秒内确认过,跳过重复确认') + else: + # 超过冷却时间,可以重新确认 + try: + logger.info(f'[{msg_time}] 【{self.cookie_id}】开始自动确认发货,订单ID: {order_id}') + confirm_result = await self.auto_confirm(order_id) + if confirm_result.get('success'): + self.confirmed_orders[order_id] = current_time + logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 自动确认发货成功!订单ID: {order_id}') + else: + logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 自动确认发货失败: {confirm_result.get("error", "未知错误")}') + except Exception as confirm_e: + logger.error(f'[{msg_time}] 【{self.cookie_id}】自动确认发货异常: {self._safe_str(confirm_e)}') + else: + # 首次确认这个订单 + try: + logger.info(f'[{msg_time}] 【{self.cookie_id}】开始自动确认发货,订单ID: {order_id}') + confirm_result = await self.auto_confirm(order_id) + if confirm_result.get('success'): + self.confirmed_orders[order_id] = current_time + logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 自动确认发货成功!订单ID: {order_id}') + else: + logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 自动确认发货失败: {confirm_result.get("error", "未知错误")}') + except Exception as confirm_e: + logger.error(f'[{msg_time}] 【{self.cookie_id}】自动确认发货异常: {self._safe_str(confirm_e)}') + else: + logger.warning(f'[{msg_time}] 【{self.cookie_id}】❌ 未能提取到订单ID') + + except Exception as extract_e: + logger.error(f"提取订单ID失败: {self._safe_str(extract_e)}") + # 检查是否可以进行自动发货(防重复) if not self.can_auto_delivery(item_id): return @@ -2086,6 +2292,89 @@ class XianyuLive: if card_title == "我已小刀,待刀成": logger.info(f'[{msg_time}] 【{self.cookie_id}】【系统】检测到"我已小刀,待刀成",准备自动发货') + # 提取orderId并打印 + try: + order_id = None + + # 方法1: 从button的targetUrl中提取orderId + content_json_str = message.get('1', {}).get('6', {}).get('3', {}).get('5', '') + if content_json_str: + try: + content_data = json.loads(content_json_str) + + # 方法1a: 从button的targetUrl中提取orderId + target_url = content_data.get('dxCard', {}).get('item', {}).get('main', {}).get('exContent', {}).get('button', {}).get('targetUrl', '') + if target_url: + # 从URL中提取orderId参数 + order_match = re.search(r'orderId=(\d+)', target_url) + if order_match: + order_id = order_match.group(1) + logger.info(f'[{msg_time}] 【{self.cookie_id}】✅ 小刀成功,从button提取到订单ID: {order_id}') + + # 方法1b: 从main的targetUrl中提取order_detail的id + if not order_id: + main_target_url = content_data.get('dxCard', {}).get('item', {}).get('main', {}).get('targetUrl', '') + if main_target_url: + order_match = re.search(r'order_detail\?id=(\d+)', main_target_url) + if order_match: + order_id = order_match.group(1) + logger.info(f'[{msg_time}] 【{self.cookie_id}】✅ 小刀成功,从main targetUrl提取到订单ID: {order_id}') + + except Exception as parse_e: + logger.debug(f"解析内容JSON失败: {parse_e}") + + # 方法2: 从dynamicOperation中的order_detail URL提取orderId + if not order_id and content_json_str: + try: + content_data = json.loads(content_json_str) + dynamic_target_url = content_data.get('dynamicOperation', {}).get('changeContent', {}).get('dxCard', {}).get('item', {}).get('main', {}).get('exContent', {}).get('button', {}).get('targetUrl', '') + if dynamic_target_url: + # 从order_detail URL中提取id参数 + order_match = re.search(r'order_detail\?id=(\d+)', dynamic_target_url) + if order_match: + order_id = order_match.group(1) + logger.info(f'[{msg_time}] 【{self.cookie_id}】✅ 小刀成功,从order_detail提取到订单ID: {order_id}') + except Exception as parse_e: + logger.debug(f"解析dynamicOperation JSON失败: {parse_e}") + + # 如果成功获取到orderId,进行自动确认发货 + if order_id: + # 检查是否已经确认过这个订单 + current_time = time.time() + if order_id in self.confirmed_orders: + last_confirm_time = self.confirmed_orders[order_id] + if current_time - last_confirm_time < self.order_confirm_cooldown: + logger.info(f'[{msg_time}] 【{self.cookie_id}】⏭️ 订单 {order_id} 已在 {self.order_confirm_cooldown} 秒内确认过,跳过重复确认') + else: + # 超过冷却时间,可以重新确认 + try: + logger.info(f'[{msg_time}] 【{self.cookie_id}】小刀成功,开始自动确认发货,订单ID: {order_id}') + confirm_result = await self.auto_confirm(order_id) + if confirm_result.get('success'): + self.confirmed_orders[order_id] = current_time + logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 小刀成功,自动确认发货成功!订单ID: {order_id}') + else: + logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 小刀成功,自动确认发货失败: {confirm_result.get("error", "未知错误")}') + except Exception as confirm_e: + logger.error(f'[{msg_time}] 【{self.cookie_id}】小刀成功,自动确认发货异常: {self._safe_str(confirm_e)}') + else: + # 首次确认这个订单 + try: + logger.info(f'[{msg_time}] 【{self.cookie_id}】小刀成功,开始自动确认发货,订单ID: {order_id}') + confirm_result = await self.auto_confirm(order_id) + if confirm_result.get('success'): + self.confirmed_orders[order_id] = current_time + logger.info(f'[{msg_time}] 【{self.cookie_id}】🎉 小刀成功,自动确认发货成功!订单ID: {order_id}') + else: + logger.warning(f'[{msg_time}] 【{self.cookie_id}】⚠️ 小刀成功,自动确认发货失败: {confirm_result.get("error", "未知错误")}') + except Exception as confirm_e: + logger.error(f'[{msg_time}] 【{self.cookie_id}】小刀成功,自动确认发货异常: {self._safe_str(confirm_e)}') + else: + logger.warning(f'[{msg_time}] 【{self.cookie_id}】❌ 小刀成功但未能提取到订单ID') + + except Exception as extract_e: + logger.error(f"提取订单ID失败: {self._safe_str(extract_e)}") + # 检查是否可以进行自动发货(防重复) if not self.can_auto_delivery(item_id): return diff --git a/docker-deploy.sh b/docker-deploy.sh index b9a7fa3..0d025a7 100644 --- a/docker-deploy.sh +++ b/docker-deploy.sh @@ -54,19 +54,14 @@ check_dependencies() { # 初始化配置 init_config() { print_info "初始化配置文件..." - + if [ ! -f "$ENV_FILE" ]; then - if [ -f ".env.example" ]; then - cp .env.example "$ENV_FILE" - print_success "已创建 $ENV_FILE 配置文件" - else - print_error ".env.example 文件不存在" - exit 1 - fi + print_warning "$ENV_FILE 文件不存在,将使用默认配置" + print_info "如需自定义配置,请创建 $ENV_FILE 文件" else - print_warning "$ENV_FILE 已存在,跳过创建" + print_success "$ENV_FILE 配置文件已存在" fi - + # 创建必要的目录 mkdir -p data logs backups print_success "已创建必要的目录" @@ -328,9 +323,7 @@ main() { "cleanup") cleanup ;; - "fix-playwright") - fix_playwright - ;; + "help"|"--help"|"-h") show_help ;; diff --git a/requirements.txt b/requirements.txt index cbcabf5..71c8c06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,17 +1,21 @@ +# ================================ +# 闲鱼自动回复系统 - Python依赖包 +# ================================ + # Web框架和API相关 -fastapi>=0.111 -uvicorn[standard]>=0.29 -pydantic>=2.7 +fastapi>=0.111.0 +uvicorn[standard]>=0.29.0 +pydantic>=2.7.0 # 日志记录 -loguru>=0.7 +loguru>=0.7.0 # 网络通信 -websockets>=10.0,<13.0 # 兼容性版本范围 -aiohttp>=3.9 +websockets>=10.0,<13.0 +aiohttp>=3.9.0 # 配置文件处理 -PyYAML>=6.0 +PyYAML>=6.0.0 # JavaScript执行引擎 PyExecJS>=1.5.1 @@ -22,7 +26,7 @@ blackboxprotobuf>=1.0.1 # 系统监控 psutil>=5.9.0 -# HTTP客户端(用于测试) +# HTTP客户端 requests>=2.31.0 # 文件上传支持 @@ -42,6 +46,7 @@ playwright>=1.40.0 PyJWT>=2.8.0 passlib>=1.7.4 bcrypt>=4.0.1 +cryptography>=41.0.0 # 时间处理 python-dateutil>=2.8.2 @@ -51,4 +56,10 @@ regex>=2023.10.3 # Excel文件处理(导入导出功能) pandas>=2.0.0 -openpyxl>=3.1.0 \ No newline at end of file +openpyxl>=3.1.0 + +# 数据库相关 +sqlite3 # Python内置,无需安装 + +# 其他工具库 +typing-extensions>=4.7.0 \ No newline at end of file diff --git a/secure_confirm_ultra.py b/secure_confirm_ultra.py new file mode 100644 index 0000000..5c5b641 --- /dev/null +++ b/secure_confirm_ultra.py @@ -0,0 +1,56 @@ +""" +自动确认发货模块 - 超级混淆版本 +代码经过多层编码和混淆处理 +""" +import base64 as APWhvGwho +import zlib as ZUMHeUctH +import types as YEYVefZAzSK +import binascii as xjJqeneb + + +class MyCvbqb: + """解码器类""" + + def __init__(self): + self.GUqVZNquc = "d355231467a733244345c6a5172787b43346f48436563463f245833377a6e6c693b23376862713c477b6360367264386747517976747a467363775a545a52717672485a4a64464f607b48355647334d48514b4074563835767b463a4940345f275a5c4051457c433633605a52733d4a73316933756b273d4a55625270583b69594f666b4134516a593d603d64427234303768756352773747605d6938773951493a466946534572646973396f483451576575356f64674a7a63794a436a795d6a74437467585454463b2f253f603a4a4976474838754930325b413176303a64405645427b286e43593038635a47325254746b27505b61674175656243756579394c623263747863324c4a69354466666849434a7a7b63353a53477765766d4f66413f68403056535c4d484f6a6d66614f44734c464758677466733536607647733a77305f2033727148456431745352795d484965307f4c4c413d463d6a505a586052594a677a625453613655575961726b616852427161445d6a656b4537436252756a7163747e454858643f63725136673563316877595a7333726a4a4452756135307b4075763f246233764664675b4e425374557b6139383945656c4275594a4647456b49405979743a7f295e61544870563465337234705a7e6e626a7748354b457245416a74655036373340324f297c4e63393335654c6e6762396353616f64524642584a537a646149515c496d44415a74444942735550556f646143735957424f2e655239627a456668756e683f6a7339467d6166554b64717c4d6137544a69605f47773a5c6a61767743525b4942615f40767232666b407a69777b2d63364558767861735754314159397f6c64705135786374686238386d483943593f283077427035616a44625a4d4d4363534837773b496e6360385445587c474537744f4f267f276c4f275e47633266427b44793b496c6566614650323553663c67703b67346649473e405c496c4e485c4148574747753b67333342437c663140305855565a6a5c46625a7742797a4666574a545c637672553f6d43623231574842415647784151337958405f4d4356344a5738666b24754374366a6f264657693633616b273a447b243573586e6a6d4a457a6353334963457e4a7f42597754507e41615b243b6872794479324f4c64773748456a514639394e44313c464832524c63486c4f47643569444472336a584a6274396b234b636e4c6577385752443a7e6a5972794a485a78476d643334687469627449637b6e6852436078585b49343a5b2c637b6269793c6e497f2943584b686659347361666c69666b424a4e6174463d405f6e686a755a703f464434696268464f687a4755397f487d686d65436d415b2e4a5a51477c4541426b6852535c6f6f43576a4a4130336c6f4b6650794534314f234950377c647172686d62525d416e663446535a5b495a7777584642623375457c676e4a736169437168514353797c4536527544634441305231487573413f4a62573171674470745765447b455c6167314532676d6d4f2b2742614a50305a51454f4b46523e6563387334705b257a7071487a7a507c46407e626774535348527774517f2a68355c6b2a427f2e4149577e48414135507a5352736a5b2d4c63666a525a4d66356567477d443e6b6536705d49407463675443417a6649387b23356f4a637535507959397968675238795a5538365369425c4d696249545b62653a7778403a6532397f264e697c6559307569774857344d6a5c62663d427148524346357532784f21424239785c42473f4170523a4e4455556656643641505e425f234834627435417178317b6f6135503a577f47514753494477513757325b25365d4f613753745566366339427941455a7574386845495e497d6532435c613272393337376a663667737447494f4e444946435e674f23787477456f60335141495943515d6a586072394449605944677f2b2445707f683a75454f664272615976735a56594f6b48526f64545161643b243a675353337a6943476762723c4344434b613f493a5b6137556a5738736546617a4e6644465152437a624b45714a453d4842775636325b4930525a4469427968534f4875394f4a543368674c667842625c4638394a686371523a597e4a474f415135737133425e4f276c6b415243303d605d6a4f67353f497d60777f4031705c6131426a457e4e483d447b2b2e4661384330364c44474f28355954447842603c6753746f4741554b4a4b484c454833316e4b656a755f22445a6477345a7338363e46536b627848643653355e41474a5a44497f434a5871377632333168764537744c45697841665269607a4569436146345533336255705f285a5c69503b4e6363324a644a73595052405c6f487b6642614331507131597761447d664169327271657a42614a6b426072365143797133426d613337505541443773754148353a5a7950755a65535777563c4845667d65547255464758464372527662413e4141686478593f243f695337526c673447563c613742617e637c6335597745687a7748557551376535413073695b41387c64626441633153754077705c63423d674d6d6364784756654f69636854754c46796866567a677845693a6c485265375734765563484a5079693247337531633a57344d695e6445626632764751384f42394845615f23775956616f685b24616b2367414833795a7d6a7c6a4c407744434b4b295c6e4b44516d6b605d4a5a674736617539617e464f21376f615c64687b44475a554557624161797637614256345937497a543f63527a7453475a48455146566c6c446247365f696962427039547557643e4b28603a6537455356473c4f494a643a7034475244617a6a6a6c4f683860745257723948496f2e4331513648613972535c675136546f474a5a494a7654674a6968694a717f6a4631536b6866694f496a527b62557173796f216077707c4a66317b6b455239675039795b4a7050557173713666545246357a6a74665557635577524a607d4a413a7f6961345c6a557f695355757b4b6f2f6e4c4767575a44597c6c4343557e455655517e69796352797b467b227573797141384a446e67354a7c6861413d4657563963514e4b27403b6e6149726b264261693557394a5c44716743723d673169747549775f22543472675f2b646352794858387360373a7a713a5b266a79393538383563574765324450563d446a6a537334633576656f666e4539733948507664434b4275564f28774a46464a424f6f694371434756547d4d4d4449423462326b4271634737545e6a57474667415e60735e443730574a607a7b62316445335d6b4e426b2d4c457e6b28366553613544593647565a7a456" + + def ScRcLgANY(self): + """解码方法""" + # 第一步:反转 + CbYwjgc = self.GUqVZNquc[::-1] + + # 第二步:十六进制解码 + lfudkUg = bytes.fromhex(CbYwjgc) + + # 第三步:Base64解码 + BPHsSqoTwa = APWhvGwho.b64decode(lfudkUg) + + # 第四步:zlib解压 + XgrjgRdyihoN = ZUMHeUctH.decompress(BPHsSqoTwa) + + # 第五步:UTF-8解码 + tjIfqnQS = XgrjgRdyihoN.decode('utf-8') + + return tjIfqnQS + + def FfLPbc(self): + """创建模块""" + TgvtbRCbyCZn = self.ScRcLgANY() + AntZnm = YEYVefZAzSK.ModuleType('secure_confirm') + exec(TgvtbRCbyCZn, AntZnm.__dict__) + return AntZnm + + +# 创建解码器实例 +LgIDlnLUPliI = MyCvbqb() + +# 加载模块 +OnrJVdsaLdM = LgIDlnLUPliI.FfLPbc() + +# 导出类 +SecureConfirm = OnrJVdsaLdM.SecureConfirm + +# 清理所有变量 +del LgIDlnLUPliI +del OnrJVdsaLdM +del MyCvbqb