wechat_msg_clicker/project.md
2026-04-24 17:27:16 +08:00

175 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 项目规划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 margin289px 高图片经常被判定裁剪。改为只检查元素**中心点**是否在可见区域内
- [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] **多策略窗口恢复** — unhideCmd+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+