From 26a0b3507d30c6159be297bc823af3e9ea086f16 Mon Sep 17 00:00:00 2001 From: quant Date: Wed, 4 Mar 2026 17:22:39 +0800 Subject: [PATCH] first commit --- test_wechat_click.py | 69 ++++ wechat_auto/.env | 23 ++ wechat_auto/.env.example | 23 ++ wechat_auto/README.md | 380 ++++++++++++++++++ wechat_auto/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 151 bytes .../__pycache__/config.cpython-313.pyc | Bin 0 -> 1579 bytes wechat_auto/__pycache__/main.cpython-313.pyc | Bin 0 -> 2712 bytes wechat_auto/api/__init__.py | 0 .../api/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 155 bytes .../api/__pycache__/trigger.cpython-313.pyc | Bin 0 -> 2281 bytes wechat_auto/api/trigger.py | 48 +++ wechat_auto/capture_icons.py | 57 +++ wechat_auto/config.py | 39 ++ wechat_auto/core/__init__.py | 0 .../core/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 156 bytes .../task_scheduler.cpython-313.pyc | Bin 0 -> 6142 bytes .../window_manager.cpython-313.pyc | Bin 0 -> 6630 bytes wechat_auto/core/desktop_automation.py | 113 ++++++ wechat_auto/core/executor/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 165 bytes .../pyautogui_executor.cpython-313.pyc | Bin 0 -> 9476 bytes .../qwen_ai_executor.cpython-313.pyc | Bin 0 -> 9841 bytes .../core/executor/pyautogui_executor.py | 156 +++++++ wechat_auto/core/executor/qwen_ai_executor.py | 197 +++++++++ wechat_auto/core/task_scheduler.py | 97 +++++ wechat_auto/core/window_manager.py | 130 ++++++ wechat_auto/images/wechat_icon.png | Bin 0 -> 3033 bytes wechat_auto/images/一见星球.png | Bin 0 -> 55765 bytes wechat_auto/images/小程序图标.png | Bin 0 -> 40193 bytes wechat_auto/main.py | 51 +++ wechat_auto/models/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 158 bytes .../__pycache__/activity.cpython-313.pyc | Bin 0 -> 2427 bytes wechat_auto/models/activity.py | 35 ++ wechat_auto/requirements.txt | 7 + wechat_auto/utils/__init__.py | 0 .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 157 bytes .../utils/__pycache__/logger.cpython-313.pyc | Bin 0 -> 1740 bytes .../utils/__pycache__/retry.cpython-313.pyc | Bin 0 -> 4133 bytes wechat_auto/utils/logger.py | 34 ++ wechat_auto/utils/retry.py | 88 ++++ 42 files changed, 1547 insertions(+) create mode 100644 test_wechat_click.py create mode 100644 wechat_auto/.env create mode 100644 wechat_auto/.env.example create mode 100644 wechat_auto/README.md create mode 100644 wechat_auto/__init__.py create mode 100644 wechat_auto/__pycache__/__init__.cpython-313.pyc create mode 100644 wechat_auto/__pycache__/config.cpython-313.pyc create mode 100644 wechat_auto/__pycache__/main.cpython-313.pyc create mode 100644 wechat_auto/api/__init__.py create mode 100644 wechat_auto/api/__pycache__/__init__.cpython-313.pyc create mode 100644 wechat_auto/api/__pycache__/trigger.cpython-313.pyc create mode 100644 wechat_auto/api/trigger.py create mode 100644 wechat_auto/capture_icons.py create mode 100644 wechat_auto/config.py create mode 100644 wechat_auto/core/__init__.py create mode 100644 wechat_auto/core/__pycache__/__init__.cpython-313.pyc create mode 100644 wechat_auto/core/__pycache__/task_scheduler.cpython-313.pyc create mode 100644 wechat_auto/core/__pycache__/window_manager.cpython-313.pyc create mode 100644 wechat_auto/core/desktop_automation.py create mode 100644 wechat_auto/core/executor/__init__.py create mode 100644 wechat_auto/core/executor/__pycache__/__init__.cpython-313.pyc create mode 100644 wechat_auto/core/executor/__pycache__/pyautogui_executor.cpython-313.pyc create mode 100644 wechat_auto/core/executor/__pycache__/qwen_ai_executor.cpython-313.pyc create mode 100644 wechat_auto/core/executor/pyautogui_executor.py create mode 100644 wechat_auto/core/executor/qwen_ai_executor.py create mode 100644 wechat_auto/core/task_scheduler.py create mode 100644 wechat_auto/core/window_manager.py create mode 100644 wechat_auto/images/wechat_icon.png create mode 100644 wechat_auto/images/一见星球.png create mode 100644 wechat_auto/images/小程序图标.png create mode 100644 wechat_auto/main.py create mode 100644 wechat_auto/models/__init__.py create mode 100644 wechat_auto/models/__pycache__/__init__.cpython-313.pyc create mode 100644 wechat_auto/models/__pycache__/activity.cpython-313.pyc create mode 100644 wechat_auto/models/activity.py create mode 100644 wechat_auto/requirements.txt create mode 100644 wechat_auto/utils/__init__.py create mode 100644 wechat_auto/utils/__pycache__/__init__.cpython-313.pyc create mode 100644 wechat_auto/utils/__pycache__/logger.cpython-313.pyc create mode 100644 wechat_auto/utils/__pycache__/retry.cpython-313.pyc create mode 100644 wechat_auto/utils/logger.py create mode 100644 wechat_auto/utils/retry.py diff --git a/test_wechat_click.py b/test_wechat_click.py new file mode 100644 index 0000000..e1ff653 --- /dev/null +++ b/test_wechat_click.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" +微信小程序自动化测试脚本 +功能:点击桌面微信图标 -> 点击小程序图标 -> 点击一见星球 +""" +import pyautogui +import time + +# 禁用pyautogui安全区域 +pyautogui.FAILSAFE = False +pyautogui.PAUSE = 0.5 + +# 屏幕尺寸 +SCREEN_WIDTH, SCREEN_HEIGHT = pyautogui.size() +print(f"屏幕尺寸: {SCREEN_WIDTH}x{SCREEN_HEIGHT}") + +# 微信窗口位置(已知) +WECHAT_WINDOW = { + 'x': 877, + 'y': 207, + 'width': 980, + 'height': 710 +} + +def click(x, y, description=""): + """点击指定坐标""" + pyautogui.click(x, y) + print(f"点击: ({x}, {y}) - {description}") + time.sleep(0.5) + +def main(): + print("=" * 50) + print("开始测试:微信 -> 小程序 -> 一见星球") + print("=" * 50) + + # 步骤1:点击桌面微信图标 + print("\n[步骤1] 点击桌面微信图标") + click(288, 162, "桌面微信图标") + time.sleep(3) + + # 步骤2:点击左侧小程序图标 + print("\n[步骤2] 点击左侧小程序图标") + wx = WECHAT_WINDOW['x'] + wy = WECHAT_WINDOW['y'] + ww = WECHAT_WINDOW['width'] + wh = WECHAT_WINDOW['height'] + + # 小程序图标在左侧边栏 + mini_x = wx + int(ww * 0.04) + mini_y = wy + int(wh * 0.22) + click(mini_x, mini_y, f"小程序图标 (窗口内相对位置: {int(ww*0.04)}, {int(wh*0.22)})") + time.sleep(2) + + # 步骤3:点击一见星球 + print("\n[步骤3] 点击一见星球小程序") + planet_x = wx + int(ww * 0.35) + planet_y = wy + int(wh * 0.25) + click(planet_x, planet_y, f"一见星球 (窗口内相对位置: {int(ww*0.35)}, {int(wh*0.25)})") + time.sleep(3) + + print("\n" + "=" * 50) + print("测试完成!请检查微信是否正确打开并进入一见星球") + print("=" * 50) + +if __name__ == "__main__": + # 等待5秒,让用户准备好 + print("将在5秒后开始执行,请将鼠标移开...") + time.sleep(5) + main() diff --git a/wechat_auto/.env b/wechat_auto/.env new file mode 100644 index 0000000..c56efec --- /dev/null +++ b/wechat_auto/.env @@ -0,0 +1,23 @@ +# FastAPI配置 +HOST=0.0.0.0 +PORT=8001 + +# 微信窗口名称 +WECHAT_WINDOW_NAME=微信 + +# 自动化配置 +CLICK_PAUSE=0.5 +FAILSAFE=true +ACTION_TIMEOUT=30 +MAX_RETRIES=3 +RETRY_BASE_DELAY=1.0 + +# 日志配置 +LOG_LEVEL=INFO +LOG_FILE=/tmp/wechat_auto.log + +# 截图保存目录 +SCREENSHOT_DIR=/tmp/wechat_screenshots + +# Qwen API配置 +DASHSCOPE_API_KEY=sk-81454152fd52459db710af56e14d94a6 diff --git a/wechat_auto/.env.example b/wechat_auto/.env.example new file mode 100644 index 0000000..7ca1119 --- /dev/null +++ b/wechat_auto/.env.example @@ -0,0 +1,23 @@ +# FastAPI配置 +HOST=0.0.0.0 +PORT=8000 + +# 微信窗口名称 +WECHAT_WINDOW_NAME=WeChat + +# 自动化配置 +CLICK_PAUSE=0.5 +FAILSAFE=true +ACTION_TIMEOUT=30 +MAX_RETRIES=3 +RETRY_BASE_DELAY=1.0 + +# 日志配置 +LOG_LEVEL=INFO +LOG_FILE=/tmp/wechat_auto.log + +# 截图保存目录 +SCREENSHOT_DIR=/tmp/wechat_screenshots + +# Qwen API配置 (必须设置) +DASHSCOPE_API_KEY=your_api_key_here diff --git a/wechat_auto/README.md b/wechat_auto/README.md new file mode 100644 index 0000000..06bce88 --- /dev/null +++ b/wechat_auto/README.md @@ -0,0 +1,380 @@ +## 微信小程序活动发布自动化系统 + +一个基于 **FastAPI + pyautogui + Qwen-VL** 的微信小程序活动发布自动化工具,用于在桌面端微信中自动打开指定小程序、填写活动信息并提交发布。 + +系统整体采用「**规则脚本方案(pyautogui)优先,AI 视觉方案(Qwen)兜底」的双方案架构,配合任务调度与重试机制,提高自动化发布成功率。 + +--- + +## 功能概览 + +- **REST API 服务** + - `POST /api/publish`:提交一个活动发布任务 + - `GET /api/task/{task_id}`:查询单个任务状态 + - `GET /api/tasks`:查看所有历史任务 + - `GET /api/health`:健康检查 + +- **双方案自动化执行** + - **方案 1:pyautogui 固定步骤** + - 通过 `xdotool` 查找并激活微信窗口 + - 使用相对坐标点击小程序入口 / 目标小程序 / 文本输入框 / 提交按钮 + - 自动输入活动标题和内容 + - 每个关键步骤都会截图留存 + - **方案 2:Qwen AI 视觉控制(备选)** + - 截图当前桌面,通过 Qwen-VL 分析界面 + - 模型返回 JSON 控制指令(点击、输入、滚动、快捷键等) + - 根据模型输出逐步操作,直到标记为 `done` 或超时 + +- **任务调度与重试** + - 每个发布请求会生成独立 `task_id` + - 支持多次重试(指数退避),优先尝试 pyautogui + - 若 pyautogui 多次失败,会自动切换到 Qwen 方案 + +- **日志与截图** + - 日志输出到控制台和文件(默认 `/tmp/wechat_auto.log`) + - 截图保存到指定目录(默认 `/tmp/wechat_screenshots`) + +--- + +## 目录结构 + +```text +wechat_auto/ + ├── main.py # FastAPI 应用入口(uvicorn 启动) + ├── config.py # 配置加载(基于 pydantic-settings) + ├── models/ + │ └── activity.py # 活动模型 & 任务状态模型 + ├── api/ + │ └── trigger.py # 对外 REST API 路由 + ├── core/ + │ ├── task_scheduler.py # 任务调度与双方案执行 + │ ├── window_manager.py # 微信窗口查找 / 激活 / 几何信息 + │ └── executor/ + │ ├── pyautogui_executor.py # 方案 1:规则脚本执行器 + │ └── qwen_ai_executor.py # 方案 2:Qwen AI 执行器 + ├── utils/ + │ ├── logger.py # 日志初始化 + │ └── retry.py # 同步 / 异步重试装饰器 + ├── .env.example # 环境变量示例文件(不含真实密钥) + ├── .env # 实际环境配置(**请勿提交到仓库**) + └── requirements.txt # Python 依赖 +``` + +--- + +## 环境要求 + +- **操作系统** + - 建议:Linux 桌面环境(X11),当前实现依赖 `xdotool`、`scrot` 等工具 +- **桌面环境** + - 已安装并登录 PC 版微信(窗口标题默认是 `WeChat`,可通过配置修改) +- **系统工具依赖** + - `xdotool`:窗口查找、激活、获取几何信息 + - `scrot`:桌面截图(若无则退回 `pyautogui.screenshot`) + +在 Debian/Ubuntu 上可通过下面命令安装: + +```bash +sudo apt update +sudo apt install -y xdotool scrot +``` + +- **Python 环境** + - Python 3.10+(建议使用虚拟环境) + +--- + +## 安装步骤 + +### 1. 克隆项目并进入目录 + +```bash +cd /home/quant/data/dev/mini_auto +cd wechat_auto +``` + +(如果你是在其他目录,请根据实际路径调整。) + +### 2. 创建并激活虚拟环境(推荐) + +```bash +python -m venv .venv +source .venv/bin/activate +``` + +Windows PowerShell: + +```powershell +python -m venv .venv +.venv\Scripts\Activate.ps1 +``` + +### 3. 安装 Python 依赖 + +```bash +pip install -r requirements.txt +``` + +> 如需使用 Qwen AI 方案,请确保能正常访问 DashScope 接口。 + +--- + +## 配置说明 + +项目通过 `pydantic-settings` 从 `.env` 文件和系统环境变量中加载配置,对应定义见 `config.py` 中 `Settings` 类。 + +### 1. 创建 `.env` + +以 `.env.example` 为模板复制一份: + +```bash +cp .env.example .env +``` + +然后根据实际情况修改 `.env` 中的配置项。 + +### 2. 关键配置项 + +- **FastAPI 服务** + - `HOST`:服务监听地址(默认 `0.0.0.0`) + - `PORT`:服务端口(例如 `8000` 或 `8001`) + +- **微信窗口相关** + - `WECHAT_WINDOW_NAME`:微信窗口标题,默认 `WeChat` + 如果你的微信窗口标题不同(例如有多语言 / 带后缀),需要改成实际名称。 + +- **自动化行为** + - `CLICK_PAUSE`:每次 pyautogui 操作之间的暂停秒数 + - `FAILSAFE`:是否开启边角移动触发 FailSafe 保护 + - `ACTION_TIMEOUT`:单步骤操作超时时间(秒) + - `MAX_RETRIES`:重试次数(用于调度和重试装饰器) + - `RETRY_BASE_DELAY`:重试基础延时(秒,配合指数退避) + +- **日志与截图** + - `LOG_LEVEL`:日志级别(如 `INFO` / `DEBUG`) + - `LOG_FILE`:日志文件路径(默认 `/tmp/wechat_auto.log`) + - `SCREENSHOT_DIR`:截图保存目录(默认 `/tmp/wechat_screenshots`) + +- **Qwen API(如需启用 AI 方案)** + - `DASHSCOPE_API_KEY`:DashScope 的 API Key + - 在 `.env.example` 中为占位值,请在自己的 `.env` 中改成真实密钥 + - **安全提示:不要把包含真实密钥的 `.env` 提交到代码仓库** + +--- + +## 运行服务 + +确保: +- 已激活虚拟环境(如有) +- `.env` 配置正确 +- 微信 PC 客户端已启动并登录 +- DISPLAY 环境变量可用(例如 `:0`) + +### 1. 直接运行入口脚本 + +在 `wechat_auto` 目录下执行: + +```bash +python main.py +``` + +或显式调用: + +```bash +python -m wechat_auto.main +``` + +启动成功后,日志中会输出类似信息: + +- 服务地址: `http://:` +- API 文档: `http://:/docs` + +例如: + +```text +服务地址: http://0.0.0.0:8000 +API文档: http://0.0.0.0:8000/docs +``` + +也可以手动启动 uvicorn(等价于入口里做的事情): + +```bash +uvicorn wechat_auto.main:app --host 0.0.0.0 --port 8000 --log-level info +``` + +--- + +## API 使用说明 + +服务启动后,可以通过 swagger 文档直接调试: +`http://:/docs` + +### 1. 发布活动:`POST /api/publish` + +- **请求体模型**:`ActivityModel` + +示例 JSON: + +```json +{ + "title": "周末优惠活动", + "content": "全场 8 折优惠,会员额外 9 折。", + "start_time": "2026-03-10 10:00:00", + "end_time": "2026-03-15 22:00:00", + "images": ["/tmp/promotion.jpg"], + "location": "线上", + "organizer": "某某公司" +} +``` + +- **响应示例**: + +```json +{ + "code": 200, + "message": "任务已提交", + "data": { + "task_id": "xxx-uuid", + "status": "success", + "method": "pyautogui", + "error": null + } +} +``` + +> 说明: +> - `method` 字段指示实际使用的执行方案,可能为 `pyautogui` 或 `qwen_ai` +> - 如果任务执行失败,`status` 会是 `failed`,`error` 中包含原因 + +### 2. 查询任务状态:`GET /api/task/{task_id}` + +路径参数: +- `task_id`:发布任务返回的 `task_id` + +返回: + +```json +{ + "code": 200, + "data": { + "task_id": "xxx-uuid", + "status": "success", + "method": "pyautogui", + "error": null, + "created_at": "2026-03-04T12:00:00", + "updated_at": "2026-03-04T12:00:15" + } +} +``` + +### 3. 查询所有任务:`GET /api/tasks` + +返回当前进程内维护的所有任务状态列表: + +```json +{ + "code": 200, + "data": [ + { + "task_id": "xxx-uuid", + "status": "success", + "method": "pyautogui", + "error": null, + "created_at": "...", + "updated_at": "..." + } + ] +} +``` + +> 注意:任务状态保存在内存中,重启进程后历史任务不会保留。 + +### 4. 健康检查:`GET /api/health` + +简单返回服务状态: + +```json +{ + "status": "ok", + "service": "wechat_auto" +} +``` + +--- + +## 执行流程与架构简要说明 + +1. **HTTP 请求进入** + - `POST /api/publish` 接收 `ActivityModel`,调用 `TaskScheduler.publish_activity` + +2. **任务调度** + - 创建 `task_id` 与 `TaskStatus`,状态置为 `running` + - 调用 `_execute_with_fallback`,执行双方案逻辑 + +3. **方案 1:pyautogui 执行** + - 通过 `WindowManager` 使用 `xdotool` 查找微信窗口 + - 如果未找到微信窗口或无法获取几何信息,则抛出异常 + - 根据预设相对坐标依次执行: + - 点击小程序入口 → 点击目标小程序 → 点击发布按钮 + - 填写标题与内容 → 点击提交按钮 + - 每一步执行前后会截图记录 + +4. **方案 2:Qwen AI 执行(备选)** + - 若 pyautogui 连续多次失败,则切换到 Qwen 方案 + - 周期性地对当前屏幕截图并编码为 base64 + - 将截图和任务描述一并发送给 Qwen-VL 模型 + - 解析模型返回的 JSON(`action` + `params`),执行对应鼠标 / 键盘操作 + - 若模型返回 `action = "done"` 则认为任务完成,否则在最大步数内继续 + +5. **状态更新与返回** + - 根据执行结果更新 `TaskStatus`(`success` 或 `failed`) + - 将 `status`、`method`、`error` 等字段返回给调用方 + +--- + +## 常见问题与排查建议 + +- **微信窗口未找到** + - 确认已登录 PC 版微信,且窗口标题与 `WECHAT_WINDOW_NAME` 配置一致 + - 终端手动执行: + ```bash + xdotool search --name WeChat + ``` + 确认能返回窗口 ID。 + +- **截图目录 / 日志目录权限问题** + - 确保当前用户对 `SCREENSHOT_DIR` 和 `LOG_FILE` 目录有读写权限 + - 如有需要,可在 `.env` 中改为当前用户有权限的路径 + +- **Qwen API 调用失败** + - 确认 `DASHSCOPE_API_KEY` 已正确配置且未过期 + - 检查服务器是否能访问 DashScope 接口 + - 查看日志中 `调用Qwen API失败` 相关报错信息 + +- **坐标不匹配 / 点击错位** + - 目前 pyautogui 方案使用固定相对坐标,适合「窗口大小 / DPI 固定」的场景 + - 如果你使用不同分辨率或窗口布局,可能需要自行调整 `_get_activity_steps` 中的相对坐标 + - 可以通过日志和截图对照,修正每一步操作的位置 + +--- + +## 开发与二次扩展建议 + +- 如需适配不同的小程序或表单结构: + - 可以扩展 `PyAutoGUIExecutor._get_activity_steps`,根据活动字段动态拼装步骤 + - 或为不同小程序编写不同的步骤模板 + +- 如需增强 Qwen 方案: + - 可以在 `QwenAIExecutor._build_prompt` 中添加更详细的 UI 说明和约束 + - 增加对更多 `action` 类型的支持(例如拖拽、选择框等) + +- 如需持久化任务状态: + - 可以在 `TaskScheduler` 中将 `tasks` 从内存结构替换为数据库存储(如 SQLite / Redis) + +--- + +## 免责声明 + +本项目涉及对桌面环境和微信客户端的自动化控制,请在遵守微信相关用户协议和所在地区法律法规的前提下使用。 +如用于生产环境,请务必充分测试自动化脚本的稳定性与安全性,避免误操作造成损失。 + diff --git a/wechat_auto/__init__.py b/wechat_auto/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wechat_auto/__pycache__/__init__.cpython-313.pyc b/wechat_auto/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83b1681bbaab051d9044d734b7bb608864e8e8ab GIT binary patch literal 151 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 ARR910 literal 0 HcmV?d00001 diff --git a/wechat_auto/api/trigger.py b/wechat_auto/api/trigger.py new file mode 100644 index 0000000..e79bd79 --- /dev/null +++ b/wechat_auto/api/trigger.py @@ -0,0 +1,48 @@ +from fastapi import APIRouter, BackgroundTasks, HTTPException +from wechat_auto.models.activity import ActivityModel, TaskStatus +from wechat_auto.core.task_scheduler import task_scheduler +from wechat_auto.utils.logger import logger + +router = APIRouter() + + +@router.post("/api/publish", response_model=dict) +async def publish_activity(activity: ActivityModel, background_tasks: BackgroundTasks): + logger.info(f"收到发布活动请求: {activity.title}") + + result = await task_scheduler.publish_activity(activity) + + return { + "code": 200 if result["status"] == "success" else 500, + "message": "任务已提交" if result["status"] == "success" else "任务失败", + "data": result + } + + +@router.get("/api/task/{task_id}", response_model=dict) +async def get_task_status(task_id: str): + task = task_scheduler.get_task_status(task_id) + if not task: + raise HTTPException(status_code=404, detail="任务不存在") + + return { + "code": 200, + "data": task + } + + +@router.get("/api/tasks", response_model=dict) +async def list_tasks(): + tasks = task_scheduler.list_tasks() + return { + "code": 200, + "data": tasks + } + + +@router.get("/api/health") +async def health_check(): + return { + "status": "ok", + "service": "wechat_auto" + } diff --git a/wechat_auto/capture_icons.py b/wechat_auto/capture_icons.py new file mode 100644 index 0000000..bc774e5 --- /dev/null +++ b/wechat_auto/capture_icons.py @@ -0,0 +1,57 @@ +""" +图标捕获工具 +用于捕获微信、小程序图标、一见星球等图标 +""" +import pyautogui +import time +from pathlib import Path + + +def capture_icon(icon_name: str, delay: int = 5): + """ + 捕获图标 + 1. 运行此函数 + 2. 在延迟时间内将鼠标移动到目标图标上 + 3. 程序会自动截图保存 + """ + template_dir = Path(__file__).parent / "images" + template_dir.mkdir(parents=True, exist_ok=True) + + print(f"请在 {delay} 秒内将鼠标移动到要捕获的图标上...") + time.sleep(delay) + + x, y = pyautogui.position() + print(f"当前鼠标位置:({x}, {y})") + + screenshot = pyautogui.screenshot(region=(x-30, y-30, 60, 60)) + filepath = template_dir / f"{icon_name}.png" + screenshot.save(str(filepath)) + print(f"图标已保存到:{filepath}") + + +if __name__ == "__main__": + print("=== 微信图标捕获工具 ===\n") + + while True: + print("\n请选择要捕获的图标:") + print("1. 桌面微信图标 (wechat_icon)") + print("2. 小程序图标 (miniprogram_icon)") + print("3. 一见星球小程序 (yijian_planet_icon)") + print("4. 手动指定名称") + print("0. 退出") + + choice = input("\n选择:") + + if choice == "0": + break + elif choice == "1": + capture_icon("wechat_icon", delay=5) + elif choice == "2": + capture_icon("miniprogram_icon", delay=5) + elif choice == "3": + capture_icon("yijian_planet_icon", delay=5) + elif choice == "4": + name = input("输入图标名称(不带扩展名): ") + capture_icon(name, delay=5) + else: + print("无效选择,请重试") diff --git a/wechat_auto/config.py b/wechat_auto/config.py new file mode 100644 index 0000000..caba689 --- /dev/null +++ b/wechat_auto/config.py @@ -0,0 +1,39 @@ +from pydantic_settings import BaseSettings +from typing import Optional +import os +from pathlib import Path + + +class Settings(BaseSettings): + # FastAPI配置 + host: str = "0.0.0.0" + port: int = 8000 + + # Qwen API配置 + dashscope_api_key: Optional[str] = None + + # 微信窗口配置 + wechat_window_name: str = "WeChat" + + # 自动化配置 + click_pause: float = 0.5 + failsafe: bool = True + action_timeout: int = 30 + + # 重试配置 + max_retries: int = 3 + retry_base_delay: float = 1.0 + + # 日志配置 + log_level: str = "INFO" + log_file: str = "/tmp/wechat_auto.log" + + # 截图保存目录 + screenshot_dir: str = "/tmp/wechat_screenshots" + + class Config: + env_file = str(Path(__file__).parent / ".env") + extra = "allow" + + +settings = Settings() diff --git a/wechat_auto/core/__init__.py b/wechat_auto/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wechat_auto/core/__pycache__/__init__.cpython-313.pyc b/wechat_auto/core/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8e4cf50fb8e05c0fe044116e1b216a927c31e3ed GIT binary patch literal 156 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 literal 0 HcmV?d00001 diff --git a/wechat_auto/core/__pycache__/window_manager.cpython-313.pyc b/wechat_auto/core/__pycache__/window_manager.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..194dfcaf3be0550da8025087116d7c39a2dd1b96 GIT binary patch literal 6630 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 Tuple[int, int]: + """获取屏幕尺寸""" + return pyautogui.size() + + def open_wechat_and_miniprogram(self) -> bool: + """ + 打开微信并进入一见星球小程序 + 流程: + 1. 点击桌面微信图标 + 2. 等待微信窗口 + 3. 点击左侧小程序图标 + 4. 点击一见星球 + """ + screen_width, screen_height = self.get_screen_size() + logger.info(f"屏幕尺寸:{screen_width}x{screen_height}") + + self.screenshot("step0_start") + + # 步骤1:点击桌面微信图标 + logger.info("步骤1:点击桌面微信图标") + self.click_at(int(screen_width * 0.15), int(screen_height * 0.15)) + time.sleep(4) + self.screenshot("step1_click_wechat") + + # 步骤2:点击左侧小程序图标 + # 微信窗口内相对位置 + wx = self.wechat_window['x'] + wy = self.wechat_window['y'] + ww = self.wechat_window['width'] + wh = self.wechat_window['height'] + + logger.info("步骤2:点击左侧小程序图标") + # 小程序图标在左侧边栏,约为窗口宽度的4%,高度的22% + mini_x = wx + int(ww * 0.04) + mini_y = wy + int(wh * 0.22) + self.click_at(mini_x, mini_y) + time.sleep(2) + self.screenshot("step2_miniprogram_panel") + + # 步骤3:点击一见星球小程序 + # 一见星球在主面板中,约为窗口宽度的35%,高度的25% + logger.info("步骤3:点击一见星球小程序") + planet_x = wx + int(ww * 0.35) + planet_y = wy + int(wh * 0.25) + self.click_at(planet_x, planet_y) + time.sleep(3) + self.screenshot("step3_yijian_planet") + + logger.info("✅ 已成功打开一见星球小程序!") + return True + + +if __name__ == "__main__": + automation = DesktopAutomation() + + print("=" * 50) + print("开始执行:打开微信 -> 进入小程序 -> 一见星球") + print("=" * 50) + + result = automation.open_wechat_and_miniprogram() + + if result: + print("\n✅ 成功完成!") + else: + print("\n❌ 执行失败,请检查日志") diff --git a/wechat_auto/core/executor/__init__.py b/wechat_auto/core/executor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wechat_auto/core/executor/__pycache__/__init__.cpython-313.pyc b/wechat_auto/core/executor/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..81197ce69ade7cc30078d75243e0d1df0fadaafc GIT binary patch literal 165 zcmey&%ge<81heih&jitrK?DpiLK&Y~fQ+dO=?t2Tek&P@n1H;`AgNm}`WgATsrrSb ziFqaZDTyVC`YEYp`nj2TnemCGCHea0smU3MB@lLUeo?A^YDH=?P@+gbJ|3v3BtBlR ipz;=nO>TZlX-=wL5i8J8kfp^S#z$sGM#ds$APWG=kt${Y literal 0 HcmV?d00001 diff --git a/wechat_auto/core/executor/__pycache__/pyautogui_executor.cpython-313.pyc b/wechat_auto/core/executor/__pycache__/pyautogui_executor.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2df2e8be4423ab95b376078c05d8bc94d3ab73e2 GIT binary patch literal 9476 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 B`9c5y literal 0 HcmV?d00001 diff --git a/wechat_auto/core/executor/__pycache__/qwen_ai_executor.cpython-313.pyc b/wechat_auto/core/executor/__pycache__/qwen_ai_executor.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3a28669336837fb4622815a8c762e02c5e4d1f13 GIT binary patch literal 9841 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�L%5Y5V|vDu=@%XnH|)fL8&L9N8Ip_2l%=E;U#M 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 kf0C*wsk%vWZ<3tv$V1 literal 0 HcmV?d00001 diff --git a/wechat_auto/core/executor/pyautogui_executor.py b/wechat_auto/core/executor/pyautogui_executor.py new file mode 100644 index 0000000..03c6a5f --- /dev/null +++ b/wechat_auto/core/executor/pyautogui_executor.py @@ -0,0 +1,156 @@ +import pyautogui +import time +import subprocess +from pathlib import Path +from typing import Optional, Dict, Any, List + +BASE_DIR = Path(__file__).parent.parent.parent +import sys +sys.path.insert(0, str(BASE_DIR)) + +from wechat_auto.config import settings +from wechat_auto.utils.logger import logger +from wechat_auto.utils.retry import sync_retry +from wechat_auto.models.activity import ActivityModel + + +class PyAutoGUIExecutor: + def __init__(self): + pyautogui.FAILSAFE = settings.failsafe + pyautogui.PAUSE = settings.click_pause + self.screenshot_dir = Path(settings.screenshot_dir) + self.screenshot_dir.mkdir(parents=True, exist_ok=True) + + # 微信窗口已知位置(从xwininfo获取) + self.wechat_window = { + 'x': 877, + 'y': 207, + 'width': 980, + 'height': 710 + } + + def click_at(self, x: int, y: int, button: str = 'left'): + """在指定位置点击""" + pyautogui.click(x, y, button=button) + logger.info(f"点击坐标:({x}, {y})") + + def screenshot(self, name: str = None): + """截图保存""" + timestamp = time.strftime("%Y%m%d_%H%M%S") + filename = f"{name or 'action'}_{timestamp}.png" + filepath = self.screenshot_dir / filename + try: + subprocess.run(['scrot', str(filepath)], capture_output=True, timeout=5) + except: + pass + logger.info(f"截图:{filepath}") + + def _input_text(self, text: str): + pyautogui.write(text, interval=0.05) + logger.info(f"输入文本:{text[:30]}...") + + def _wait(self, seconds: float = 1.0): + time.sleep(seconds) + + @sync_retry(max_retries=2, base_delay=2.0) + def execute(self, activity: ActivityModel) -> Dict[str, Any]: + logger.info(f"开始执行 pyautogui 方案,发布活动:{activity.title}") + + self.screenshot("start") + + steps = self._get_publish_steps(activity) + + for i, step in enumerate(steps): + logger.info(f"执行步骤 {i+1}/{len(steps)}: {step['description']}") + try: + step['action']() + self.screenshot(f"step_{i+1}") + self._wait(step.get('wait_after', 1.0)) + except Exception as e: + logger.error(f"步骤 {i+1} 失败:{e}") + self.screenshot(f"error_step_{i+1}") + raise + + logger.info("pyautogui 方案执行成功") + return {"status": "success", "method": "pyautogui"} + + def _get_publish_steps(self, activity: ActivityModel) -> List[Dict]: + wx = self.wechat_window['x'] + wy = self.wechat_window['y'] + ww = self.wechat_window['width'] + wh = self.wechat_window['height'] + + return [ + { + 'description': '点击桌面微信图标', + 'action': lambda: self.click_at(288, 162), + 'wait_after': 4.0 + }, + { + 'description': '点击左侧小程序图标', + 'action': lambda: self.click_at(wx + int(ww * 0.04), wy + int(wh * 0.22)), + 'wait_after': 2.0 + }, + { + 'description': '点击一见星球小程序', + 'action': lambda: self.click_at(wx + int(ww * 0.35), wy + int(wh * 0.25)), + 'wait_after': 3.0 + }, + { + 'description': '点击发布活动按钮', + 'action': lambda: self.click_at(wx + int(ww * 0.5), wy + int(wh * 0.12)), + 'wait_after': 2.0 + }, + { + 'description': '输入活动标题', + 'action': lambda: self._input_title(activity.title), + 'wait_after': 1.0 + }, + { + 'description': '输入活动内容', + 'action': lambda: self._input_content(activity.content), + 'wait_after': 1.0 + }, + { + 'description': '点击提交按钮', + 'action': lambda: self._click_submit(), + 'wait_after': 2.0 + }, + ] + + def _input_title(self, title: str): + """输入活动标题""" + wx = self.wechat_window['x'] + wy = self.wechat_window['y'] + ww = self.wechat_window['width'] + wh = self.wechat_window['height'] + + # 点击标题输入框 + self.click_at(wx + int(ww * 0.3), wy + int(wh * 0.25)) + self._wait(0.5) + self._input_text(title) + logger.info(f"已输入标题:{title}") + + def _input_content(self, content: str): + """输入活动内容""" + wx = self.wechat_window['x'] + wy = self.wechat_window['y'] + ww = self.wechat_window['width'] + wh = self.wechat_window['height'] + + # 点击内容输入框 + self.click_at(wx + int(ww * 0.3), wy + int(wh * 0.4)) + self._wait(0.5) + self._input_text(content) + logger.info(f"已输入内容:{content[:30]}...") + + def _click_submit(self): + """点击提交按钮""" + wx = self.wechat_window['x'] + wy = self.wechat_window['y'] + ww = self.wechat_window['width'] + wh = self.wechat_window['height'] + + # 点击提交按钮 + self.click_at(wx + int(ww * 0.7), wy + int(wh * 0.8)) + logger.info("已点击提交按钮") diff --git a/wechat_auto/core/executor/qwen_ai_executor.py b/wechat_auto/core/executor/qwen_ai_executor.py new file mode 100644 index 0000000..b548b88 --- /dev/null +++ b/wechat_auto/core/executor/qwen_ai_executor.py @@ -0,0 +1,197 @@ +import os +import json +import base64 +import asyncio +import subprocess +import time +from pathlib import Path +from typing import Dict, Any, Optional +import pyautogui +import requests +from wechat_auto.config import settings +from wechat_auto.utils.logger import logger +from wechat_auto.models.activity import ActivityModel + + +class QwenAIExecutor: + def __init__(self, api_key: str = None): + self.api_key = api_key or os.getenv("DASHSCOPE_API_KEY") or settings.dashscope_api_key + if not self.api_key: + raise ValueError("未配置DASHSCOPE_API_KEY") + + self.endpoint = "https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation" + self.model = "qwen-vl-plus" + self.screenshot_dir = Path(settings.screenshot_dir) + self.screenshot_dir.mkdir(parents=True, exist_ok=True) + self.max_steps = 15 + + def _screenshot(self) -> str: + timestamp = time.strftime("%Y%m%d_%H%M%S") + filepath = self.screenshot_dir / f"ai_step_{timestamp}.png" + + try: + subprocess.run( + ['scrot', str(filepath)], + capture_output=True, + timeout=5 + ) + except FileNotFoundError: + pyautogui.screenshot(str(filepath)) + + logger.debug(f"AI截图: {filepath}") + return str(filepath) + + def _encode_image(self, image_path: str) -> str: + with open(image_path, 'rb') as f: + return base64.b64encode(f.read()).decode('utf-8') + + def _call_qwen(self, prompt: str, image_base64: str) -> Dict[str, Any]: + headers = { + 'Authorization': f'Bearer {self.api_key}', + 'Content-Type': 'application/json' + } + + payload = { + "model": self.model, + "input": { + "messages": [ + { + "role": "user", + "content": [ + {"image": f"data:image/png;base64,{image_base64}"}, + {"text": prompt} + ] + } + ] + }, + "parameters": { + "max_tokens": 2000 + } + } + + response = requests.post(self.endpoint, headers=headers, json=payload, timeout=60) + response.raise_for_status() + + result = response.json() + content = result['output']['choices'][0]['message']['content'] + + try: + return json.loads(content) + except json.JSONDecodeError: + return {"action": "continue", "reason": content} + + def _execute_action(self, action: str, params: Dict[str, Any]): + if action == "click": + x, y = params.get('x', 0), params.get('y', 0) + pyautogui.click(x, y) + logger.info(f"AI点击: ({x}, {y})") + + elif action == "type": + text = params.get('text', '') + pyautogui.write(text, interval=0.05) + logger.info(f"AI输入: {text[:20]}...") + + elif action == "press": + key = params.get('key', '') + pyautogui.press(key) + logger.info(f"AI按键: {key}") + + elif action == "wait": + seconds = params.get('seconds', 1) + time.sleep(seconds) + logger.info(f"AI等待: {seconds}秒") + + elif action == "hotkey": + keys = params.get('keys', []) + pyautogui.hotkey(*keys) + logger.info(f"AI快捷键: {keys}") + + elif action == "scroll": + clicks = params.get('clicks', 0) + pyautogui.scroll(clicks) + logger.info(f"AI滚动: {clicks}") + + elif action == "done": + logger.info("AI任务完成") + + elif action == "continue": + logger.info(f"AI继续: {params.get('reason', '无原因')}") + + else: + logger.warning(f"未知AI动作: {action}") + + async def execute(self, activity: ActivityModel) -> Dict[str, Any]: + logger.info(f"开始执行Qwen AI方案,发布活动: {activity.title}") + + prompt = self._build_prompt(activity) + + for step in range(self.max_steps): + logger.info(f"AI执行步骤 {step + 1}/{self.max_steps}") + + screenshot_path = self._screenshot() + image_b64 = self._encode_image(screenshot_path) + + try: + result = self._call_qwen(prompt, image_b64) + except Exception as e: + logger.error(f"调用Qwen API失败: {e}") + await asyncio.sleep(2) + continue + + action = result.get('action', 'continue') + params = result.get('params', {}) + + self._execute_action(action, params) + + if action == "done": + logger.info("Qwen AI方案执行成功") + return {"status": "success", "method": "qwen_ai"} + + await asyncio.sleep(1) + + logger.error("Qwen AI方案执行超时") + return {"status": "failed", "error": "执行超时"} + + def _build_prompt(self, activity: ActivityModel) -> str: + prompt = f"""你正在控制一台Linux电脑的微信客户端。请根据当前屏幕内容,帮我完成以下任务: + +任务:在微信小程序中发布一个活动 + +活动信息: +- 标题:{activity.title} +- 内容:{activity.content} +""" + + if activity.start_time: + prompt += f"- 开始时间:{activity.start_time}\n" + if activity.end_time: + prompt += f"- 结束时间:{activity.end_time}\n" + if activity.location: + prompt += f"- 地点:{activity.location}\n" + + prompt += """ +请分析当前屏幕,输出JSON格式的下一个操作指令: + +```json +{ + "action": "click|type|press|wait|scroll|hotkey|done|continue", + "params": { + "x": 100, + "y": 200, + "text": "要输入的文字", + "key": "enter", + "seconds": 1, + "clicks": -300, + "keys": ["ctrl", "v"] + }, + "reason": "操作原因说明" +} +``` + +注意事项: +1. 点击位置使用绝对坐标 +2. 如果任务已完成,action设为"done" +3. 如果需要继续下一步,action设为"continue" +4. 先找到并点击小程序入口,然后找到目标小程序,点击发布活动按钮,填写表单并提交 +""" + return prompt diff --git a/wechat_auto/core/task_scheduler.py b/wechat_auto/core/task_scheduler.py new file mode 100644 index 0000000..aa8c755 --- /dev/null +++ b/wechat_auto/core/task_scheduler.py @@ -0,0 +1,97 @@ +import asyncio +import uuid +from datetime import datetime +from typing import Dict, Any, Optional +from wechat_auto.models.activity import ActivityModel, TaskStatus +from wechat_auto.core.executor.pyautogui_executor import PyAutoGUIExecutor +from wechat_auto.core.executor.qwen_ai_executor import QwenAIExecutor +from wechat_auto.utils.logger import logger +from wechat_auto.config import settings + + +class TaskScheduler: + def __init__(self): + self.primary = PyAutoGUIExecutor() + self.secondary = QwenAIExecutor() + self.max_retries = settings.max_retries + self.tasks: Dict[str, TaskStatus] = {} + + async def publish_activity(self, activity: ActivityModel) -> Dict[str, Any]: + task_id = str(uuid.uuid4()) + logger.info(f"创建任务 {task_id},发布活动: {activity.title}") + + task_status = TaskStatus( + task_id=task_id, + status="running", + created_at=datetime.now(), + updated_at=datetime.now() + ) + self.tasks[task_id] = task_status + + result = await self._execute_with_fallback(activity) + + task_status.status = result.get("status", "failed") + task_status.method = result.get("method") + task_status.error = result.get("error") + task_status.updated_at = datetime.now() + + return { + "task_id": task_id, + "status": task_status.status, + "method": task_status.method, + "error": task_status.error + } + + async def _execute_with_fallback(self, activity: ActivityModel) -> Dict[str, Any]: + logger.info("=" * 50) + logger.info("开始执行方案1: pyautogui") + logger.info("=" * 50) + + for attempt in range(1, self.max_retries + 1): + try: + result = await asyncio.to_thread(self.primary.execute, activity) + if result.get("status") == "success": + logger.info(f"pyautogui方案成功") + return result + except Exception as e: + logger.warning(f"pyautogui方案第{attempt}次失败: {e}") + + if attempt < self.max_retries: + delay = settings.retry_base_delay * (2 ** (attempt - 1)) + logger.info(f"{delay}秒后重试...") + await asyncio.sleep(delay) + + logger.warning("pyautogui方案全部失败,切换到备选方案") + + logger.info("=" * 50) + logger.info("开始执行方案2: Qwen AI") + logger.info("=" * 50) + + for attempt in range(1, self.max_retries + 1): + try: + result = await self.secondary.execute(activity) + if result.get("status") == "success": + logger.info(f"Qwen AI方案成功") + return result + except Exception as e: + logger.warning(f"Qwen AI方案第{attempt}次失败: {e}") + + if attempt < self.max_retries: + delay = settings.retry_base_delay * (2 ** (attempt - 1)) + logger.info(f"{delay}秒后重试...") + await asyncio.sleep(delay) + + logger.error("所有方案均失败") + return { + "status": "failed", + "error": "pyautogui和Qwen AI方案均失败" + } + + def get_task_status(self, task_id: str) -> Optional[TaskStatus]: + return self.tasks.get(task_id) + + def list_tasks(self) -> list[TaskStatus]: + return list(self.tasks.values()) + + +task_scheduler = TaskScheduler() diff --git a/wechat_auto/core/window_manager.py b/wechat_auto/core/window_manager.py new file mode 100644 index 0000000..407b3af --- /dev/null +++ b/wechat_auto/core/window_manager.py @@ -0,0 +1,130 @@ +import subprocess +import time +from dataclasses import dataclass +from typing import Optional, Tuple +from wechat_auto.utils.logger import logger +from wechat_auto.config import settings + + +@dataclass +class WindowPosition: + x: int + y: int + width: int + height: int + + @property + def center(self) -> Tuple[int, int]: + return (self.x + self.width // 2, self.y + self.height // 2) + + def relative_to(self, rel_x: int, rel_y: int) -> Tuple[int, int]: + return (self.x + rel_x, self.y + rel_y) + + +class WindowManager: + def __init__(self, window_name: str = None): + self.window_name = window_name or settings.wechat_window_name + + def find_window(self, timeout: float = 10.0) -> Optional[str]: + start_time = time.time() + + search_methods = [ + ['xdotool', 'search', '--name', self.window_name], + ['xdotool', 'search', '--class', 'wechat'], + ['xdotool', 'search', '--classname', 'wechat'], + ] + + while time.time() - start_time < timeout: + for cmd in search_methods: + try: + result = subprocess.run( + cmd, + capture_output=True, + text=True, + timeout=5 + ) + window_id = result.stdout.strip().split('\n')[0] + if window_id: + logger.info(f"找到窗口: {self.window_name}, ID: {window_id}") + return window_id + except Exception as e: + logger.debug(f"搜索方式 {cmd} 失败: {e}") + + time.sleep(0.5) + + logger.error(f"未找到窗口: {self.window_name}") + return None + + def get_window_position(self, window_id: str = None) -> Optional[WindowPosition]: + if not window_id: + window_id = self.find_window() + if not window_id: + return None + + try: + result = subprocess.run( + ['xdotool', 'getwindowgeometry', window_id], + capture_output=True, + text=True, + timeout=5 + ) + + output = result.stdout + x = y = width = height = 0 + + for line in output.split('\n'): + line = line.strip() + if line.startswith('Position:'): + parts = line.split(':')[1].strip().split(',') + x = int(parts[0]) + y = int(parts[1]) + elif line.startswith('Geometry:'): + parts = line.split(':')[1].strip().split('x') + width = int(parts[0]) + height = int(parts[1]) + + if x or y or width or height: + pos = WindowPosition(x=x, y=y, width=width, height=height) + logger.info(f"窗口位置: {pos}") + return pos + + except Exception as e: + logger.error(f"获取窗口位置失败: {e}") + + return None + + def activate_window(self, window_id: str = None) -> bool: + if not window_id: + window_id = self.find_window() + if not window_id: + return False + + try: + subprocess.run( + ['xdotool', 'windowactivate', window_id], + capture_output=True, + timeout=5 + ) + time.sleep(0.5) + logger.info(f"窗口已激活: {window_id}") + return True + except Exception as e: + logger.error(f"激活窗口失败: {e}") + return False + + def is_window_visible(self, window_id: str = None) -> bool: + if not window_id: + window_id = self.find_window() + if not window_id: + return False + + try: + result = subprocess.run( + ['xdotool', 'getwindowname', window_id], + capture_output=True, + text=True, + timeout=5 + ) + return bool(result.stdout.strip()) + except Exception: + return False diff --git a/wechat_auto/images/wechat_icon.png b/wechat_auto/images/wechat_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..37a1d48027743409befd89450a9339b58c3eab02 GIT binary patch literal 3033 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

