Files
wechatauto-replica/docs/技术文档.md
T

515 lines
26 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.
# wechatauto 技术文档
> 版本:1.0.6 适用:微信 4.x Windows 客户端 语言:Python 3.9+
> 本文档描述 wechatauto 的设计原理、内部实现与扩展方式,面向二次开发与排障。
---
## 1. 项目概述
wechatauto 是上游 [wxauto](https://wxauto.org) 的复刻版,面向**微信 4.x
Windows 客户端**的自动化库。它**不是网页版**,直接操作本机客户端。
与老版本微信(3.x,暴露 UIAutomation 无障碍树)不同,微信 4.1.12+ 的聊天
区域采用**自绘渲染**`MMUIRenderSubWindow*`,不同版本后缀不同,如
`MMUIRenderSubWindowHW` / `MMUIRenderSubWindow`),对 UIAutomation / MSAA 完全
不暴露内容。因此本项目拆成两条技术路线:
| 路线 | 模块 | 用途 |
| ---- | ---- | ---- |
| 本地数据库解密 | `wechatauto/db.py` | 读取消息、监听、历史导出、会话/联系人 |
| 坐标 + OCR | `wechatauto/guia.py` | 发送文本/文件/图片、回复、@成员 |
外加两个独立子系统:媒体下载(`media.py`)、朋友圈读取(`moment.py`)。
---
## 2. 总体架构
```
┌────────────────────────────────────────────┐
│ wechatauto │
├──────────────┬─────────────┬───────────────┤
读取/监听/导出 │ wechatauto │ wechatauto │ wechatauto │ 发送/回复/@
┌──────────────┐ │ db.py │ media.py │ guia.py │ ┌────────────┐
│ Listener │ │ (SQLCipher4) │ (媒体解密) │ (坐标+OCR) │ │ WeChatGUI │
│ MomentDB │ └──────────────┴─────────────┴───────────────┘ └────────────┘
│ msgs/ ui/ │ │ │ │
└──────┬───────┘ │ │ │
▼ ▼ ▼ ▼
contact/session/message_*.db msg\attach\*.dat 屏幕截取 + Win32 微信主窗口
sns.dbSQLCipher 4 media_0.db 用户输入(SendInput (MMUIRender…)
```
### 2.1 模块职责
| 模块 | 职责 |
| ---- | ---- |
| `db.py` | 微信数据库密钥提取、SQLCipher 4 解密、WAL 增量合并、消息查询/监听/导出、会话/联系人 |
| `guia.py` | 坐标 + OCR 发送:窗口定位、会话点击、输入框探测、剪贴板粘贴、发送按钮、发送后 DB 验证 |
| `media.py` | 图片 v2 解密(AES+XOR)、语音/视频/文件下载、剪贴板 CF_HDROP |
| `moment.py` | 朋友圈时间线读取、媒体下载(`MomentDB` 数据库路线) |
| `wx.py` | 高层 API 入口(`WeChat`/`Chat`/`Listener`),内部按能力分派到 DB 或 GUI |
| `msgs/` | 消息对象模型(文本/图片/语音/引用/系统消息等) |
| `ui/` | 老版 UIAutomation 控件层(微信 3.x 遗留,4.x 受限,保留兼容) |
| `utils/` | `win32.py`(窗口/剪贴板/进程)、`lock.py`(全局 UI 锁)、`tools.py` |
| `param.py` | `WxParam`/`WxResponse`/`PROJECT_NAME` 等公共参数与返回类型 |
---
## 3. 数据库读取子系统(`wechatauto/db.py`
### 3.1 数据存储布局
微信 4.x 数据位于登录目录下。**不同电脑/账号的目录位置不同**,由
`auto_detect_db_dir()` 自动定位,探测链(`db.py`):
1. **微信配置文件**:扫描 `%APPDATA%`/`%LOCALAPPDATA%`
`Tencent\xwechat``Tencent\WeChat` 等配置目录内的**所有文件**,内容支持
JSON`dataDir`/`fileSavePath` 等字段)、纯路径、或任意文本中提取第一个
Windows 路径(`_extract_path_from_config`);
2. **注册表**:读 `HKCU\Software\Tencent\xwechat` 等键下含
path/dir/save 的值(用户自定义保存位置时的补充来源);
3. **常见默认目录**`Documents`、用户主目录兜底。
定位结果统一经 `_locate_account_root()` 归一化:兼容
`<root>/xwechat_files/<wxid>_xxxx/db_storage`
`<root>/<wxid>_xxxx/db_storage` 两种布局,返回包含 `wxid_*` 账号目录的
父目录(即 `WeChatDB.db_dir`)。仍找不到时可手动传入 `db_dir=` 指定。
典型布局:
```
D:\微信文件\xwechat_files\<wxid>_xxxx\db_storage\
├── contact\contact.db 联系人(昵称、备注、别名)
├── session\session.db 会话列表(未读数、摘要、排序时间戳)
├── message\message_0..4.db 聊天消息(按会话分表 Msg_<md5>,跨库分片)
├── message\media_0.db 语音(VoiceInfo.voice_dataSILK 二进制)
├── message\message_resource.db 文件原名(MessageResourceDetail.packed_info
├── sns\sns.db 朋友圈(SnsTimeLine、SnsDataItem XML
└── ...
```
每个数据库都是 **SQLCipher 4** 加密的 SQLite:页大小 4096、
`PBKDF2-HMAC-SHA512`(加密密钥迭代 256000 次),每页 IV(16) + HMAC(80)。
### 3.2 密钥提取(进程内存只读扫描)
每个数据库有**独立的 32 字节密钥**,运行时保存在微信进程
`com.Tencent.WCDB.Config.Cipher` 配置对象中。`extract_keys()` 流程:
1.`psutil` 枚举 `Weixin.exe` 进程,取可读内存区域;
2. 定位字符串 `com.Tencent.WCDB.Config.Cipher` 的内存地址;
3. 从字符串地址回溯配置对象(`[ptr][len]` 结构),读取数据块;
4. 数据块与固定掩码(`CONFIG_XOR_MASK`)逐字节异或,得到明文配置:
`x'<64位hex密钥><32位hex盐>'`
5. 用 SQLCipher 4 的 HMAC 校验规则验证候选密钥(取某库首页试解);
6. 通过后写入 `%TEMP%\wechatauto_db\<账号>\keys.json` 缓存,
之后进程内复用(`_cached_db`),无需每次重新扫描。
### 3.3 数据库解密
- SQLCipher 4`PAGE_SZ=4096`,页尾 `RESERVE_SZ=80` 保留字节
IV 16 + HMAC 64);
- 解密结果按页写入临时工作目录;通过比对源文件 mtime/size 的 stamp
`STAMP_VERSION=2`,含缓存格式版本号)判定是否可复用缓存;
- 首次解密 contact.db 约 6s,之后全部秒级;
- 每次打开连接前用 `PRAGMA integrity_check` 兜底,损坏时自动全量重建。
### 3.4 WAL 增量合并(帧盐校验)
微信 `-wal` 是**预分配文件**checkpoint 时 WAL 头 salt+1 并清零写游标,
但**旧世代帧仍留在文件中**。若合并时不过滤帧盐,会把过期页覆盖进主库,
导致 `database disk image is malformed`。修复方案(`_merge_wal`):
1. 读取 WAL 头 `salt1/salt2`
2. 仅合并 **salt 与当前 WAL 头一致**的帧,旧世代帧直接跳过;
3. 合并结果 `PRAGMA integrity_check` 校验,失败自动回退全量重建;
4. 缓存 stamp 版本升级到 2,旧格式缓存强制重建。
### 3.5 消息分片与查询
- 会话名 → `Md5(会话微信号)` → 表名 `Msg_<md5>`
- 同一会话可能分片在多个 `message_*.db`,查询时按 `sort_seq` 合并排序;
- 关键列:
- `local_type` → 消息类型(见 `MSG_TYPE_NAMES`);
- `real_sender_id``2` 表示自己,其他为数字 id,可经
`message_resource.SenderName2Id` 反查微信号;
- `server_id`:服务端消息 id
- `packed_info_data`:图片/视频 md5 等二进制载荷;
- `sort_seq`:会话内单调递增序号(增量游标用);
- `get_messages(user, limit, offset)` 倒序取最近消息;
- `get_message_row(user, local_id)` 取含 `packed_info` 的原始行(媒体用);
- `get_new_messages(user, since_seq)` 返回 `sort_seq > since_seq` 的增量,
升序排列。
### 3.6 增量消息与水印
`Listener` 每个被监听会话维护一个 `watermark`(最近一次 `sort_seq`)。
轮询时以水印为游标取增量,成功后把水印推进到 `msgs[-1]["sort_seq"]`
保证不重不漏。回调签名 `callback(msg: dict, listener)`
### 3.7 会话 / 联系人 / 导出
- `get_sessions(limit)`:读 `session.db``SessionTable`
`username/unread/summary/last_time/last_sender`);
- `search_contact(keyword)`:对 contact.db 按 昵称/备注/微信号/别名 模糊匹配;
- `get_nickname(user)`:微信号 → 显示昵称(内部维护昵称/发送者索引);
- `export_history(out, fmt, users, limit_per_chat)`:全量导出
JSON / SQLite,支持指定会话与条数上限。
---
## 4. 媒体下载子系统(`wechatauto/media.py`
### 4.1 媒体存储
| 媒体 | 位置 | 说明 |
| ---- | ---- | ---- |
| 图片 | `msg\attach\<会话md5>\<YYYY-MM>\Img\<md5>.dat` | v2 加密 |
| 语音 | `media_0.db``VoiceInfo.voice_data` | SILK 明文 BLOB |
| 文件 | `msg\file\<YYYY-MM>\<原文件名>` | 原名来自 message_resource |
| 视频 | `msg\video\<YYYY-MM>\<id>.mp4` | 仅本地落盘时可用 |
### 4.2 图片 v2 格式
```
[6B 签名 07 08 56 32 08 07] [4B aes_size LE] [4B xor_size LE]
+ AES-ECB 密文段 + 明文段 + 异或段
```
### 4.3 AES 密钥内存反测
图片 AES 密钥是 16 字节 ASCII、账户级稳定,但**仅在微信查看图片时驻留进程
内存**。`detect_image_key()` 的流程:
1. 优先读缓存 `image_keys.json`
2. 否则对 `Weixin.exe` 内存做定向扫描,对每个候选 16 字节串用 AES-ECB
解密图片首块,校验 JPEG/PNG 魔数,命中即认为有效;
3. 命中后持久化;也可用 `image_key=` 显式注入。
### 4.4 XOR 密钥推导
异或段密钥是单字节,从同图缩略图 `<md5>_t.dat` 尾部 JPEG 结束标记
`FF D9` 反推:`key = tail[0] ^ 0xFF`
### 4.5 下载分发
`download_media(user, local_id)``local_type` 自动分发到
`download_image/_voice/_video/_file`,输出到 `DEFAULT_SAVE_PATH`
`~/Documents/wechatauto_media`)或自定义 `save_dir`
---
## 5. 朋友圈子系统(`wechatauto/moment.py`
`MomentDB` 直接读 `sns.db`
- `get_moments(limit, offset, username)`:时间线(最新在前,3382 条全量可读);
- `get_moment(tid)` / `get_my_moments(limit)`:单条 / 我的动态;
- 文本、图片 md5、点赞、评论均由 `SnsDataItem` XML 解析得到;
- `find_local_media(md5, kind)` / `download_media(media, save_dir)`
本地缓存(`Sns\Img` / `Sns\Video`)优先,否则 URL 拉取。
> 发表朋友圈已舍弃(4.x 自绘界面不可靠);点赞/评论仅在旧 UIA 类中
> 保留,4.x 下不可用。
---
## 6. 发送子系统(`wechatauto/guia.py`
发送走「屏幕坐标 + 本地 OCR」,全部坐标基于**渲染子窗口相对坐标**,
运行时换算为屏幕绝对坐标,保证跨 DPI / 分辨率 / 窗口尺寸一致。
### 6.1 窗口定位与布局
- 主窗口:**多特征兜底定位**(防 Qt 升级改名)。类名前缀
`Qt51514QWindowIcon` 只是「软条件」之一,`_find_main_window()` 对全部
可见顶层窗口按「标题含微信 / 类名前缀 / 进程名 `weixin.exe` / 可见 /
尺寸 ≥ 800px」加权评分,取最高分;完全找不到时退回按标题精确查找。
- 渲染子窗口:类名前缀 `MMUIRenderSubWindow`(自绘内容所在;不同版本
后缀不同,如 `MMUIRenderSubWindowHW` / `MMUIRenderSubWindow`
`_find_render_window()` 按前缀匹配并取面积最大者);找不到渲染子窗口
时(Qt 改版等)直接**回退用主窗口矩形计算坐标**,不中断使用。
- `_update_render_rect()``GetWindowRect` 得到渲染区,`_update_layout()`
按比例换算各布局常量:
- `SIDEBAR_RATIO=0.22` → 侧栏宽度 / 渲染区宽度(默认基线);
- `SEARCH_BOX_RATIO` → 左侧搜索框;
- `SEND_BUTTON_RATIO` → 右下角发送按钮检索区;
- 所有像素坐标均改为比例计算,避免硬编码失效。
### 6.1.1 布局动态校准(防 DPI/布局漂移)
不同机器(DPI 缩放 / 窗口尺寸 / 微信版本)下,固定比例常量可能漂移。
`WeChatGUI` 提供「跑一次永久兼容」的校准机制:
1. **锚点实测**`calibrate_layout()` 用 OCR 检测「搜索」占位文本
(文本中心 ≈ 侧栏宽 0.28)反推侧栏宽度;在右下角 OCR 找「发送」
按钮反推其检索区比例。结果都钳制在合理范围(侧栏比例限
`[0.14, 0.30]`),**检测失败一律回退默认常量**,宁可不动不误配;
2. **按机器缓存**:结果写入 `~/.wechatauto/layout-<主机名>_<分辨率>.json`
(含当时的 `render_w/h`);再次运行时 `_load_layout()` 自动加载,
与当前窗口尺寸差异 >15% 视为不匹配,自动重新校准;
3. **异常自动重校准**`get_input_box()` 探测连续 6 次失败时,自动触发
`calibrate_layout()` 并再次探测(每会话一次),应对运行中途布局漂移;
4. **显式强制**`WeChatGUI(calibrate=True)` 强制重新校准;也可在任何
时候调用 `wx.calibrate_layout()`
> 说明:微信 4.x 侧栏背景与消息区同为纯白,**像素色差检测不可靠**
> (实测边界两侧均为 255,唯一稳定锚点是搜索框 OCR 文本)。
### 6.2 会话定位与点击(防 toggle 关闭)
`open_chat()` 优先点侧栏(最可靠),失败走搜索框回退。流程:
1. `find_session(name)`:OCR 侧栏,按名称匹配返回会话行位置;
2. **活动行检测** `_row_is_active(rel_y)`:微信活动会话行背景为绿色
`(21,172,112)`,普通行浅灰 `(238,238,240)`。采样行内横向条带统计
"g 显著大于 r/b" 的绿色像素数,超过阈值判定为已打开;
3. 若行已是活动行(会话已打开),**跳过点击**——因为再点一次会
toggle 关闭会话(这是二次发送失败的历史根因);
4. 否则点击并 `_chat_open_confirmed()`:轮询标题 OCR 命中,或
`_pane_has_content()` 判定消息区已渲染非空白;
5. 侧栏确认失败 → `_search_chat(name)` 搜索框兜底,命中独立聊天窗则
`use_window` 切换 GUI 目标。
### 6.3 输入框定位与文本输入
- `get_input_box()`:在输入区按比例扫描白色矩形带(`_probe_input_box`);
- `focus_input()`:点击输入框聚焦;
- `input_text()`:文字以 **剪贴板 + Ctrl+V** 注入(绕过中文输入法拦截),
失败时回退拼音组合(`pypinyin` + SendInput 大写字母键)。
### 6.4 发送与验证
- `click_send()`OCR 定位发送按钮(`发送`/`Enter` 图标)点击,
找不到则直接回车;
- `verify=True` 时用 `WeChatDB` 读回确认:`send_msg` 轮询
`_verify_sent()`(新消息类型 + 内容匹配),`send_file/send_image`
`_verify_attachment_sent()``sort_seq` 基线 + 类型/文件名/`packed_info`
匹配,图片按类型+时序)。
- DB 实例经 `_get_db()` 缓存复用,避免每次发送验证都重新解析数据库
(这是历史慢 2.5 分钟问题的根因之一)。
### 6.5 文件 / 图片发送(CF_HDROP
文件/图片通过 **剪贴板 CF_HDROP + Ctrl+V** 插入草稿再回车发送,绕开自绘
「+ 菜单」定位。`copy_files_to_clipboard(paths)` 写入文件列表剪贴板,
粘贴后 `click_send()`
### 6.6 回复与艾特
- `reply_msg(text, who, target_text, verify)`:悬停最近一条消息
`_last_message_y` 估算)→ OCR 悬停工具栏找「回复」→ 点击 →
`input_text` → 发送;找不到工具栏回退右键菜单;
- `at_member(member, text, who, verify)`:聚焦输入框 → 键入 `@`
OCR 成员选择弹层定位成员名 → 点击 → 输入正文 → 发送;
- 两者均可用 `verify=True` 走 DB 确认。
### 6.7 关键工程修复记录
| 问题 | 根因 | 修复 |
| ---- | ---- | ---- |
| 二次发送失败/2.5min 卡死 | 点击已打开会话被 toggle 关闭 | `_row_is_active` 活动行高亮检测,跳过点击 |
| 发送验证每次重新解析 DB | `WeChatDB()` 每次新建、重复解库 | `_get_db()` 缓存实例 |
| 窗口尺寸变化布局失效 | 硬编码像素坐标 | 全量改按比例布局 |
| `Listener.stop()` 崩溃(v1.0.3 | `_run/_poll_once``sys.stderr` 却未 `import sys` | db.py 补 `import sys` |
| 部分文本消息显示为 `[文本]`/空(v1.0.3 | 微信 4.x 文本消息 content 为「容器头+明文+填充」结构,`_friendly_content` 无法解码 | 新增 `_extract_text_from_blob` 还原明文,数据库与 bot 均可见真实内容 |
| 动画表情被落盘为打不开的伪 `.gif`v1.0.3 | 解密后为 `wxgf` 容器(微信动画表情私有格式),按魔数误判为 GIF | `download_image``wxgf` 直接返回 `None`,不落盘 |
| 搜索联系人点错「网络搜索」(v1.0.3) | OCR 未按视觉顺序排序,且未过滤「搜索网络结果」节标题 | `_search_chat` 按 y 排序并跳过网络搜索节标题,取第一条联系人 |
| 表情截图截到自己发的消息(v1.0.3) | `capture()` 固定取「最底部一条」,截图前自己发消息时底部是自己的气泡 | 按消息方向(`msg.attr`)裁剪:自己消息用消息分隔空白定位、对方消息用头像锚点定位,阈值自适应截图尺寸 |
| 逐条发送都要重新点击对话框(v1.0.3) | `send_msg` 每次对目标会话都走完整 `open_chat`(重扫侧栏+点击) | 记录 `_current_chat`,目标会话已打开时跳过 `open_chat` 直接输入发送 |
---
## 7. 消息监听(`Listener`
```python
from wechatauto import WeChatDB
from wechatauto.db import Listener
db = WeChatDB()
lst = Listener(db, interval=1.0) # 轮询间隔(秒)
lst.add_listener("filehelper", cb) # cb(msg: dict, listener)
lst.start() # 后台守护线程
lst.stop() # 优雅停止
```
- 每个会话独立水印,`watermark` 属性可读写(支持断点续跑);
- 单次轮询/回调异常不终止监听,写 `sys.stderr` 后继续;
- 数据来源是解密后的本地库,与客户端在线状态无关;
- **回调并发模型(v1.0.2)**:回调在独立工作线程中执行。每个被监听会话
对应一条**串行**工作线程:同一会话内消息按序处理、不同会话间并行;
轮询线程只负责读取数据库并分派任务,不会被慢回调(AI 调用 / 图片识别等)
阻塞。`stop()` 会向工作队列投递停止信号并等待线程退出。
---
## 8. 消息模型与 UIA 兼容层
- `msgs/``BaseMessage` 及子类(`TextMessage/ImageMessage/VideoMessage/
VoiceMessage/FileMessage/QuoteMessage/LinkMessage/LocationMessage/
PersonalCardMessage/EmojiMessage/SystemMessage`),`parse_msg()` 统一解析;
- `EmojiMessage``type='emotion'`v1.0.2 新增):"动画表情"消息专属类型,
`FriendEmojiMessage` / `SelfEmojiMessage` 按收发方向区分。微信 4.x 表情
content 在数据库中为加密数据,`capture()` 采用「打开会话 → 滚动到底 →
截取消息区 → 自动裁剪最后一条消息气泡」的屏幕截图方案返回图片路径,
供上层 AI 视觉识别使用(示例见 `demo_emoji_capture.py`)。
定位实现(`msgs/mtype.py`):
- 微信 4.x 主窗口为 Qt 自绘渲染(`Qt51514QWindowIcon`),不暴露 UIA 子树,
且 DB 模式消息的 control 是伪控件(`_DBMessageControl.BoundingRectangle`
恒为 0),无法用控件坐标定位,因此**全部基于屏幕像素分析**;
- `_pane_capture_img()` 截取消息区全宽画面(渲染顶部到输入框);
- 自己发的消息(`attr='self'`):`_crop_bottom_message()` 用「消息分隔
空白」定位消息顶部,空白阈值自适应截图高度(约消息区高度的 2.5%),
跨分辨率/DPI 保持一致;
- 对方发的消息(`attr='friend'`):`_crop_by_avatar()` 优先用左侧头像
圆形彩色块的顶部锚定消息顶部(特征跨分辨率稳定),失败回退
`_crop_bottom_message()`
- 结果再经 `_content_bbox_crop()` 裁掉空白边缘后保存;
- 会话已打开时跳过 `open_chat`(避免刷新消息列表/切换窗口),并记录
`[CAP]` 诊断日志与 `~/pane_diag_raw.png` 原图便于跨电脑排查;
- `msgs/mtype.py``WxParam.MSG_TYPE_*` 常量到类型类的映射;
- `ui/``BaseUISubWnd/Component/Main/NavigationBox/SessionBox/ChatBox`
老版 UIA 控件层,微信 4.x 下受限,保留兼容;
- `wx.py``WeChat/Chat/Listener` 高层入口,内部按能力分派:
聊天区读取/监听走 DB,发送走 GUI(`guia.WeChatGUI`)。
---
## 9. 工具层(`utils/`
- `win32.py``find_all_windows_from_root`(遍历子窗口)、
`GetPathByHwnd`(进程路径)、`SetClipboardText`
- `lock.py``LockManager` + `uilock` 全局互斥锁,避免多线程并发操作
同一微信窗口;
- `tools.py`:杂项工具函数。
---
## 10. API 参考
### `wechatauto/__init__.py` 顶层导出
`WeChat`、`Chat`、`Listener`、`WeChatDB`、`auto_detect_db_dir`、
`list_accounts`、`MediaDownloader`、`WeChatGUI`、`quick_send`、
`quick_send_file`、`quick_send_image`、`quick_reply`、`WinInput`、
`ScreenOCR`、`WxParam`、`WxResponse`、`wxlog`、`Moment`、`MomentDB`、
`LockManager`、`uilock`、异常类、消息模型、`parse_msg`、`PROJECT_NAME`。
### `WeChatDB`
| 方法 | 说明 |
| ---- | ---- |
| `get_self_info()` | 当前账号(username / nick_name / remark |
| `get_sessions(limit=100)` | 会话列表 |
| `search_contact(keyword)` | 搜索联系人 |
| `get_messages(user, limit, offset)` | 会话消息 |
| `get_message_row(user, local_id)` | 原始行(媒体用) |
| `get_new_messages(user, since_seq)` | 增量消息 |
| `get_nickname(user)` | 微信号 → 昵称 |
| `list_message_chats()` | 含消息的会话 |
| `export_history(out, fmt, ...)` | 导出 JSON/SQLite |
| `extract_keys()` | 手动密钥提取 |
### `MediaDownloader`
`detect_image_key(refresh)`、`decrypt_image(dat_path)`、
`download_media(user, local_id)`、`download_image/_voice/_video/_file`、
`copy_files_to_clipboard(paths)`。
### `MomentDB`
`get_moments(limit, offset, username)`、`get_moment(tid)`、
`get_my_moments(limit)`、`find_local_media(md5, kind)`、
`download_media(media, save_dir)`。
### `WeChatGUI`
`send_msg`、`send_file`、`send_image`、`reply_msg`、`at_member`、
`open_chat`、`focus_input`、`bring_to_front`、`get_sessions`、
`get_input_box`;一行式 `quick_send` 等。
---
## 11. 目录结构
```
wechatauto/
├── wechatauto/
│ ├── __init__.py 公共导出
│ ├── wx.py 高层 APIUIA 受限,分派 DB/GUI
│ ├── guia.py ★ 坐标+OCR 发送模块
│ ├── db.py ★ 数据库解密/监听/导出
│ ├── media.py ★ 媒体下载(图片 v2 解密)
│ ├── moment.py ★ 朋友圈(MomentDB
│ ├── param.py / logger.py / languages.py / exceptions.py
│ ├── ui/ msgs/ utils/ uia/
│ └── py.typed
├── demo.py UI 自动化示例(4.x 受限)
├── demo_db.py ★ 数据库读取示例
├── demo_guia.py ★ 坐标+OCR 发送示例
├── demo_listen.py ★ 实时消息监听示例
├── demo_reply_at.py ★ 回复/@ 成员实测示例
├── demo_emoji_capture.py ★ 表情消息截图示例
├── README.md
├── docs/技术文档.md 本文档
└── pyproject.toml
```
---
## 12. 已知限制
1. **需要微信登录**:数据库密钥存于进程内存,首次需微信运行中(提取后缓存);
重新登录密钥变化需重新提取(自动校验失败重扫);
2. **图片 AES 密钥瞬态**:仅在微信查看图片时驻留内存,命中后持久化
`image_keys.json`,也可 `image_key=` 显式注入;
3. **发送依赖桌面**:锁屏/会话断开时 `desktop_available()` 返回 False
回复/艾特依赖 OCR 工具栏/弹层,弱网或弹层排版变化时可能失败;
4. **视频仅本地可用**:视频 mp4 未落盘时返回 None;
5. **发朋友圈已舍弃**;点赞/评论在 4.x 下不可用;
6. **UI 自动化受限**4.1.12+ 聊天区自绘,UIA 路线不可用。
---
## 13. 扩展开发指南
### 新增一个发送类型
1. 在 `guia.py` 仿照 `send_file`/`send_image` 实现原语(定位 → 注入 →
发送 → 可选验证);
2. 需要验证时,用 `_target_seq()` 取基线、`_verify_attachment_sent()`
轮询 DB
3. 在 `__init__.py` 导出,并加一行式 `quick_*` 封装。
### 新增一种媒体格式
1. 在 `media.py` 参照 `_decrypt_v2` 实现解码原语;
2. 在 `download_media()` 分发处按 `local_type` 接入;
3. 按需在 `_img_md5`/`_find_dat` 补充定位逻辑。
### 新增数据库表
在 `db.py` 的 `_collect_db_files()` 注册新库,用 `_open(rel)` 打开连接,
复用 `_check_merged` 的 WAL 合并与 integrity 兜底。
### 布局常量校准
微信更新导致 OCR 定位失效时,优先检查 `guia.py` 顶部 `*_RATIO` 常量与
`_update_layout()`,而非改硬编码坐标。跨机器/DPI 的布局漂移走自动校准:
删掉 `~/.wechatauto/layout-<机器>.json` 后重启(或
`WeChatGUI(calibrate=True)` / 调用 `calibrate_layout()`)会重新实测并
缓存「搜索/发送」锚点比例。
---
## 14. 路线图
1. 回复/艾特的跨版本稳定性校准(当前依赖 OCR 工具栏/弹层位置);
2. 视频消息下载增强(确认 4.x 视频落盘位置);
3. 导出/首扫并行化,密钥内存扫描增量缓存;
4. 支持更多消息类型(小程序、转账、位置等)友好显示(表情消息已在 v1.0.2
支持)。