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

26 KiB
Raw Blame History

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.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\xwechatTencent\WeChat 等配置目录内的所有文件,内容支持 JSONdataDir/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 4PAGE_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_id2 表示自己,其他为数字 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.dbSessionTable 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.dbVoiceInfo.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_oncesys.stderr 却未 import sys db.py 补 import sys
部分文本消息显示为 [文本]/空(v1.0.3 微信 4.x 文本消息 content 为「容器头+明文+填充」结构,_friendly_content 无法解码 新增 _extract_text_from_blob 还原明文,数据库与 bot 均可见真实内容
动画表情被落盘为打不开的伪 .gifv1.0.3 解密后为 wxgf 容器(微信动画表情私有格式),按魔数误判为 GIF download_imagewxgf 直接返回 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() 统一解析;

  • EmojiMessagetype='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.pyWxParam.MSG_TYPE_* 常量到类型类的映射;

  • ui/BaseUISubWnd/Component/Main/NavigationBox/SessionBox/ChatBox, 老版 UIA 控件层,微信 4.x 下受限,保留兼容;

  • wx.pyWeChat/Chat/Listener 高层入口,内部按能力分派: 聊天区读取/监听走 DB,发送走 GUI(guia.WeChatGUI)。


9. 工具层(utils/

  • win32.pyfind_all_windows_from_root(遍历子窗口)、 GetPathByHwnd(进程路径)、SetClipboardText
  • lock.pyLockManager + uilock 全局互斥锁,避免多线程并发操作 同一微信窗口;
  • tools.py:杂项工具函数。

10. API 参考

wechatauto/__init__.py 顶层导出

WeChatChatListenerWeChatDBauto_detect_db_dirlist_accountsMediaDownloaderWeChatGUIquick_sendquick_send_filequick_send_imagequick_replyWinInputScreenOCRWxParamWxResponsewxlogMomentMomentDBLockManageruilock、异常类、消息模型、parse_msgPROJECT_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/_filecopy_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_msgsend_filesend_imagereply_msgat_memberopen_chatfocus_inputbring_to_frontget_sessionsget_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 支持)。