Q 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 literal 0 HcmV?d00001 diff --git a/wechat_auto/images/小程序图标.png b/wechat_auto/images/小程序图标.png new file mode 100644 index 0000000000000000000000000000000000000000..c5a942d23a0f598fa0786c885c83f903837f9edb GIT binary patch literal 40193 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 literal 0 HcmV?d00001 diff --git a/wechat_auto/main.py b/wechat_auto/main.py new file mode 100644 index 0000000..ea1261f --- /dev/null +++ b/wechat_auto/main.py @@ -0,0 +1,51 @@ +import os +import sys +import uvicorn +from fastapi import FastAPI +from pathlib import Path + +BASE_DIR = Path(__file__).parent.parent +sys.path.insert(0, str(BASE_DIR)) + +from wechat_auto.config import settings +from wechat_auto.api.trigger import router as trigger_router +from wechat_auto.utils.logger import logger + + +app = FastAPI( + title="微信小程序活动发布自动化系统", + description="使用pyautogui + Qwen AI双方案自动化发布小程序活动", + version="1.0.0" +) + +app.include_router(trigger_router) + + +@app.on_event("startup") +async def startup_event(): + logger.info("=" * 50) + logger.info("微信小程序活动发布自动化系统启动") + logger.info(f"服务地址: http://{settings.host}:{settings.port}") + logger.info(f"API文档: http://{settings.host}:{settings.port}/docs") + logger.info("=" * 50) + + +@app.on_event("shutdown") +async def shutdown_event(): + logger.info("服务关闭") + + +def main(): + os.environ.setdefault("DISPLAY", os.getenv("DISPLAY", ":0")) + + uvicorn.run( + "wechat_auto.main:app", + host=settings.host, + port=settings.port, + reload=False, + log_level=settings.log_level.lower() + ) + + +if __name__ == "__main__": + main() diff --git a/wechat_auto/models/__init__.py b/wechat_auto/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wechat_auto/models/__pycache__/__init__.cpython-313.pyc b/wechat_auto/models/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..def8ee6f2c65e6d3d73e68d580c9c35dc9353275 GIT binary patch literal 158 zcmey&%ge<81R@WXXM*U*AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl<({fzwFRQ8kPccI%T~}^=F>Xgrt__doe`SzeANMrMG%TXY8>QZNcv}i>dEMi6|Ky%#i%KC5!%4M+-rO|QK(!40M~ zD}S1c#i8bUVwWfK*T0{e8O@&?H=ao?0j1o9iQM)3d-5ZrhUko^xv6e8 zO|4D%Q^4HmZEjv>mIahpJP=1xOv1_f`{NN6Lt*~@qq*r3qby^|{8%z~;fLI%X`dCj zK#Gbn!SNyNAK}=L2a|Fjw}@^Zt=U*s zg2;;!h)errNo3_vA0Ojb{oldx(RN$^k%t4t;-oK%;f_<}qJWRS>~u20Gn#i`QWi33AB($wy(r()DI;ZJ#55^oPNX{sgb-7+4Y-P51) z9#3>8ZKIyct~7N#Tf2FD|Lm^5RIQNc80t&P!?83aWb3z1Q?s2Pr0Utk!J&@i(b3J9 zds0PpX^Pb^Oo^%51JCpiWa}Enk4}A*s%uXi8ah2{9{xB@wf{#sEsiAuh!Hyudrdd3 zPUv4jvbLM>gbrF5lRo`unAI?;84Ju$tInra`BvyO%(n&YK}w&f7RFv6b?8z@fz-Jo z4LaYg0LRZQrl^I4DeVOBu9wg()-{W08{41{K%br;%uw;gvzUYIDk}96vRkCRzo3PmIJ+<5aq+jiLcYRGgP@-#rT{1 z<j)tp{y4+XahgMo<3CZnHvnGpLV$0Ed#C{u9qPD;5Kal{czzA#mwqevf>w7O%b_}rBo~Djvt2d1|%)b79 zs=7xVI$`|#d0wGWt66?dzDs!mn4VU(w-w&z*DQEdhu zvGZ=9U9-gp!Z1{X5HbTt&rGA0fjB&ek(b_=I4`5~^vKt?q-J}=n0;-i8T3f_%C7)< nY9>kYF|p+_vFUfBW{EB*9mzvW1pF-dY4Xiv=0.100.0 +uvicorn>=0.23.0 +pydantic>=2.0.0 +pydantic-settings>=2.0.0 +pyautogui>=0.9.54 +pillow>=10.0.0 +requests>=2.31.0 diff --git a/wechat_auto/utils/__init__.py b/wechat_auto/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/wechat_auto/utils/__pycache__/__init__.cpython-313.pyc b/wechat_auto/utils/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4ad33616d9a69f1dd3f5f7a64d22325830410097 GIT binary patch literal 157 zcmey&%ge<81pfDzXM*U*AOZ#$p^VQgK*m&tbOudEzm*I{OhDdekkl<3{fzwFRQ5aS~=BO_xGGmr%U96u)g literal 0 HcmV?d00001 diff --git a/wechat_auto/utils/__pycache__/logger.cpython-313.pyc b/wechat_auto/utils/__pycache__/logger.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cdaaec2e99c760eaa46633125404786889f42ccb GIT binary patch literal 1740 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 GH2PnA`+R`_ literal 0 HcmV?d00001 diff --git a/wechat_auto/utils/logger.py b/wechat_auto/utils/logger.py new file mode 100644 index 0000000..8ee6620 --- /dev/null +++ b/wechat_auto/utils/logger.py @@ -0,0 +1,34 @@ +import logging +import sys +from pathlib import Path +from wechat_auto.config import settings + + +def setup_logger(name: str = "wechat_auto") -> logging.Logger: + logger = logging.getLogger(name) + + if logger.handlers: + return logger + + logger.setLevel(getattr(logging, settings.log_level.upper())) + + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' + ) + + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setFormatter(formatter) + logger.addHandler(console_handler) + + try: + file_handler = logging.FileHandler(settings.log_file) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + except Exception as e: + logger.warning(f"无法创建日志文件: {e}") + + return logger + + +logger = setup_logger() diff --git a/wechat_auto/utils/retry.py b/wechat_auto/utils/retry.py new file mode 100644 index 0000000..9a36b2f --- /dev/null +++ b/wechat_auto/utils/retry.py @@ -0,0 +1,88 @@ +import asyncio +import functools +from typing import Callable, Any, TypeVar, Coroutine +from wechat_auto.utils.logger import logger +from wechat_auto.config import settings + +T = TypeVar('T') + + +def async_retry( + max_retries: int = None, + base_delay: float = None, + exponential: bool = True, + exceptions: tuple = (Exception,) +): + max_retries = max_retries or settings.max_retries + base_delay = base_delay or settings.retry_base_delay + + def decorator(func: Callable[..., Coroutine[Any, Any, T]]): + @functools.wraps(func) + async def wrapper(*args, **kwargs) -> T: + last_exception = None + + for attempt in range(max_retries): + try: + return await func(*args, **kwargs) + except exceptions as e: + last_exception = e + if attempt < max_retries - 1: + if exponential: + delay = base_delay * (2 ** attempt) + else: + delay = base_delay + logger.warning( + f"{func.__name__} 失败,{attempt + 1}/{max_retries}," + f"{delay:.1f}秒后重试: {e}" + ) + await asyncio.sleep(delay) + else: + logger.error( + f"{func.__name__} 失败,已达到最大重试次数 {max_retries}: {e}" + ) + + raise last_exception + + return wrapper + return decorator + + +def sync_retry( + max_retries: int = None, + base_delay: float = None, + exponential: bool = True, + exceptions: tuple = (Exception,) +): + max_retries = max_retries or settings.max_retries + base_delay = base_delay or settings.retry_base_delay + + def decorator(func: Callable[..., T]): + @functools.wraps(func) + def wrapper(*args, **kwargs) -> T: + last_exception = None + + for attempt in range(max_retries): + try: + return func(*args, **kwargs) + except exceptions as e: + last_exception = e + if attempt < max_retries - 1: + if exponential: + delay = base_delay * (2 ** attempt) + else: + delay = base_delay + logger.warning( + f"{func.__name__} 失败,{attempt + 1}/{max_retries}," + f"{delay:.1f}秒后重试: {e}" + ) + import time + time.sleep(delay) + else: + logger.error( + f"{func.__name__} 失败,已达到最大重试次数 {max_retries}: {e}" + ) + + raise last_exception + + return wrapper + return decorator