26 KiB
wechatauto 技术文档
版本:1.0.6 | 适用:微信 4.x Windows 客户端 | 语言:Python 3.9+ 本文档描述 wechatauto 的设计原理、内部实现与扩展方式,面向二次开发与排障。
1. 项目概述
wechatauto 是上游 wxauto 的复刻版,面向微信 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):
- 微信配置文件:扫描
%APPDATA%/%LOCALAPPDATA%下Tencent\xwechat、Tencent\WeChat等配置目录内的所有文件,内容支持 JSON(dataDir/fileSavePath等字段)、纯路径、或任意文本中提取第一个 Windows 路径(_extract_path_from_config); - 注册表:读
HKCU\Software\Tencent\xwechat等键下含 path/dir/save 的值(用户自定义保存位置时的补充来源); - 常见默认目录:
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_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() 流程:
- 用
psutil枚举Weixin.exe进程,取可读内存区域; - 定位字符串
com.Tencent.WCDB.Config.Cipher的内存地址; - 从字符串地址回溯配置对象(
[ptr][len]结构),读取数据块; - 数据块与固定掩码(
CONFIG_XOR_MASK)逐字节异或,得到明文配置:x'<64位hex密钥><32位hex盐>'; - 用 SQLCipher 4 的 HMAC 校验规则验证候选密钥(取某库首页试解);
- 通过后写入
%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):
- 读取 WAL 头
salt1/salt2; - 仅合并 salt 与当前 WAL 头一致的帧,旧世代帧直接跳过;
- 合并结果
PRAGMA integrity_check校验,失败自动回退全量重建; - 缓存 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() 的流程:
- 优先读缓存
image_keys.json; - 否则对
Weixin.exe内存做定向扫描,对每个候选 16 字节串用 AES-ECB 解密图片首块,校验 JPEG/PNG 魔数,命中即认为有效; - 命中后持久化;也可用
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、点赞、评论均由
SnsDataItemXML 解析得到; 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 提供「跑一次永久兼容」的校准机制:
- 锚点实测:
calibrate_layout()用 OCR 检测「搜索」占位文本 (文本中心 ≈ 侧栏宽 0.28)反推侧栏宽度;在右下角 OCR 找「发送」 按钮反推其检索区比例。结果都钳制在合理范围(侧栏比例限[0.14, 0.30]),检测失败一律回退默认常量,宁可不动不误配; - 按机器缓存:结果写入
~/.wechatauto/layout-<主机名>_<分辨率>.json(含当时的render_w/h);再次运行时_load_layout()自动加载, 与当前窗口尺寸差异 >15% 视为不匹配,自动重新校准; - 异常自动重校准:
get_input_box()探测连续 6 次失败时,自动触发calibrate_layout()并再次探测(每会话一次),应对运行中途布局漂移; - 显式强制:
WeChatGUI(calibrate=True)强制重新校准;也可在任何 时候调用wx.calibrate_layout()。
说明:微信 4.x 侧栏背景与消息区同为纯白,像素色差检测不可靠 (实测边界两侧均为 255,唯一稳定锚点是搜索框 OCR 文本)。
6.2 会话定位与点击(防 toggle 关闭)
open_chat() 优先点侧栏(最可靠),失败走搜索框回退。流程:
find_session(name):OCR 侧栏,按名称匹配返回会话行位置;- 活动行检测
_row_is_active(rel_y):微信活动会话行背景为绿色(21,172,112),普通行浅灰(238,238,240)。采样行内横向条带统计 "g 显著大于 r/b" 的绿色像素数,超过阈值判定为已打开; - 若行已是活动行(会话已打开),跳过点击——因为再点一次会 toggle 关闭会话(这是二次发送失败的历史根因);
- 否则点击并
_chat_open_confirmed():轮询标题 OCR 命中,或_pane_has_content()判定消息区已渲染非空白; - 侧栏确认失败 →
_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)
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原图便于跨电脑排查;
- 微信 4.x 主窗口为 Qt 自绘渲染(
-
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. 已知限制
- 需要微信登录:数据库密钥存于进程内存,首次需微信运行中(提取后缓存); 重新登录密钥变化需重新提取(自动校验失败重扫);
- 图片 AES 密钥瞬态:仅在微信查看图片时驻留内存,命中后持久化
image_keys.json,也可image_key=显式注入; - 发送依赖桌面:锁屏/会话断开时
desktop_available()返回 False; 回复/艾特依赖 OCR 工具栏/弹层,弱网或弹层排版变化时可能失败; - 视频仅本地可用:视频 mp4 未落盘时返回 None;
- 发朋友圈已舍弃;点赞/评论在 4.x 下不可用;
- UI 自动化受限:4.1.12+ 聊天区自绘,UIA 路线不可用。
13. 扩展开发指南
新增一个发送类型
- 在
guia.py仿照send_file/send_image实现原语(定位 → 注入 → 发送 → 可选验证); - 需要验证时,用
_target_seq()取基线、_verify_attachment_sent()轮询 DB; - 在
__init__.py导出,并加一行式quick_*封装。
新增一种媒体格式
- 在
media.py参照_decrypt_v2实现解码原语; - 在
download_media()分发处按local_type接入; - 按需在
_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. 路线图
- 回复/艾特的跨版本稳定性校准(当前依赖 OCR 工具栏/弹层位置);
- 视频消息下载增强(确认 4.x 视频落盘位置);
- 导出/首扫并行化,密钥内存扫描增量缓存;
- 支持更多消息类型(小程序、转账、位置等)友好显示(表情消息已在 v1.0.2 支持)。