wechat_msg_clicker/CLAUDE.md
2026-05-06 14:31:56 +08:00

88 lines
6.0 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 消息自动点击器
自动点击微信桌面端未读消息中的图片、视频和文件,触发原始文件下载到本地目录。
## 项目背景
配合另一个信息收集项目使用:信息收集脚本负责将微信消息入库,但图片、视频和文件需要被点击预览后微信才会下载原始文件到本地。本工具自动完成这个"点击"操作。
## 技术架构
- **语言**: Python 3.11+
- **核心技术**: macOS Accessibility API (AXUIElement) via pyobjc
- **目标应用**: 微信桌面端 v4.1.9+ (bundle ID: com.tencent.xinWeChat)
- **运行平台**: macOS Sonoma 14+
### 模块结构
```
wechat_clicker/
├── ax_bridge.py # AXUIElement 底层封装(属性读取、鼠标点击、键盘事件、滚动)
├── wechat_ui.py # 微信 UI 导航(聊天列表、消息列表、预览界面元素查找)
├── state_machine.py # UI 状态机窗口状态检测、恢复、Preview.app 管理)
├── automator.py # 主自动化逻辑(扫描→进入聊天→锚点+箭头导航点击图片/视频→点击文件→循环)
├── human_like.py # 拟人行为(高斯分布延迟、长休息、工作时间)
├── config.py # YAML 配置加载
└── logger_setup.py # 日志配置
```
### 关键设计决策
- 聊天列表项为 AXStaticText无 AXPress使用 **CGEvent 鼠标坐标点击**
- AXValue 位置/尺寸需用 AXValueGetValue 解包 CGPoint/CGSize
- 消息类型通过 title 内容判断:`"图片"` → 图片,`"视频..."` → 视频,`"文件\n..."` → 文件
- **图片/文件气泡偏移点击**AXStaticText 覆盖整行575px宽实际气泡仅在左侧 ~80-170px 区域,使用 `click_at_element_offset(x=120, y=height/2)` 命中
- **图片+视频处理(锚点+箭头导航)**:滚到底部 → 找最底部可见图片或视频(锚点)→ 点击锚点进入预览 → 按 i 次左箭头到达第 i 个 → 检测类型(查找"查看原视频"按钮区分视频/图片)→ 图片:点"..."→"使用预览打开"→ 关闭 Preview.app视频点"查看原视频"→ 等 10s → Escape 退出 → 回到微信 → 再次点击锚点 → 左箭头 i+1 次 → ... 循环直到覆盖 `unread_count + overlap`
- **视频类型三层检测**(1) 有"查看原视频"按钮 → 未下载视频,点击触发下载+等10s(2) 无"查看原视频"但有"原视频" → 已下载视频等1s后Escape跳过(3) 都没有 → 图片,走"..."→"使用预览打开"流程
- **视频首次点击兜底**视频首次点击可能不弹出独立预览窗口微信先下载等待5s后仍无窗口时检查主窗口内是否有"查看原视频"按钮(内嵌预览),有则直接处理
- **锚点稳定性**:连续 3 次失败自动重锚(重新滚到底部 + 重找锚点 + i 归零),最多重锚 2 次;每聊天最多处理 30 个媒体(`MAX_IMAGES_PER_CHAT`),防止单聊天垄断处理时间
- 文件处理:直接点击触发下载(不走箭头导航)
- **滚动方向**macOS CGEvent ScrollWheel **负值=向下滚**(看新消息),**正值=向上滚**(看旧消息)
- 进入聊天后滚到底部(负值),图片/视频通过箭头导航覆盖历史
- 滚动使用 **kCGEventMouseMoved + ScrollWheel**(不触发点击),避免误点 UI 元素
- "..."按钮搜索限制在预览区域(独立窗口或主窗口 x>200排除侧边栏
- 可见性检查基于元素**中心点**是否在消息列表可见区域内30px margin
- 状态检测基于窗口计数 + **AXMinimized 属性**:区分窗口存在但最小化 vs 真正可见
- **窗口恢复策略4 层)**NSRunningApplication.unhide隐藏→ 检测 0 窗口时 `open -b` 重新打开(窗口被关闭,带 polling 验证)→ AX API 设置 AXMinimized=False最小化→ activate前台
- **UNKNOWN/ABNORMAL 状态恢复**:先关闭所有非主窗口(含空标题遗留预览、小尺寸浮窗),再 Escape + activate + 点击侧边栏
- **主窗口识别**:多个"微信"窗口时取面积最大的为主窗口(解决视频迷你播放器等小浮窗干扰)
- **WECHAT_ABNORMAL 状态**:有窗口但标题非"微信"时(窗口正在加载/登录界面),先尝试关闭多余窗口再等待就绪
- **窗口恢复二次确认**`ensure_wechat_visible` 找到主窗口后等 0.5s 二次确认,防止瞬时窗口骗过检测
- **连续恢复失败保护**≥15 次连续失败后发送 macOS 系统通知 + 暂停 600s避免无限空转外层循环指数退避10s → 20s → 40s → ... → 120s 封顶)
## 使用方法
```bash
# 前置条件
pip install -r requirements.txt
# 系统设置 > 隐私与安全 > 辅助功能 → 添加终端/Python
# 复制配置
cp config.example.yaml config.yaml
# 运行
python main.py # 持续运行
python main.py --once # 单次扫描
python main.py --dry-run # 只扫描不点击
python main.py --dump-ui # 输出 UI 元素树
python main.py --dump-ui --chat "聊天名" # 进入指定聊天 dump 消息元素树(诊断用)
python main.py --debug # 详细日志
```
## 配置重点
- `config.yaml` 中可设置扫描间隔、延迟范围、白/黑名单、工作时间、媒体类型开关
- `max_chats_per_scan: 0` 表示不限制,处理全部未读聊天(适合大量群聊场景)
- 默认点击图片和视频(`media.click_files: false`
- 默认日志级别 DEBUG便于问题定位
- 默认工作时间 7:00-次日1:00非工作时间 1:00-7:00支持跨午夜
- 默认黑名单包含微信系统账号和非聊天界面(腾讯新闻、微信支付、微信团队、服务号、订阅号、文件传输助手)
- 处理完有未读的聊天后立即重扫,不等待 scan interval只有无未读时才 sleep
- 忙时(未读 > 5自动跳过随机休息
## 注意事项
- 微信窗口最小化、隐藏或被关闭时工具均会自动恢复unhide → open -b 重开窗口 → AXMinimized=False → activate
- 运行时会占用微信前台操作
- 建议在专用电脑上运行