# 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.db(SQLCipher 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()` 归一化:兼容 `/xwechat_files/_xxxx/db_storage` 与 `/_xxxx/db_storage` 两种布局,返回包含 `wxid_*` 账号目录的 父目录(即 `WeChatDB.db_dir`)。仍找不到时可手动传入 `db_dir=` 指定。 典型布局: ``` D:\微信文件\xwechat_files\_xxxx\db_storage\ ├── contact\contact.db 联系人(昵称、备注、别名) ├── session\session.db 会话列表(未读数、摘要、排序时间戳) ├── message\message_0..4.db 聊天消息(按会话分表 Msg_,跨库分片) ├── message\media_0.db 语音(VoiceInfo.voice_data,SILK 二进制) ├── 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_`; - 同一会话可能分片在多个 `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>\\Img\.dat` | v2 加密 | | 语音 | `media_0.db` → `VoiceInfo.voice_data` | SILK 明文 BLOB | | 文件 | `msg\file\\<原文件名>` | 原名来自 message_resource | | 视频 | `msg\video\\.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 密钥推导 异或段密钥是单字节,从同图缩略图 `_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 高层 API(UIA 受限,分派 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 支持)。