175 lines
13 KiB
Markdown
175 lines
13 KiB
Markdown
# 项目规划:WeChat 消息自动点击器
|
||
|
||
## 项目目标
|
||
|
||
创建一个长期稳定运行的自动化工具,自动点击微信桌面端中未读消息的图片和文件,触发微信下载原始文件到本地目录,配合信息收集系统完成数据入库流程。
|
||
|
||
## 项目状态
|
||
|
||
### 已完成 (v0.1.0 - 2026/04/22)
|
||
|
||
- [x] 项目结构搭建
|
||
- [x] 配置系统(YAML 配置文件、默认值、白/黑名单)
|
||
- [x] AXUIElement 底层封装(ax_bridge.py)
|
||
- [x] 微信 UI 导航(wechat_ui.py)— 聊天列表解析、消息类型分类
|
||
- [x] UI 状态机(state_machine.py)— 基于窗口计数的状态检测与恢复
|
||
- [x] 拟人行为引擎(human_like.py)— 高斯分布延迟、长休息、工作时间
|
||
- [x] 主自动化逻辑(automator.py)— 完整扫描-点击-关闭循环
|
||
- [x] 入口脚本(main.py)— 参数解析、信号处理
|
||
- [x] 调试工具(--dump-ui, --dry-run)
|
||
|
||
### v0.2.0 修复与增强 (2026/04/22)
|
||
|
||
- [x] **修复: 聊天项点击失败** — AXStaticText 无 AXPress 动作,改用 CGEvent 鼠标坐标点击
|
||
- [x] **修复: AXValue 位置解包** — get_position/get_size 改用 AXValueGetValue 正确提取 CGPoint/CGSize
|
||
- [x] **新增: 消息列表滚动** — 进入聊天后向上滚动 5 轮,加载更多历史媒体消息
|
||
- [x] **新增: 图片"使用预览打开"流程** — 点击大图 → 点"..." → "使用预览打开" → 关闭 Preview.app,确保原始文件保存到本地
|
||
- [x] **新增: 文件直接点击下载** — 点击文件气泡触发微信下载
|
||
- [x] **新增: Preview.app 管理** — state_machine 添加检测与关闭 macOS Preview.app 的能力
|
||
- [x] **新增: 鼠标双击、滚轮滚动** — ax_bridge 扩展 CGEvent 操作
|
||
|
||
### v0.3.0 修复与调试增强 (2026/04/23)
|
||
|
||
- [x] **修复: 滚动误点击** — `_scroll_at()` 原先用 mouseDown/mouseUp 聚焦导致误点侧边栏按钮,改用 kCGEventMouseMoved 仅移动鼠标不触发点击
|
||
- [x] **修复: 未跳转最新消息** — 进入聊天后先 `scroll_to_bottom()` 滚到消息列表底部(最新消息),再向上滚动加载历史
|
||
- [x] **修复: "..."按钮误匹配** — `_find_more_button_in_preview()` 限制搜索范围,主窗口中只搜索 x>200 区域(排除左侧侧边栏),增加位置日志
|
||
- [x] **修复: 重复点击同一图片** — 用消息列表子元素索引(child index)做去重,processed_indices 跟踪已处理元素,跨滚动轮次不重复
|
||
- [x] **修复: 裁剪图片点击无效** — 只处理完全在消息列表可见区域内的媒体(上下各 30px margin),跳过部分被裁剪的元素
|
||
- [x] **增强: 全链路调试日志** — click_at_element 记录坐标/角色/标题,get_media_messages 输出每个媒体详情,find_menu_item 记录搜索过程,状态检测记录所有窗口标题
|
||
|
||
### v0.4.0 诊断工具与窗口恢复 (2026/04/23)
|
||
|
||
- [x] **新增: --dump-ui --chat 诊断工具** — `python main.py --dump-ui --chat "聊天名"` 进入指定聊天,滚到底部,以 max_depth=10 dump 每个消息子元素的完整 AX 树(含索引、position、actions、identifier),用于诊断不同图片元素结构差异
|
||
- [x] **新增: dump_element 增强** — 输出 AXPosition、AXActions、AXIdentifier 字段
|
||
- [x] **新增: 微信窗口最小化自动恢复** — 新增 `WECHAT_MINIMIZED` 状态,detect_state 区分"未运行"和"已最小化",通过 AppleScript 自动取消最小化并恢复前台
|
||
- [x] **增强: verify_setup 自动恢复** — 启动时找不到主窗口先尝试恢复最小化,再判断失败
|
||
|
||
### v0.5.0 图片点击与滚动修复 (2026/04/23)
|
||
|
||
- [x] **修复: 图片点击不生效(根本原因)** — AXStaticText 覆盖整行(575px宽),实际图片气泡仅在左侧 80~170px 区域。改用 `click_at_element_offset(x=120, y=height/2)` 命中实际缩略图区域(通过逐像素探测确认可点击边界)
|
||
- [x] **修复: 滚动方向反转** — macOS CGEvent ScrollWheel 正值=向上滚(看旧消息),负值=向下滚(看新消息)。`scroll_to_bottom` 从 +20 改为 -20,历史加载滚动从 -10 改为 +10
|
||
- [x] **修复: 可见性判断过严** — 原先要求元素完全在可见区域内(30px margin),289px 高图片经常被判定裁剪。改为只检查元素**中心点**是否在可见区域内
|
||
- [x] **新增: click_at_element_offset** — ax_bridge 新增带偏移量的点击方法,文件/视频点击也使用偏移量
|
||
- [x] **已验证: 完整图片处理流程** — 蜗牛聊天 4 张不同尺寸图片(289px、155px、180px)全部成功:缩略图→预览窗口→...按钮→使用预览打开→关闭 Preview→恢复
|
||
- [x] **已验证: 带滚动的媒体处理** — scroll_to_bottom 正确到达最新消息,向上滚动正确加载历史,去重正确(processed_indices=[45,47,49,50])
|
||
|
||
### v0.6.0 最小化窗口恢复修复 (2026/04/23)
|
||
|
||
- [x] **修复: 最小化时恢复失败(根本原因)** — 原 AppleScript `tell application "WeChat"` 方案存在两个致命问题:(1) 微信的 localizedName 是"微信"不是"WeChat",AppleScript 找错应用或静默失败导致打开了别的窗口;(2) 微信不支持标准 AppleScript `miniaturized` 属性(返回 error -10006)
|
||
- [x] **修复: 最小化检测遗漏** — 微信窗口最小化后 AXWindows 仍返回该窗口(AXMinimized=True),原 `detect_state` 和 `ensure_wechat_visible` 未检查此属性,误判窗口已可见
|
||
- [x] **改用 AX API 恢复窗口** — 通过 `AXUIElementSetAttributeValue(window, "AXMinimized", False)` 取消最小化,比 AppleScript 更可靠
|
||
- [x] **多策略窗口恢复** — unhide(Cmd+H隐藏)→ AXMinimized=False(最小化)→ activate(前台),覆盖所有不可见场景
|
||
- [x] **恢复重试与验证** — `ensure_wechat_visible` 增加 3 次重试循环,每次验证主窗口 AXMinimized=False 后才返回成功
|
||
- [x] **已验证** — 两次完整测试通过:最小化微信→运行 `--once --debug`→自动恢复→正常扫描处理
|
||
|
||
### v0.7.0 默认只点击图片 (2026/04/23)
|
||
|
||
- [x] **配置变更: click_files 默认关闭** — `media.click_files` 默认值从 `true` 改为 `false`,默认只点击图片不点击文件。需要点击文件时在 config.yaml 中设置 `click_files: true`
|
||
|
||
### v0.8.0 提高图片可靠性 — 减少遗漏 (2026/04/24)
|
||
|
||
- [x] **修复: 去重 bug** — 原用绝对子元素索引去重,滚动后索引偏移导致图片被错误跳过。改用反向索引(距数组末尾距离),滚动时新元素从头部插入不影响已有元素
|
||
- [x] **新增: 失败重试** — 图片点击失败不再永久跳过,最多重试 2 次后才标记为已处理
|
||
- [x] **改进: 移除聊天数量上限** — `max_chats_per_scan` 默认改为 0(不限制),几十个群全部处理
|
||
- [x] **改进: 忙时不 sleep** — 处理完有未读聊天后立即重扫,不等待 scan interval;只有无未读时才 sleep 15s
|
||
- [x] **改进: 动态滚动轮数** — 根据聊天未读数动态计算滚动轮数(`max(5, unread//3)`,上限 30),大群覆盖更深
|
||
- [x] **新增: 尾部复检** — 滚动处理完成后回到底部复检,捕获处理期间新到达的消息
|
||
- [x] **改进: 忙时不休息** — 当全局未读 > 5 时跳过随机休息检查
|
||
- [x] **改进: 缩短延迟范围** — 所有延迟缩短约 50-60%,提高处理效率
|
||
- [x] **增强: 日志** — 记录被过滤掉的聊天名、每周期输出摘要(全局未读/处理聊天数/过滤数/点击媒体数/耗时)
|
||
|
||
### v0.9.0 锚点+箭头导航重构图片处理 (2026/04/24)
|
||
|
||
- [x] **重构: 图片处理改为锚点+箭头导航** — 不再逐个查找图片元素、滚动、去重。改为:找最底部可见图片(锚点)→ 点击进入预览 → 左箭头 i 次到达第 i 张 → "..."→"使用预览打开"→ 关闭 Preview → 重复。彻底绕过滚动覆盖不足、可见性判断、去重 bug 等问题
|
||
- [x] **新增: ax_bridge 左箭头键支持** — `kVK_LeftArrow` 常量和 `send_left_arrow()` 方法
|
||
- [x] **新增: `_find_anchor_image`** — 在消息列表底部查找最后一个可见图片元素作为导航锚点
|
||
- [x] **新增: `_process_images_with_arrow`** — 锚点+箭头循环核心逻辑,每张图片:点击锚点 → 左箭头 N 次 → 预览流程
|
||
- [x] **新增: `_do_preview_and_close`** — 从旧 `_click_image` 提取的预览流程(找"..."→ "使用预览打开"→ 关闭 Preview)
|
||
- [x] **新增: `_process_visible_files`** — 文件/视频独立处理(不走箭头导航,直接点击可见元素)
|
||
- [x] **删除: 旧图片逐个处理逻辑** — `_click_image`、`_process_visible_media` 中的图片分支、`processed_indices`/`retry_counts` 去重、滚动上翻 N 轮、尾部复检
|
||
- [x] **已验证: 锚点+箭头导航** — 测试脚本 `test_arrow_nav.py` 验证 5/5 张图片全部成功,平均 10.4s/张
|
||
|
||
### v0.9.1 锚点+箭头导航稳定性增强 (2026/04/24)
|
||
|
||
- [x] **新增: 重锚机制(Re-anchor)** — 连续 3 次失败(预览未打开或预览流程失败)自动触发重锚:滚到底部 → 重新找锚点 → i 归零重新开始。最多重锚 2 次,超过则放弃当前聊天
|
||
- [x] **新增: `_scroll_and_find_anchor`** — 合并滚动+锚点查找为单一方法,供初始化和重锚复用
|
||
- [x] **改进: `_process_images_with_arrow` 重写** — 改为接收 `msg_list` 参数,内部管理锚点生命周期(查找、使用、失效检测、重锚)
|
||
- [x] **新增: 每聊天图片处理上限** — `MAX_IMAGES_PER_CHAT = 30`,防止单个高活跃群聊垄断处理时间(30张×10s = 5分钟/聊天)
|
||
- [x] **简化: `_process_media_with_scroll`** — 不再负责滚动和锚点查找(移入 `_process_images_with_arrow` 内部),代码更清晰
|
||
|
||
### v0.9.2 UNKNOWN 状态恢复加固 (2026/04/24)
|
||
|
||
- [x] **修复: UNKNOWN 状态长时间无法恢复** — 原恢复逻辑只做 activate + 点侧边栏,遇到弹窗/多余窗口无法清理。改为先 Escape + Cmd+W 关闭可能的弹窗,再 activate + 点侧边栏
|
||
- [x] **改进: 恢复失败指数退避** — 外层循环连续恢复失败时退避等待:10s → 20s → 40s → 80s → 120s(封顶),避免每 13 秒重复无效尝试。恢复成功后计数器归零
|
||
|
||
### 待验证
|
||
|
||
- [x] ~~验证最小化窗口自动恢复功能(需手动最小化微信窗口测试)~~
|
||
- [ ] 有未读图片消息时的完整 --once 自动化流程
|
||
- [ ] 长时间运行稳定性测试
|
||
|
||
### 未来可能的改进
|
||
|
||
- [ ] 聊天列表滚动支持(处理不在可见区域的聊天)
|
||
- [ ] 已处理消息去重(记录已点击的媒体,避免重复)
|
||
- [ ] 微信版本适配(检测 UI 结构变化并自动调整)
|
||
- [ ] 运行状态 Web 面板(远程监控)
|
||
- [ ] 与信息收集系统直接集成
|
||
|
||
## 技术细节
|
||
|
||
### 微信 UI 结构 (v4.1.9)
|
||
|
||
- 主窗口 "微信" 包含侧边栏 + 聊天列表
|
||
- 聊天列表:`AXList name="会话"`,子元素为 `AXStaticText`(无 AXPress 动作)
|
||
- 聊天项 AXIdentifier 格式:`session_item_聊天名`
|
||
- AXStaticText 的 AXPosition/AXSize 需通过 AXValueGetValue 解包
|
||
- 点击聊天项打开独立会话窗口
|
||
- 消息列表:`AXList name="消息"`
|
||
- 图片消息 title = "图片",文件消息 title 以 "文件\n" 开头
|
||
|
||
### 点击策略
|
||
|
||
- 聊天项和消息元素均使用 CGEvent 鼠标坐标点击(AXStaticText 不支持 AXPress)
|
||
- 计算元素中心坐标 = position + size/2
|
||
- 图片预览中的按钮优先尝试 click_at_element,后备 AXPress
|
||
|
||
### 图片下载流程(锚点+箭头导航)
|
||
|
||
1. 滚到消息列表底部(最新消息)
|
||
2. 找到最底部可见的图片元素(锚点图片 N)
|
||
3. 对 i = 0, 1, 2, ... image_count-1:
|
||
a. 点击锚点图片 N → 打开预览
|
||
b. 按左箭头 i 次 → 到达图片 N-i
|
||
c. 点击 "..." (更多) 按钮
|
||
d. 点击 "使用'预览'打开"
|
||
e. 等待 macOS Preview.app 打开(原始文件已保存到本地)
|
||
f. 关闭 Preview.app + 额外窗口
|
||
g. 回到微信,准备下一轮
|
||
4. image_count = min(max(5, unread_count) + 5, 30)(固定 overlap,上限 30 张/聊天)
|
||
5. 连续 3 次失败自动重锚(滚到底部 + 重找锚点 + i 归零),最多重锚 2 次
|
||
|
||
### 调度策略
|
||
|
||
- 高斯分布随机延迟(非均匀)
|
||
- ±20% 扫描间隔抖动(无未读时生效)
|
||
- 忙时(未读 > 5)跳过随机休息,空闲时 5% 概率长休息
|
||
- 工作时间限制(8:00-23:00)
|
||
- 聊天数量不限制(`max_chats_per_scan: 0`)
|
||
- 处理完有未读聊天后立即重扫
|
||
- 图片覆盖量 = min(max(5, unread_count) + 5, 30) (固定 overlap,上限 30 张/聊天)
|
||
|
||
### 依赖
|
||
|
||
- pyobjc-framework-Quartz
|
||
- pyobjc-framework-ApplicationServices
|
||
- pyobjc-framework-Cocoa
|
||
- PyYAML
|
||
|
||
### 系统要求
|
||
|
||
- macOS Sonoma 14+
|
||
- Python 3.11+
|
||
- 辅助功能权限(终端/Python 需要授权)
|
||
- 微信桌面端 v4.1.9+
|