wechatauto-replica — 微信 4.x Windows 自动化 / WeChat 4.x Automation
中文版 在下方 · English version below
🇨🇳 中文版
wechatauto —— 微信 4.x Windows 客户端自动化(wxauto 复刻版)
本项目复刻上游 wxauto 项目,目标是实现对当前微信 4.x Windows 客户端的自动化 (读取消息、发送消息、媒体下载、朋友圈),非网页版,直接操作本机客户端。
当前版本:1.2.2.3
兼容范围:Windows 10/11 | Python 3.9+(已在 3.12 验证)| 微信 4.1.12+ (数据库读取路线对微信版本不敏感;坐标+OCR 发送路线依赖 4.1.12+ 自绘渲染 布局,其它 4.x 小版本可能需校准
guia.py布局常量)。
直接解密读取 xwechat_files/.../db_storage/ 下的 contact.db / message_*.db / sns.db 加密库——纯本地,无 Web API。
🤝 致谢
感谢 vesio 在 issue #1 提供微信 4.1.12 的 UIA 控件树代码与思路,促成了 v1.0.8 的 UIA 混合驱动。
感谢 nanshanjack 发现 UI 锁的可重入问题(v1.1.2 修复)。
感谢 maozhitao12450 报告 WXAM (wxgf) 图片下载问题(v1.1.3 修复)。
感谢 uiharukazari0105 发现语音数据分片存储(
media_1.db等)从未被搜索的问题(v1.1.4 修复)。
版本记录
v1.2.2.3(2026-09-16)
- 修复:库运行期间磁盘持续约 50MB/s 读写。解密缓存的 stamp 判定用精确浮点相等比较 mtime,而写盘用
%f(只 6 位小数)、Windows 的 mtime 有 7 位小数 → 每轮查询(约 1s)都被判定为“库已变化”,于是反复全量解密 + 合并 WAL + 重写缓存。现改为STAMP_VERSION 3+%r(完整往返精度),升级后重建一次即稳定。 - 消息读取改为“分片内先 LIMIT 再合并”(48k 条大群实测 5.5×:1.053s → 0.191s;
get_new_messages约 6×)。超大群不再把每个分片的全部行物化进 Python;get_messages/get_new_messages/get_message_row(..., local_type=)/get_message_rows_for_media等公开 API 签名与结果均不变(6 个会话 × 71 个用例逐条比对一致)。 - 修复 UTF-8 模式下“取不到密钥”:4 处
tasklist调用未指定编码,在python -X utf8/PYTHONUTF8=1下解码 GBK 输出失败 →stdout变成 None →AttributeError,整个取密钥流程崩掉。现统一encoding="gbk", errors="replace"并对 stdout 兜底。 WeChatUIA.is_running()改为多判据:原来单判据 +except → False,任何异常都会变成“微信未运行”的假报错;现为 tasklist / 主窗口标题 / psutil 三判据,且只有所有判据都出错失败时才写 stderr 说明原因。- 演示脚本与文档中的真实联系人/群名清理:统一改为「文件传输助手」(示例默认值「兔仔仔」「送你挖银子」保留)。
v1.2.2.2(2026-09-13)
- 密钥问题根除(不再“每次微信更新都复发”):三层修复。
- 缓存不再被写空:
_save_keys()空结果不落盘(原子写 + 保留.bak)。此前提取偶发失败(选错账号/权限)会把好缓存覆盖成空文件,之后每次启动都报“0 把密钥”——故障现场的keys cached: 0即由此而来。 - 稳定密钥副本:自动在
%LOCALAPPDATA%\wechatauto_keys\<账号>.json保留一份(可用环境变量WECHATAUTO_KEYS_DIR指定目录,例如项目工作区),跨 TEMP 清理与微信更新复用。启动时按「稳定副本 → 工作缓存 →.bak→ 其它账号缓存」多位置合并,并逐条页1 HMAC 校验,只保留真能用的。 - 账号选择改由密钥校验决定:不再按“最近修改的 .db”猜账号(微信每次更新会重写 .db,mtime 全变 → 选错账号 → 0 密钥)。现在一次内存扫描收集候选密钥,对每个账号目录分别做页1 HMAC 打分,选能解开的那一个并自动切换(日志:
已按密钥校验选定账号目录: …)。
- 缓存不再被写空:
- cfg 主密钥告警:cfg 路径在微信 4.1.13+ 会返回不可信的主密钥(v1.1.9 起已降级为回退路径);现在它复现不出任何库密钥时会明确告警,不再静默当作成功。
- 诊断增强(
diagnose_keys):新增微信客户端 FileVersion、逐账号「缓存可用 / 主密钥派生」计数、主密钥一致性检查(判断“密钥属于哪个账号”);_open报错文本直接列出三条经典原因(32 位 Python / 权限与微信不一致 / 多账号选错)+account=提示。
v1.2.2.1(2026-09-12)
- 兼容微信新版界面(4.1.13.65 实测):新版把
AutomationId从短名改成了点分路径(旧session_list/chat_input_field→ 新MainView.main_tabbar、MainView….main_window_sub_splitter_view…),原来按短名精确等值匹配会失配。现在 AID 一律按「精确 / 点分段相等 / 结尾匹配」判定(_aid_hit()),旧版短名与新版路径都能命中。 - 窗口标题匹配放宽:新版主窗口标题为
Weixin、带未读数时变成微信(3);_title_is_main()改为包含匹配,WeChat等其它窗口不会误匹配。 - 锚点候选 + 结构兜底:主窗口 / 登录窗 / 搜索框改为候选元组匹配(单值常量保留,兼容外部引用);搜索框、聊天输入框、搜索结果列表各自增加结构兜底(Name 含「搜索」的 EditControl / 聊天区 EditControl / 根节点属性搜索),日后新版改类名或 AID 时不至于整体失效。
- 新增布局自检
WeChatUIA.describe_layout():一次调用返回主类名、标题、布局类型(merged合并布局 /legacy独立朋友圈窗 /chat)与各锚点解析结果(main_window / search_box / session_list / chat_input / main_tabbar / sns_list)。微信再改界面时先跑它,即可定位是哪个锚点失配。 - 说明:朋友圈相关锚点本身已是双布局分支(独立
mmui::SNSWindow/ 合并mmui::SNSContentView),4.1.13.65 实测类名未变,无需调整。
v1.2.2(2026-09-12)
- 修复跨分片消息读取(消息/语音不全):同一会话的
Msg_<md5>表实际横跨多个message_*.db分片,而get_messages只命中第一个分片——实测某会话真实 8904 条消息(24 条语音)此前只返回 1 条。新增_find_msg_tables()/_msg_conns()/_shard_rows(),跨全部分片合并后按sort_seq排序;get_messages、get_new_messages、_find_media_rows均改用合并视图。get_message_row新增local_type过滤(local_id跨分片不唯一),并新增get_message_rows_for_media()返回全部分片命中行;媒体下载各方法传入类型码以选中正确分片行。 - 监听可靠投递(行为变更):水位改为回调成功后才推进(新增
_inflight分派边界,回调未确认前不重复分派同一消息),回调失败按max_retries(默认 3 次)重试后再记丢弃;水位自动落盘listener_watermark.json。进程停机期间到达的消息会在下次启动时补投,不再被静默跳过。传watermark_file=""可关闭落盘。 - 文本还原不再要求含中文:纯英文 / 纯数字 / URL / Emoji 的容器格式消息不再退化成
[文本](改为可打印率 + 字符类别双重判定)。 Chat.GetNewMessage()不再丢积压:单批 200 条以上时连续分批拉取直到追平,水位只推进到实际取回的最后一条,不再直接跳到库内最新位置。- UIA 物化自愈(微信重启/升级后子控件全扫不到):微信重启或升级后 Qt accessibility gate 字节归零,
mmui::树退化为 Qt 空壳(Qt51514QWindowIcon+ 2 个节点,扫不到任何控件)。现在会热写 gate → 校验mmui::是否真的出现 → 失败自动换候选 RVA 重试(真正生效过的 RVA 按 DLL 身份缓存);_get_uia()增加 30s 节流自愈,不再「一次唤醒失败就永久降级 OCR」,也不再需要人工refresh=True。兜底表补4.1.13.65 → 0x0AE2B0C8。 - 朋友圈滚动定位修复:加入反向上限(每轮最多反向 1 次,之后单向向下)与卡死检测(顶部指纹改为含包围盒几何——合并布局整屏复用 ListItem、同名 cell 不再误判「卡死」而中途放弃);DB 标尺判断目标在下方时跳过「先滚到顶部」;停止判据改为下一条朋友圈 UIA 出现即停;方向与距离修正(被裁像素换算滚轮格数)+ 底部余量,解决「翻的距离不够、够不到 … 按钮」。
- 消息类型表:支持微信 4.x 复合
local_type(按低 32 位分解真实类型);新增50 音视频通话(<voipmsg>气泡)、11000 动画表情、8594229559345 红包(库侧此前被低 8 位映射误标为「文件/链接/卡片」);空正文(表情/贴纸类)显示[动画表情]占位;demo_group_messages对所有类型统一 zstd 解压并输出一行摘要。 demo_listen.py --all自动发现新会话(此前只取启动时最近 30 个,之后新建会话不会加入监听)。- 新增防撤回监听
RecallGuard(测试版):watch(listener)后把每条新消息写入独立镜像 sqlite 库、附件(图片/语音/视频/文件)增量备份到media/;收到revokemsg系统消息时终端打印[撤回] 撤回者 → 原文并写入recall_events表。未充分实机验证,按测试版发布。 - 新增
MomentObserver(测试版):朋友圈缓存 key 的「观察即固化」——snapshot()/diff()快照与轮询导出(缓存 key 与 feed md5 之间无可推导映射且缓存易失,故观察即可固化)。未充分实机验证,按测试版发布。
v1.2.1(2026-09-06)
- 新增「引用消息并发送」(测试版):
WeChatGUI.quote_msg(text, who, target_text=None, verify=False)右键定位消息 → 弹出菜单选择「引用」→ 输入内容并发送;target_text省略时引用最近一条。quick_quote()提供一行式入口,示例脚本wechatauto/demo_quote.py。- 测试版说明:引用功能走「坐标 + OCR + SendInput」模拟点击路线,依赖微信 4.1.x 自绘渲染布局;随窗口尺寸/DPI/会话内容不同可能存在定位偏差。右键采用
SendInput注入(微信渲染窗口对mouse_event右键不响应),光标先SetCursorPos移至目标再注入,避免“只移动不点击 / 只点击不移动”的错位。使用中发现定位不准时请调整会话内消息布局后重试。
- 测试版说明:引用功能走「坐标 + OCR + SendInput」模拟点击路线,依赖微信 4.1.x 自绘渲染布局;随窗口尺寸/DPI/会话内容不同可能存在定位偏差。右键采用
- 移除
desktop_available()桌面白屏判定:控件定位已全面走 UIA,不再依赖整窗截图白色占比采样——该判定在微信窗口正常时曾误报「窗口不可见」。ensure_visible()现以窗口句柄存活判定可见性,保留「最小化遮挡窗口 + 置顶微信」的前置动作。
v1.2.0.3(2026-08-31)
- 修复 WAL 合并后数据库解密缓存损坏导致死循环:
_check_merged之前用SELECT count(*) FROM sqlite_master只查 schema 树,数据页损坏仍能通过校验,缓存 stamp 标记为"最新"后每秒轮询复用坏缓存,反复抛database disk image is malformed形成死循环。改用PRAGMA quick_check全库校验(含数据页/索引页);新增_invalidate_cache()清空解密.db/.stamp缓存;查询统一入口_run_msg_query:命中 malformed 时清缓存→重建→自动重试一次;_msg_conn及时关闭分片库连接避免 Windows 文件占用。
v1.2.0(2026-08-30)
注:本版本合并了 1.1.10.2 之后、此前尚未发布的全部改动(1.1.10.3 → 1.1.10.7 的内容)。
- 朋友圈智能定位与自动点赞:
Moment.find_moment(publisher, keyword, ...)采用 数据库路线计算目标偏移 + UIA 路线滚动定位 的混合方案——先用本地sns.db标尺算出目标动态相对当前可见条目的索引偏移,再按偏移方向动态滚动(自适应步长),最终定位到指定作者/关键词的朋友圈,摆脱了“盲目往下翻”和“过早判定未找到”的问题。 - “…”浮层识别:
Moment._locate_more_click/_find_more_button通过模板匹配(深浅两套模板,随包打包进assets/)定位朋友圈右下角“…”按钮并点击,未识别到时自动微调滚动重试,弹出点赞/评论浮层。 - 一键点赞:
Moment.LikeMoment(publisher, keyword, ...)一键完成“定位 → 点…→ 浮层内点赞”;浮层内“赞/评论”按钮通过从 UIA 根节点向下做全局深度遍历按名称匹配后按其中心坐标点击。 - 朋友圈点赞/评论(UIA 控件路线):
WeChat现暴露Moment属性与SwitchToMoments(),通过热激活mmuiUIA 树并点击导航栏“朋友圈”。Moment.Like(item, cancel=False)与Moment.Comment(item, content, reply_to=None)基于 UIA 控件对动态条目操作——点赞/评论属服务端行为,只能走界面(数据库路线保持只读)。UIA 树不可用时WeChat.Moment为None。示例wechatauto/demo_moments_interact.py。 - 朋友圈图片/视频下载:新增
MomentDB.download_media(media, save_dir, kind)——优先从本地缓存原样复制(离线、秒级),缓存缺失时回退到 CDN url 下载;MomentDB.download_moment_media(feed, save_dir, ...)批量把一条动态的图片/视频落地到目录。find_local_media(md5, kind, size)按 md5 定位缓存文件,对视频按totalSize跨整个Sns/Video树按大小近似匹配(视频缓存文件名是内容哈希、与朋友圈记录里的 md5 无关,故用大小找回真实 MP4)。parse_feed现通过videomd5/videoDuration/type区分图片与视频,并记录每条媒体的size。示例wechatauto/demo_moments_download.py。 - 朋友圈读取 API(数据库路线):
MomentDB.get_moments()新增since/until(Unix 秒时间过滤)与keyword(正文过滤),并支持limit=0全量返回。新增增量同步latest_tid()/get_moments_since(),便于轮询检测「有新朋友圈」。新增互动通知get_interactions()/interactions_unread_count(),读取「他人对我朋友圈的赞/评论」表(SnsMessage_tmp3)。新增comment_tree()/comment_reply_to(),按comment_id/ref_comment_id将评论组织成回复树。 - 新增群名 ↔ 群ID 互查:
get_groups()现在返回每个群的真实name(来自 contact 表,无群名时回退 wxid)。新增group_name_to_id(name)(先精确匹配,再子串/模糊匹配)与group_id_to_name(chatroom_wxid),可按群显示名反查群 wxid(及反向),便于与get_group_members()、at_member()配合使用。 - 新增群成员枚举与变动监测(只读,无需 UI):新增
WeChatDB.get_groups()/get_group_members(chatroom_wxid),读取contact.db的chat_room+chatroom_member+contact三表关联,返回每个群的成员。新增GroupMemberWatcher(经get_group_member_watcher创建):先capture()存基线快照,之后poll()对比当前成员输出joined/left差异,实现轮询式群成员变动监测。可与现有 UI 自动化的at_member()配合使用。 - 新增可运行示例
wechatauto/demo_moment_find.py、demo_moment_more.py、demo_moment_like.py;新增依赖pyautogui、opencv-python。
v1.1.10.2(2026-08-30)
- 修复全新安装后长文本仍显示
[文本]:新增必需依赖zstandard。微信4.x 将长文本的message_content存为 zstd 压缩帧,由_friendly_content通过import zstandard解压。但zstandard此前不在必需依赖中,用户机器未安装时该 import 被静默吞掉,长文本退化为[文本]占位符(监听本身正常,故难定位)。现已将zstandard加入必需依赖;_friendly_content同时新增惰性双包名导入(zstandard/zstd,见_get_zstd_module()/_zstd_decompress())。
v1.1.10.1(2026-08-29)
- 修复消息读取的
AttributeError: 'sqlite3.Row' object has no attribute 'get':_msg_row_to_dict对sqlite3.Row调用了.get("compress_content"),而该对象只支持下标[]访问。当消息内容解压后仍为占位符(如表情等特殊类型)时走此分支,导致实时Listener轮询循环崩溃。现改为下标访问并容错,get_messages/get_new_messages/get_message_row均修复。
v1.1.10(2026-08-27)
- 新增原图下载功能:
MediaDownloader.download_image_original()通过UI自动化点击图片消息,触发微信下载原图。解决了群聊图片只有缩略图可用的限制。 - 修复长文本消息内容提取:添加zstd解压支持、
compress_content回退,修复换行符处理问题。
v1.1.9(2026-08-27)
- 修复微信4.1.13+密钥提取:调整密钥提取优先级,将
Config.Cipher内存扫描置于extract_master_key_from_cfg之前。cfg提取方式在微信4.1.13.12上返回错误的主密钥,而Config.Cipher扫描(从XOR解码的blob中读取原始enc_key值)工作正常。此修复解决了新版微信"0/24密钥验证通过"的问题。
v1.1.8(2026-08-25)
- 修复 MediaDownloader 缺失
_derive_xor_key方法:v1.1.7 发布时意外遗漏了_derive_xor_key()方法,但代码路径(_decrypt_v2、detect_image_key)仍引用它,导致图片解密时出现AttributeError。已恢复该方法,用于从缩略图_t.dat/_h.dat文件反推 XOR 密钥。 - 修复群聊
sender_id→sender_username映射:Listener回调现在会在消息字典中返回sender_username(wxid 格式),通过message_resource.SenderName2Id映射表将数字sender_id转换为可直接用于search_contact()的用户名。 - 感谢 uiharukazari0105 报告 v1.1.7 版本缺失
_derive_xor_key方法的 bug。
v1.1.6.1(2026-08-20)
- PyPI 描述修复:1.1.6 发布时漏同步
README_pypi.md(描述停留在 1.1.5.1),本补丁版补全 v1.1.6 更新记录并同步版本号。
v1.1.6(2026-08-20)
- 缺密钥报错自动诊断:
数据库无可用密钥报错前会自动检测三项最常见根因——Python 位数(32 位读不了 64 位微信内存)、逐个微信进程的OpenProcess/ReadProcessMemory读取权限、多账号目录与所选账号对比(提示用WeChatDB(account=...)显式指定),无需先手动运行diagnose_keys。 - 新增诊断工具:
wechatauto/diagnose_keys.py(微信登录后运行python -m wechatauto.diagnose_keys)输出库版本、Python 位数、微信进程 PID 及逐个进程的读取权限检测、磁盘全部账号与所选账号对比、已缓存密钥、进程内存重新提取结果与密钥校验情况——报密钥提取问题时把输出完整发给维护者即可定位。 - 跳过
migrate\unspportmsg.db:该库是微信保留的「未支持消息」库,进程内存中无对应密钥、代码也从不会访问;此前它会让每次初始化都触发一次全进程内存扫描。
v1.1.5.1(2026-08-18)— 测试版 / beta
- 修复实时监听不触发:
WeChatDB.get_new_messages()引用了未定义的found(NameError 被Listener._poll_once吞掉),导致消息回调从未触发——包括从未聊过天的联系人的首条消息。 - 动态消息分片:
_message_dbs()现在会重新扫描磁盘,微信运行中新建的分片(如message_5.db)会被自动发现并提取密钥。
v1.1.5(2026-08-18)
- 版本号规范化:语音跨库下载修复后整理补丁版本号(1.1.4.2 → 1.1.5)。
v1.1.4.2(2026-08-18)
- PyPI 描述清理:移除 v1.1.4 版本记录中关于 demo 默认群改动的条目。
v1.1.4.1(2026-08-18)
- PyPI 页面中英双语:PyPI 描述合并中文(
README.zh-CN.md)与英文(README.md)两个版本,中文版在包页面可见。
v1.1.4(2026-08-18)
- 跨全部媒体库下载语音:
download_voice()现在搜索所有media_*.db(不再只查media_0.db)——微信把语音分片存到多个媒体库;此前存在media_1.db等的语音无法找到(感谢 uiharukazari0105)。 - 群聊图片缩略图回退:群聊的图片原图只有被点开(查看)后才会落盘本地;原图未点开不下发时,
download_image自动回退到缩略图(_t.dat),保存为带_thumb后缀的文件。 WeChatDB._find_media_rows(user, types):新增批量查媒体接口——返回某会话指定local_type集合的全部媒体local_id(用于批量下载)。demo_media.py --images N:按local_type直接从数据库下载某会话最近 N 张图片,绕过总消息数--limit的限制——群聊消息上万条时不再「只列出几张图」。
v1.1.3(2026-08-17)
- WXAM (wxgf) 图片解码:微信 4.x 现在把普通图片(不仅是动图贴纸)也存进 WXAM 容器
(内部为 HEVC 比特流)。
MediaDownloader.download_image新增 wxgf 处理:提取 HEVC Annex-B 流,用 ffmpeg 转码为 JPG(优先用imageio-ffmpeg内置二进制,其次 PATH 上的 ffmpeg);ffmpeg 不可用时不再丢弃数据,改为保存原始解密数据为.wxgf兜底。 - 新增依赖:
imageio-ffmpeg>=0.4.9。
v1.1.2(2026-08-16)
- UIA 驱动线程安全:
WeChatUIA实例化时在当前线程初始化 COM(CoInitializeEx,幂等)——修复后台线程/宿主进程(如 WeChatBot)实例化报「尚未调用 CoInitialize / 无法加载 UIAutomationCore.dll」。 - 主窗口过滤:只认加载了
Weixin.dll的主进程窗口,过滤无 DLL 的辅助进程窗口(其热激活必然失败,不再刷噪音警告)。 - 转发语音修复:
Chat.ForwardVoiceMessage未指定目标时用self(原_cur()可能误取会话)。 - UI 锁可重入:
LockManager同线程可重入——@uilock函数互相调用(如ForwardVoiceMessage→VoiceMessage.forward_to)不再死锁。
v1.1.1(2026-08-16)
- 撤回消息(
Chat.RecallLastMessage/uia_driver.recall_last_message):右键最新一条自己发的消息 → UIA 优先 (主窗口树内mmui::XMenuView菜单项定位「撤回」,Invoke/Select 或鼠标点击),OCR 兜底(全屏识别「撤回」 文字定位点击);菜单只剩「删除」(超过 2 分钟撤回时限)时返回失败。 - UIA 健壮性:菜单项查找限定在主窗口子树内(避免触发 Windows UIA 根遍历的系统挂起 bug);移除脆弱的
WindowControl(ClassName=...)兜底定位。 - 媒体修复:视频 id bytes→str 解码(
MediaDownloader),修复视频文件定位。 demo_media.py--photos默认 3 → 10。
v1.1.0(2026-08-15)
- 图片 AES 密钥自动监控捕获(
media.py):微信 4.x 的 V2 图片 AES 密钥仅在 查看图片大图时短暂驻留进程内存(实测约 5 分钟后释放)。_scan_aes_key()新增monitor模式——首次扫描未命中时自动持续轮询并提示去微信点开一张图片看大图, 密钥进入内存后自动捕获并持久化到image_keys.json,之后免扫描直接解密。 首次用户无需手工找密钥,看图一次即可完成配置。 - 修复进程排序扫描 bug:移除
_scan_aes_key中按内存占用排序进程的逻辑 (GetProcessMemoryInfo结构体大小传错导致工作集全为 0,reverse排序反而把 主进程排到最后,错过密钥驻留窗口),恢复按微信进程原顺序扫描(主进程优先命中)。 - 语音/视频/文件不受影响:仅图片
.dat为 V2 AES 加密需密钥;语音(SILK)、 视频(MP4)、文件均为明文直接读取。
v1.0.9(2026-08-14)
- open_chat 账号/微信号搜索修复(
uia_driver.py):微信搜索框不认 wxid (系统账号),open_chat传入 username 时自动通过本地 DB 映射为昵称/备注/ 微信号再搜索(_resolve_search_keyword),并清空搜索框残留重试; 实测open_chat('wxid_sb9or2x9zxj012')成功。 - UIA 表情包精确读取(
msgs/mtype.py+uia_driver.py):热激活后消息 列表暴露mmui::RecyclerListView,新增find_in_message_list()用鼠标滚轮 驱动虚拟化列表滚动,按 ClassName/Name 定位表情行并取 BoundingRectangle 精确坐标;EmojiMessage.capture()优先走 UIA 定位 + 方向感知气泡裁剪 (_crop_bubble_from_row),实测 1.1s 裁出 271×271 表情,替代原先 「截图全消息区 + 连通域猜气泡」的脆弱方案;失败自动回退原连通域逻辑。 - 语音通话(
uia_driver.voice_call+Chat.VoiceCall):标题栏暴露mmui::ChatVoIPView.voip_button(Name=语音通话),控件树动态重建需重试 定位;video=True 尝试找视频通话按钮(当前版本未暴露,通常失败)。 - 拍一拍(
uia_driver.poke+Chat.Poke):微信 4.x 拍一拍只能通过 右键对方头像触发,菜单为自绘不暴露 UIA;实现为「内容重心定位 friend 消息行 → 右键头像 → 全屏 OCR 定位「拍一拍」→ 点击」,实测 3.2s 发出 (网络正常时对方收到,网络异常时微信显示失败提示,链路本身正确)。 EmojiMessage.capture()/voice_call/poke失败均自动回退或返回 WxResponse 失败,不影响既有 OCR 发送路径。
v1.0.8(2026-08-13)
- 🎉 特别感谢 vesio:在 issue #1 中提供了微信 4.1.12 可出 UIA 控件树的代码与调试思路,本版 UIA 混合驱动由此而来;
- UIA 混合驱动(
uia_driver.py,微信 4.1.12.26 实测):- 新增
WeChatUIA引擎:冷启动时Qt51514QWindowIcon只是空壳(Qt 无障碍门未激活),通过写 Weixin.dll 内的 Qt accessibility gate (RVA 扫描定位)热激活后,锚点变为mmui::MainWindow,搜索框mmui::XValidatorTextEdit/ 搜索下拉search_list/ 输入框chat_input_field全部可用; - 发送链路全部走 UIA:搜索下拉选人(
search_item_*)打开会话 →chat_input_field直接输入 + 回车发送,current_chat校验防误配, 无 OCR 抖动;Windows 冷状态热激活后 UIA 树保持可用; guia.py集成混合路径:_get_uia()惰性启用,open_chat/send_msgUIA 优先、OCR 兜底——UIA 树不可用(版本变更新增 RVA) 或失败时自动降级到坐标 + 放大 OCR 方案,首次失败本次会话内不再重试。- 实测:
send_msg('文件传输助手')10.2s、send_msg('某好友')13.2s 均走 UIA 并数据库确认成功(含 verify);UIA 对生僻字会话名不再依赖 OCR 识别。
- 新增
- 新增依赖:
uiautomation(UIA 客户端库)。
v1.0.7(2026-08-13)
- OCR 识别可靠性提升(
guia.py,针对生僻字/小字号会话名识别失败):- 新增
ocr_zoomed():对区域放大 N 倍后再 OCR,坐标按 1/N 还原;实测 微信小字号中文在放大 3 倍时识别率最高(放大 6 倍图像过大反而整块 返回空),超过 5 倍即回落; _chat_is_open标题检测改用放大 3 倍 + y 范围扩到 0-185(微信 4.x 标题实际渲染在 y≈80-180,原 15-100 的区间会漏检已打开的会话);_search_chat搜索回退排除「群聊」节标题以下行、含「包含」的群成员 预览行(如「00,包含:某好友」)与群名结尾行,只点联系人,修复 「搜索选中群聊而非联系人」的问题;_chat_open_confirmed改为优先标题命中,标题读不到才退而用面板 非空白作为已打开判据,修复「点错会话也误判成功」;open_chat首查_chat_is_open && _pane_has_content,右侧面板已打开 目标会话时直接成功(不再滚动/搜索),已打开场景耗时 45s → 2.7s。
- 新增
- OCR 多轮投票(
find_session._scan_vote):WinRT OCR 对生僻字存在 抖动(同一行不同轮次可能读出「某好友」或「亠人五」)。对侧栏放大 3x 扫描 4 轮,命中行按 y 聚类(≤30px 视为同行),票数 ≥2 才返回,显著 降低误配;普通会话仍走单轮快速路径,无性能损失。 - 实测:
find_session('某好友')连续 5 轮 4/4 票一致、稳定命中;open_chat+send_msg全链路成功。
v1.0.6(2026-08-11)
- 元数据与门面优化:README 增加徽章(PyPI 版本/下载量/Python 版本/License/Stars)、PyPI description/keywords/classifiers SEO 优化、Homepage 修正为项目 GitHub 地址。
v1.0.5(2026-08-10)
- 表情截图跨机器修复:
EmojiMessage.capture()表情气泡自动裁剪全面重构:- 主路径改用连通域分析(
_crop_last_bubble),按消息方向(左=对方/右=自己) 精确定位最后一条消息气泡,自动过滤细长竖条(滚动条/面板边框)、剔除头像类 小元素,从根源解决右缘滚动条/边框被当成内容导致的右侧大片空白; - 圆形表情顶部/底部在缩放采样时因 LANCZOS 模糊丢失边缘像素:加大裁剪边距
(
pad = max(10, scale*5))并在全分辨率下逐像素边缘扩展找回丢失内容, 且扩展遇连续空白行(消息间分隔)即停,避免吃进相邻消息; - 时间戳等居中小文字(水平居中约 50% 宽度)不再被误当成消息:方向判定加 阈值(左侧 <45% 宽度、右侧 >55%),居中元素两边都不匹配;
- 最终尺寸校验:
min(crop) < 50视为时间戳/文字误判,自动回退到 「消息分隔空白」「头像锚点」等备用定位,仍过小则判定失败返回 None; - 本机与高 DPI 机器均已实测通过(完整表情、无空白、无切顶、不截时间戳)。
- 主路径改用连通域分析(
v1.0.4(2026-08-10)
- 多特征兜底窗口定位:主窗口定位不再只依赖类名
Qt51514QWindowIcon(类名降级为软条件),联合 进程名weixin.exe/ 窗口可见 / 大尺寸 (≥800px)/ 标题关键词(微信/Weixin/WeChat)评分定位——Qt 升级改名 (Qt51514→Qt6xxx)也不失效;渲染子窗口按前缀MMUIRenderSubWindow匹配(兼容MMUIRenderSubWindowHW/MMUIRenderSubWindow等变体), 找不到时退回用主窗口矩形计算坐标。 - 布局自动校准:首次运行自动校准——OCR 检测「搜索」「发送」锚点实测
布局比例,保存到
~/.wechatauto/layout-<机器标识>.json,之后自动加载; 布局漂移(DPI/窗口尺寸/缩放变化)时自动重新校准。 - 最大化状态保持:激活窗口时先
GetWindowPlacement记录状态,原为 最大化则用SW_SHOWMAXIMIZED恢复(原SW_RESTORE会把最大化窗口 缩成普通大小),最小化恢复不再破坏用户窗口布局。 - 发送模块窗口兜底:
find_main_window类名查找失败后按标题「微信」 兜底,适配类名不同的机器。
v1.0.3(2026-08-08)
- 文本消息还原:微信 4.x 部分文本消息 content 为「容器头 + UTF-8 明文 +
尾部填充」结构,此前显示为
[文本]/空。新增_extract_text_from_blob还原明文,数据库读取与 bot 均可见真实内容(含群消息wxid_xxx:前缀)。 - 表情截图方向感知与兼容性:
_db_row_to_message写入msg.attr(self/friend),EmojiMessage.capture()按方向定位气泡(自己发的用 消息分隔空白、对方发的用头像锚点),避免截图前自己又发了一条消息时误截到 自己的气泡;裁剪阈值自适应截图尺寸,跨分辨率/DPI 可用。微信 4.x 主窗口为 Qt 自绘渲染,不暴露 UIA 子树,故截图定位全部基于屏幕像素分析。 - 发送会话复用:
send_msg记录_current_chat,目标会话已打开时跳过open_chat(重扫侧栏+点击),逐条连续发送不再反复点击对话框,效率提升。 - 搜索联系人选第一条:
_search_chat按视觉顺序排序并过滤「搜索网络结果/ 搜一搜」节标题,点选第一条联系人而非网络搜索。 - 动画表情不再落盘伪
.gif:download_image识别到wxgf容器(微信 动画表情私有格式)时返回None,不再生成打不开的假图片。 Listener.stop()崩溃修复:db.py补import sys(_run/_poll_once使用sys.stderr却未导入)。
v1.0.2(2026-08-08)
- 表情消息支持:新增
EmojiMessage消息类型(type='emotion'), "动画表情"不再被归为OtherMessage,并按收发方向提供FriendEmojiMessage/SelfEmojiMessage。微信 4.x 表情消息在本地数据库中的 content 为加密数据,无法直接还原成图片,因此新增EmojiMessage.capture(): 采用「打开会话 → 滚动到底 → 截取消息区 → 自动裁剪最后一条消息气泡」 的屏幕截图方案,返回图片路径,可直接供 AI 视觉识别使用 (示例见demo_emoji_capture.py)。 - 监听器并发工作线程:
Listener回调移到独立工作线程执行,每个被监听 会话对应一条串行工作线程——同一会话内消息按序处理、不同会话间并行; 轮询线程只负责读取数据库并分派任务,不再被慢回调(AI 调用 / 图片识别等) 阻塞,stop()优雅关闭所有工作线程。 - 数据库消息兼容增强:
_db_row_to_message支持 bytes 类型 content (自动解码还原文本)、local_type缺失时自动推导消息类型,_extract_group_sender兼容 bytes 内容。
一、项目状态
| 能力 | 状态 | 实现方式 |
|---|---|---|
| 读取消息 | ✅ 已完成并验证 | 本地数据库解密(wechatauto/db.py) |
| 消息监听(轮询) | ✅ 已完成并验证 | Listener + get_new_messages 增量回调 |
| 表情消息识别与截图 | ✅ 已完成并验证(v1.0.3 方向感知) | EmojiMessage + capture()(屏幕截图自动裁剪) |
| WAL 增量合并 | ✅ 已修复并验证 | 帧盐校验合并 -wal(见 §2.4) |
| 历史消息全量导出 | ✅ 已完成并验证 | export_history(JSON / SQLite) |
| 媒体下载(图片/语音/文件) | ✅ 已完成并验证 | wechatauto/media.py(图片 V2 解密) |
| 朋友圈读取 | ✅ 已完成并验证 | MomentDB 直接读 sns.db |
| 多账号管理 | ✅ 已完成并验证 | list_accounts() + account= 参数 |
| 读取会话列表 | ✅ 已完成并验证 | 同上 |
| 搜索联系人 | ✅ 已完成并验证 | 同上 |
| 发送消息 | ✅ 已完成并验证 | UIA + 坐标+OCR 混合(wechatauto/guia.py) |
| 发送文件/图片/回复/艾特 | ✅ 已完成并验证 | 剪贴板 CF_HDROP + OCR |
| 语音通话 / 拍一拍 | ✅ 已完成并验证 | UIA 按钮 + OCR 菜单(Chat.VoiceCall / Chat.Poke) |
| UI 自动化(UIAutomation) | ✅ 热激活后可用 | 写 Weixin.dll Qt accessibility gate,物化 mmui::* 树 |
结论:微信 4.1.x 聊天界面使用自绘渲染(MMUIRenderSubWindow*),冷启动
对 UIAutomation 只暴露 Qt51514QWindowIcon 空壳(原 wxauto 的 UI 方案因此
失效)。本项目通过热激活 Qt accessibility gate(写 Weixin.dll 内读屏
标志位,从 qt.accessibility.core 引用扫描 RVA)物化 mmui::* UIA 树,
实现发送/语音通话/拍一拍等操作(UIA 优先、坐标+OCR 兜底);消息读取仍走
「本地数据库解密」(已全链路验证)。
二、读取原理
微信 4.x 的数据存放在本地 SQLCipher 4 加密的 SQLite 数据库中:
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)
└── ...
2.1 密钥提取(进程内存只读扫描)
每个数据库有独立的 32 字节密钥,保存在微信进程内存中的
com.Tencent.WCDB.Config.Cipher 配置对象里:
- 在 Weixin.exe 所有可读内存区域中查找该字符串;
- 由字符串地址定位配置对象(
[ptr][len]结构回溯); - 数据块与固定掩码异或后得到
x'<64位hex密钥><32位hex盐>'明文配置; - 用 SQLCipher 4 HMAC 校验规则验证每个候选密钥;
- 验证通过的密钥保存到
%TEMP%\wechatauto_db\<账号>\keys.json缓存。
2.2 数据库解密
- SQLCipher 4,页大小 4096,
PBKDF2-HMAC-SHA512(加密密钥 256000 次迭代); - 解密结果按页写入临时目录,校验源 mtime/size 复用缓存;
- 首次解密 contact.db 约 6s,之后全部秒级。
2.3 消息查询
- 会话名 →
Md5(会话微信号)→ 表名Msg_<md5>(同一会话可能分片在多个message_*.db,按sort_seq合并排序); - 关键列:
local_type、real_sender_id(2=自己,其他为数字 id,可通过message_resource.SenderName2Id反查微信号)、server_id、packed_info_data(图片/视频 md5)、sort_seq。
2.4 WAL 增量合并(已修复)
微信 -wal 是预分配文件:checkpoint 时 WAL 头 salt+1 并清零写游标,但
旧世代帧仍留在文件中。若合并时不过滤帧盐,会把过期页覆盖进主库导致
database disk image is malformed。修复方案:
_merge_wal读取 WAL 头后仅合并 salt 与当前 WAL 头一致的帧, 旧世代帧直接跳过;- 缓存 stamp 加入版本号
STAMP_VERSION=2,旧损坏缓存自动强制全量重建; - 合并结果用
PRAGMA integrity_check校验,失败自动重试全量重建。
验证:contact.db 合并后 integrity OK,2354 个联系人全部可查。
2.5 媒体存储与解密(图片 v2 格式)
- 图片:
msg\attach\<会话md5>\<YYYY-MM>\Img\<md5>.dat(加密); - 语音:
media_0.db→VoiceInfo.voice_data(SILK 明文 BLOB); - 文件:
msg\file\<YYYY-MM>\<原文件名>(原名来自 message_resource); - 视频:
msg\video\<YYYY-MM>\<id>.mp4(未落盘时返回 None)。
图片 .dat 为 v2 格式:[6B sig 070856320807][4B aes_size LE][4B xor_size LE]
- AES-ECB 密文 + 明文段 + 异或段:
- AES 密钥:16 字节 ASCII,账户级稳定密钥,但仅在微信查看图片时驻留
进程内存。
MediaDownloader通过内存扫描反测(AES 解首块后校验 JPEG/PNG 魔数)获取,命中后持久化到image_keys.json;也支持image_key=参数 显式注入。本机实测:单一密钥稳定解密 35/40 张随机图片(其余为微信动画 表情容器wxgf)。 - XOR 密钥:单字节,从同图缩略图
<md5>_t.dat尾部 JPEG 结束标记FF D9反推(key = tail[0] ^ 0xFF)。
三、快速开始
3.1 安装
pip install -e .
# 坐标+OCR 发送路线额外依赖:
pip install winsdk pypinyin
3.2 示例程序
python demo_db.py
3.3 代码示例
from wechatauto import WeChatDB
db = WeChatDB() # 自动检测账号与数据目录(微信需已登录)
info = db.get_self_info() # 当前账号昵称
for s in db.get_sessions(limit=10): # 会话列表
print(db.get_nickname(s["username"]), s["unread"])
hits = db.search_contact("Ayi") # 搜索联系人
who = hits[0]["username"]
for m in db.get_messages(who, limit=10): # 最近消息
print(m["create_time"], m["sender_id"], m["type"], m["content"])
3.4 媒体下载
from wechatauto import WeChatDB, MediaDownloader
db = WeChatDB()
md = MediaDownloader(db) # 可传 image_key="..." 注入图片密钥
key = md.detect_image_key() # 内存扫描/缓存取 AES+XOR 密钥
print(key)
for m in db.get_messages("filehelper", limit=50):
out = md.download_media("filehelper", m["local_id"]) # 按类型自动分发
if out:
print("已下载:", out)
3.5 朋友圈读取
from wechatauto import WeChatDB, MomentDB
md = MomentDB(WeChatDB())
for feed in md.get_moments(limit=10): # 时间线(3382 条全量可读)
print(feed["nickname"], feed["text"])
print(" 图片:", [i["md5"] for i in feed["images"]])
print(" 赞:", [l["nickname"] for l in feed["likes"]])
print(" 评论:", [(c["nickname"], c["content"]) for c in feed["comments"]])
md.download_media(feed["images"][0]) # 本地缓存或 URL 拉取
3.6 消息监听
from wechatauto import WeChatDB
from wechatauto.db import Listener
db = WeChatDB()
lst = Listener(db, interval=1.0)
lst.add_listener("filehelper", lambda msg, lst: print("新消息:", msg["content"]))
lst.start()
# ... 业务代码 ...
lst.stop()
- 回调在独立工作线程中执行(v1.0.2):每个被监听会话对应一条串行 工作线程,同一会话内消息按序处理、不同会话间并行;轮询线程只负责读取 数据库并分派任务,不会被慢回调(AI 调用 / 图片识别等)阻塞。
3.7 历史导出
db.export_history(r"D:\backup\chat.json", fmt="json") # 全部会话
db.export_history(r"D:\backup\chat.db", fmt="sqlite")
db.export_history(r"D:\backup\one.json", fmt="json",
users=["filehelper"], limit_per_chat=1000)
3.8 多账号
from wechatauto import list_accounts, WeChatDB
for a in list_accounts():
print(a["account"], a["wxid"])
db2 = WeChatDB(account="wxid_xxx_abcd") # 显式指定账号(缓存按账号隔离)
3.9 表情消息与截图
微信 4.x 的"动画表情"消息在本地数据库中 content 为加密数据,无法直接还原成
图片。v1.0.2 起监听回调中的表情消息为独立的 EmojiMessage 类型
(type='emotion',FriendEmojiMessage / SelfEmojiMessage 按收发方向区分),
并支持对屏幕上的表情气泡自动截图:
# 在 Listener 回调内,把消息 dict 转成消息对象后再截图:
def on_msg(msg, listener):
if msg["type"] == "动画表情":
from wechatauto.wx import _db_row_to_message
m = _db_row_to_message(msg, chat) # chat: 当前会话
path = m.capture() # 返回 PNG 路径,供 AI 视觉识别
capture(save_dir=None) 流程:打开会话(已打开则跳过,避免刷新消息列表导致
控件失效)→ 滚动到底 → 截取消息区 → 按消息方向定位最后一条消息气泡:
- 自己发的消息(
attr='self',右侧无头像):用「消息分隔空白」定位 消息顶部,空白阈值按截图高度自适应(约消息区高度的 2.5%), 跨分辨率/DPI 保持一致; - 对方发的消息(
attr='friend',左侧有头像):优先检测头像圆形彩色块 的顶部作为消息顶部(特征跨分辨率稳定),失败时回退消息分隔空白。
返回图片路径(失败返回 None)。独立示例:python demo_emoji_capture.py。
调试时可保留 ~/pane_diag_raw.png(每次截图保存的消息区原图)与
[CAP] 日志行(截图尺寸、消息方向、裁剪路径、结果尺寸)用于排查。
四、API 参考
WeChatDB(db_dir=None, keys_file=None, workdir=None, account=None)
| 方法 | 说明 |
|---|---|
get_self_info() -> dict |
当前账号(username / nick_name / remark) |
get_sessions(limit=100) |
会话列表:username / unread / summary / last_time |
search_contact(keyword) |
按昵称/备注/微信号搜索 |
get_messages(user, limit, offset) |
读取指定会话消息 |
get_message_row(user, local_id) |
单条原始消息(含 server_id / packed_info,媒体用) |
get_new_messages(user, since_seq) |
sort_seq > since_seq 的增量消息(升序) |
get_nickname(user) |
微信号 → 显示昵称 |
list_message_chats() |
所有含消息的会话(md5 / 昵称 / 消息数) |
export_history(out_path, fmt, ...) |
全量导出 JSON / SQLite |
extract_keys() |
手动触发密钥提取 |
wxid / account / account_dir |
当前账号信息 |
list_accounts()(模块级) |
扫描本机所有微信账号 |
auto_detect_db_dir()(模块级) |
自动定位数据目录(配置文件 → 注册表 → 常见默认目录) |
MediaDownloader(db, save_dir=None, image_key=None)
| 方法 | 说明 |
|---|---|
detect_image_key(refresh) |
取 (AES 密钥, XOR 密钥),命中后持久化 |
decrypt_image(dat_path) |
解密单个 .dat(自动识别 v1/v2) |
download_media(user, local_id) |
按类型分发下载 |
download_image / _voice / _video / _file |
各类媒体下载 |
copy_files_to_clipboard(paths) |
CF_HDROP 写剪贴板(发送附件用) |
MomentDB(db)
| 方法 | 说明 |
|---|---|
get_moments(limit, offset, username) |
朋友圈时间线(最新在前) |
get_moment(tid) / get_my_moments(limit) |
单条 / 我的动态 |
find_local_media(md5, kind) |
本地缓存查找(Sns\Img / Sns\Video) |
download_media(media, save_dir) |
缓存优先,否则 URL 拉取 |
Listener(db, interval, watermark)
add_listener(user, cb) / remove_listener / start / stop / watermark。
WeChatGUI(发送,锁屏不可用)
| 方法 | 说明 |
|---|---|
send_msg(text, who, verify) |
文本发送(OCR 定位 + 剪贴板粘贴) |
send_file(path, who, verify) |
文件(CF_HDROP 粘贴 + 回车) |
send_image(path, who, verify) |
图片(同上) |
reply_msg(text, who, verify) |
回复最近消息(悬停 + OCR 回复入口) |
at_member(member, text, who, verify) |
群聊 @ 成员 |
open_chat / focus_input / bring_to_front |
基础操作 |
一行式:quick_send / quick_send_file / quick_send_image / quick_reply。
五、已知限制
- 需要微信登录:数据库密钥存于进程内存,首次使用需微信运行中 (提取后本地缓存);重新登录后密钥变化需重新提取(自动校验失败重扫);
- 图片 AES 密钥瞬态:仅在微信查看图片时驻留内存;
MediaDownloader扫描命中后会持久化(image_keys.json),也可用image_key=显式传入; - 发送为 GUI 操作:锁屏/会话断开时窗口不响应,发送接口返回明确失败; 文件/图片/回复/艾特代码已完成但需桌面解锁后实测;
- 视频文件未落盘时不可下载:视频 mp4 仅在本地存在(
msg/video)时 返回,否则返回 None; - 发朋友圈功能已舍弃:4.x 的发表为自绘界面操作,不可靠自动化; 本库仅保留朋友圈读取/点赞/评论能力。
- 评论/回复功能仅供测试:
回复某条评论(ReplyComment)通过截图 OCR 定位评论区评论行,再驱动界面点击/粘贴/发送;朋友圈评论区为自绘、 布局多变,稳定性无法保证,仅建议在测试账号中验证流程,勿用于生产。 - 引用消息功能(BETA)仅供测试:
quote_msg通过坐标 + OCR + SendInput 模拟右键菜单选择「引用」,依赖微信 4.1.x 自绘渲染布局,随窗口尺寸/DPI/ 会话内容不同可能存在定位偏差,仅建议在测试账号中验证流程。
六、发送消息(坐标 + OCR)
微信 4.1.12+ 聊天界面自绘渲染、无无障碍节点,发送走
「屏幕坐标 + 本地 OCR」(wechatauto/guia.py):
- 多特征兜底定位主窗口(类名
Qt51514QWindowIcon只是「软条件」, 联合标题 / 进程名weixin.exe/ 可见 / 大尺寸评分,Qt 升级改名也不 失效),再按前缀MMUIRenderSubWindow找渲染子窗口(兼容MMUIRenderSubWindowHW/MMUIRenderSubWindow等不同版本类名; 找不到时回退用主窗口矩形计算坐标); - 布局用渲染子窗口相对坐标描述,运行时换算为屏幕绝对坐标;首次运行自动
校准(OCR 检测「搜索/发送」锚点实测比例),保存到
~/.wechatauto/layout-<机器>.json,之后自动加载、布局漂移自动重校准; - OCR 识别会话列表点击目标(失败走搜索框;生僻字/小字号会话名自动放大 3 倍 + 多轮投票重扫,搜索回退只点联系人、自动排除群聊与群成员预览行);
- 扫描输入框白色区定位并聚焦;
- 文字以「剪贴板 + Ctrl+V」输入(避免中文输入法拦截),失败回退拼音组合;
- OCR 定位「发送」按钮(找不到回退回车键);
verify=True时用WeChatDB读回确认。
文件/图片通过 CF_HDROP 剪贴板 + Ctrl+V 插入草稿再回车发送,绕开自绘 「+ 菜单」定位;回复/艾特分别走悬停 OCR 工具栏与成员弹层 OCR。
from wechatauto.guia import quick_send, quick_send_file
quick_send('你好', '文件传输助手', verify=True)
quick_send_file(r'D:\资料\报告.pdf', '文件传输助手')
注意:OCR 需要系统语言包含中文(
Windows.Media.Ocr)。
七、后续路线
- 发送功能实测:桌面解锁后校准 guia 各坐标常量,验证文件/图片/回复/艾特;
- 视频消息下载增强:微信 4.x 聊天视频存储位置仍需确认(本机无样本);
- 性能优化:导出/首扫并行化,内存扫描增量缓存。
八、目录结构
├── wechatauto/
│ ├── wx.py UIA 自动化入口(4.x 受限)
│ ├── guia.py ★ 坐标+OCR 发送模块(文本/文件/图片/回复/艾特)
│ ├── db.py ★ 数据库读取(密钥提取 + 解密 + WAL 合并 + 导出 + 监听)
│ ├── media.py ★ 媒体下载(图片 v2 解密 / 语音 / 视频 / 文件)
│ ├── moment.py ★ 朋友圈(MomentDB 数据库路线 + 旧 UIA 兼容)
│ ├── ui/ UI 控件层
│ ├── msgs/ 消息模型
│ └── ...
├── demo.py UI 自动化示例(微信 4.1 上受限)
├── demo_db.py ★ 数据库读取示例(推荐)
├── demo_guia.py ★ 坐标+OCR 发送示例
├── demo_listen.py ★ 实时消息监听示例
├── demo_reply_at.py ★ 回复/@ 成员实测示例
├── demo_emoji_capture.py ★ 表情消息截图示例
├── docs/技术文档.md ★ 完整技术文档(架构/原理/API/扩展)
└── pyproject.toml
九、免责声明
本项目仅用于个人学习与自动化研究,请遵守微信软件许可协议及当地法律法规, 勿用于任何违反规定的用途。
注:本库完全由AI(opencode+deepseek-v4-flash)生成
十、联系方式
🇬🇧 English
wechatauto-replica — WeChat 4.x Windows Automation (wxauto-compatible)
Automate the WeChat 4.x Windows desktop client (not the web version): read messages, listen in real time, download media, export full history, read Moments (朋友圈), and send messages — by driving the local client directly.
Current version: 1.2.2.3 · Windows 10/11 · Python 3.9+ (verified on 3.12) · WeChat 4.1.12+
Why this project exists: the classic wxauto relies on the UI Automation tree, which WeChat 4.x broke with self-drawn rendering (no accessibility nodes). wechatauto-replica is a drop-in-style replacement: messages are read through local database decryption (SQLCipher 4), and sending uses a UIA + OCR hybrid driver that auto-falls back between engines.
Reading the encrypted contact.db / message_*.db / sns.db files directly from xwechat_files/.../db_storage/ — no web API, all local.
✨ Features
| Capability | Status | How |
|---|---|---|
| Read messages | ✅ verified | Local SQLCipher 4 DB decryption (wechatauto/db.py) |
| Real-time message listening | ✅ verified | Listener incremental polling, per-chat worker threads |
| Emoji message capture | ✅ verified | Screen capture + direction-aware bubble auto-cropping |
| Full history export | ✅ verified | JSON / SQLite |
| Media download (image / voice / file) | ✅ verified | MediaDownloader: image v2 AES decryption, SILK voice, files |
| Moments (朋友圈) read | ✅ verified | Direct sns.db reads (3382 feeds verified) |
| Multi-account | ✅ verified | list_accounts() + account= |
| Send text / file / image / reply / @member | ✅ verified | UIA-first, coordinate + OCR fallback |
| Voice call / Poke (拍一拍) | ✅ verified | UIA buttons + OCR menus |
| UIAutomation tree | ✅ after hot-activation | Writes the Qt accessibility gate inside Weixin.dll |
🚀 Quick Start
pip install -e .
# extra deps for the OCR sending path:
pip install winsdk pypinyin
Read messages
from wechatauto import WeChatDB
db = WeChatDB() # auto-detects account & data dir (WeChat must be logged in)
info = db.get_self_info() # current account
for s in db.get_sessions(limit=10): # session list
print(db.get_nickname(s["username"]), s["unread"])
hits = db.search_contact("Ayi") # search contacts
for m in db.get_messages("filehelper", limit=10): # recent messages
print(m["create_time"], m["sender_id"], m["type"], m["content"])
Send a message
from wechatauto.guia import quick_send, quick_send_file
quick_send("Hello", "filehelper", verify=True) # verify=True reads back from DB
quick_send_file(r"D:\report.pdf", "filehelper")
Real-time listening
from wechatauto import WeChatDB
from wechatauto.db import Listener
db = WeChatDB()
lst = Listener(db, interval=1.0)
lst.add_listener("filehelper", lambda msg, lst: print("new:", msg["content"]))
lst.start()
# ... your code ...
lst.stop()
Callbacks run on dedicated per-chat worker threads: messages in one chat are processed in order, different chats in parallel; slow callbacks (AI calls, image recognition) never block the poller.
Media & Moments
from wechatauto import WeChatDB, MediaDownloader, MomentDB
db = WeChatDB()
md = MediaDownloader(db)
md.detect_image_key() # scan process memory for the image AES key (persisted after first hit)
for m in db.get_messages("filehelper", limit=50):
out = md.download_media("filehelper", m["local_id"])
if out:
print("downloaded:", out)
moments = MomentDB(db)
for feed in moments.get_moments(limit=10):
print(feed["nickname"], feed["text"])
print(" images:", [i["md5"] for i in feed["images"]])
print(" likes:", [l["nickname"] for l in feed["likes"]])
print(" comments:", [(c["nickname"], c["content"]) for c in feed["comments"]])
# download this feed's pictures & videos (local cache first, then CDN url)
saved = moments.download_moment_media(feed, save_dir=r"D:\moments")
print(" saved:", saved)
See wechatauto/demo_moments_download.py for a runnable download demo
(python -m wechatauto.demo_moments_download [N] --out 目录).
Like & comment are server-side actions done through the client UI, so they
use the UIA-tree route (not the local DB) — WeChat hot-activates the mmui
UIA tree, clicks 朋友圈, then acts on UIA feed items:
from wechatauto import WeChat
wx = WeChat()
moments = wx.Moment # None if the UIA tree is unavailable
if moments is None:
raise SystemExit("UIA tree unavailable")
wx.SwitchToMoments()
items = moments.GetMoments()
moments.Like(items[0]) # thumb up
moments.Like(items[0], cancel=True) # undo
moments.Comment(items[0], "Nice!") # comment
moments.Comment(items[0], "Thanks!", reply_to="张三") # reply
Runnable demo: python -m wechatauto.demo_moments_interact [--like N | --unlike N | --comment N 文字]
⚠️ Comment/reply automation is experimental — testing only. The reply-to-a-comment feature (
ReplyComment) locates the comment row on screen via OCR (WeChat's comment area is self-drawn) and then drives the UI to click / paste / send. Layout varies across versions and it is not production-grade — use it only on a test account to validate the pipeline. (plain run lists the latest feeds without touching the UI).
🧠 How It Works
- Reading — WeChat 4.x stores everything in SQLCipher 4 encrypted SQLite databases under
xwechat_files/<wxid>/db_storage/(contact.db,message_*.db,media_0.db,sns.db, …). Each DB has its own 32-byte key living in the Weixin.exe process memory (com.Tencent.WCDB.Config.Cipherconfig objects). The library locates them with a read-only memory scan, validates candidates with SQLCipher HMAC rules, decrypts pages to a temp dir and caches the result (first decrypt ~6s, then instant). WAL incremental merging with frame-salt filtering preventsdatabase disk image is malformedcorruption. - Sending — WeChat 4.x chat UI is self-drawn (no accessibility nodes), so sending uses a hybrid driver: hot-activate the Qt accessibility gate inside Weixin.dll (RVA scan, writes the screen-reader flag) to materialize the
mmui::*UIA tree — search box,chat_input_field, etc. Sending is UIA-first, coordinate + OCR fallback: auto-calibrating layout (~/.wechatauto/layout-<machine>.json), zoomed OCR (3x) with multi-round voting for rare Chinese characters, clipboard + Ctrl+V input to dodge IME interception. - Media — image
.datfiles are[6B sig][4B aes_size][4B xor_size] + AES-ECB + plaintext + xorchunks. The account-level AES key is transient (only resident in memory while viewing an image);MediaDownloaderscans for it, validates via JPEG/PNG magic, and persists it toimage_keys.jsonso later runs need no scanning (or passimage_key=explicitly). Voice is plain SILK read frommedia_0.db; files are read frommsg/file/with original names resolved frommessage_resource.db.
⚖️ vs wxauto
| wxauto | wechatauto-replica | |
|---|---|---|
| WeChat 4.x | ❌ UIA tree gone → broken | ✅ DB decryption + UIA hot-activation |
| Message reading | via UI tree | via local DB (full history, faster) |
| Sending | UIA clicks | UIA-first + OCR fallback |
| Media | limited | image AES decrypt, SILK voice, files |
| Moments | read | read (posting dropped: self-drawn UI) |
⚠️ Known Limitations
- WeChat must be logged in — DB keys live in process memory; cached after first extraction, re-extracted automatically after re-login.
- Image AES key is transient — only resident while viewing an image; persisted to
image_keys.jsononce found, or inject viaimage_key=. - Sending is a GUI operation — fails cleanly when the window is locked/unresponsive (operations return a clear failure).
- Videos are downloadable only when the mp4 already exists on disk (
msg/video/). - Group-chat image originals are stored locally only after being opened (viewed) in WeChat; until then only the thumbnail (
_t.dat) exists —download_imagefalls back to the thumbnail (marked_thumbin the filename). - Moments posting is dropped (4.x self-drawn UI, unreliable); reading/likes/comments are supported.
- Quote-message sending (BETA) goes through a coordinate + OCR +
SendInputpipeline that depends on WeChat 4.1.x self-drawn layout; positioning may drift with window size / DPI / chat content — test flow on a throwaway account only.
🗺️ Roadmap
- Calibrate and verify file/image/reply/@ sending on unlocked desktops
- Video message download (4.x storage location TBD)
- Performance: parallel export / first-scan, incremental memory-scan cache
📝 Changelog
v1.2.2.3 (2026-09-16)
- Fixed: constant ~50 MB/s disk read + write while the library runs. The decrypt-cache stamp compared mtimes with exact float equality while writing them with
%f(6 decimals) against Windows' 7-decimal mtimes — so every poll (~1s) looked like a changed database and re-decrypted everything (WAL merge + cache rewrite included). NowSTAMP_VERSION 3with%r(exact round-trip): one rebuild after upgrading, then stable. - Message reads now LIMIT inside each shard before merging (5.5× on a 48k-message group: 1.053s → 0.191s;
get_new_messages≈6×). Huge chats no longer materialize every shard's rows in Python. Public APIs (get_messages,get_new_messages,get_message_row(..., local_type=),get_message_rows_for_media) keep identical signatures and results (verified across 6 chats × 71 cases). - Fixed “cannot get keys” under UTF-8 mode: four
tasklistcalls decoded GBK output with the default codec; underpython -X utf8/PYTHONUTF8=1the decode failed, leftstdoutas None and raisedAttributeError, killing key extraction. All four now useencoding="gbk", errors="replace"with a None guard. WeChatUIA.is_running()is now multi-criterion: it used to be one probe wrapped inexcept → False, so any error silently became “WeChat is not running”. It now checks tasklist / main-window title / psutil, and only writes an explicit stderr note when every probe errors.- Real contact/group names removed from demos and docs (replaced with 「文件传输助手」; 「兔仔仔」/「送你挖银子」 kept as sample defaults).
v1.2.2.2 (2026-09-13)
- Key handling hardened: no more recurring failure after every WeChat update. Three layers:
- The cache can no longer be wiped:
_save_keys()never persists an empty result (atomic write +.bakkept). Previously a transient extraction failure (wrong account / permission) overwrote a good cache with an empty file, so every later start reported "0 keys" — that is exactly thekeys cached: 0seen in the field. - Durable key copy: a copy is kept at
%LOCALAPPDATA%\wechatauto_keys\<account>.json(override the directory with theWECHATAUTO_KEYS_DIRenv var, e.g. your project workspace), surviving TEMP cleanup and WeChat updates. On startup the caches are merged from several locations (durable copy → work cache →.bak→ other accounts' caches) and every entry is verified against page-1 HMAC, keeping only working keys. - Account selection is now decided by key verification, not by "most recently modified .db" (a WeChat update rewrites every .db, shifting mtimes and picking the wrong account → 0 keys). One memory scan now collects candidate key material and scores every account directory by page-1 HMAC, switching to the one that unlocks (log:
已按密钥校验选定账号目录: …).
- The cache can no longer be wiped:
- cfg master-key warning: on WeChat 4.1.13+ the cfg path returns an untrustworthy master key (demoted to a fallback since v1.1.9); it now logs an explicit warning when it cannot reproduce any database key instead of silently succeeding.
- Better diagnostics (
diagnose_keys): now prints the WeChat client FileVersion, per-account "cache / derived" availability and a master-key consistency check (which tells you which account the keys belong to); the_openerror text now lists the three classic causes (32-bit Python / permission mismatch / wrong account among several) plus theaccount=hint.
v1.2.2.1 (2026-09-12)
- Compatibility with the new WeChat UI (verified on 4.1.13.65): the new build changed
AutomationIdfrom short names into dotted paths (oldsession_list/chat_input_field→ newMainView.main_tabbar,MainView….main_window_sub_splitter_view…), which broke exact-equality matching. AutomationIds are now matched as exact / dotted-segment / suffix (_aid_hit()), so both the old short names and the new paths resolve. - Relaxed window-title matching: the new main window title is
Weixin, and becomes微信(3)when there are unread counts;_title_is_main()now matches by containment and still rejects unrelated titles such asWeChat. - Anchor candidate lists + structural fallbacks: the main window / login window / search box now match against candidate tuples (single-value constants kept for backward compatibility); the search box, chat input and search-result list each gained a structural fallback (an EditControl whose Name contains 搜索, an EditControl inside the chat area, attribute-based search from the root), so a renamed class or AID in a future build no longer breaks the whole path.
- New layout self-check
WeChatUIA.describe_layout(): one call returns the main class name, window title, layout kind (merged/legacy/chat) and the resolution result of every anchor (main_window, search_box, session_list, chat_input, main_tabbar, sns_list). Run it first when a new WeChat build changes the UI. - Note: the Moments anchors were already dual-layout (standalone
mmui::SNSWindow/ mergedmmui::SNSContentView); 4.1.13.65 keeps those class names, so no change was needed there.
v1.2.2 (2026-09-12)
- Fix cross-shard message reads (missing messages / voice): a conversation's
Msg_<md5>table actually spans severalmessage_*.dbshards, butget_messagesonly hit the first one — e.g. a chat with 8,904 real messages (24 voice notes) reported just 1. New_find_msg_tables()/_msg_conns()/_shard_rows()merge reads across all shards and sort bysort_seq;get_messages,get_new_messagesand_find_media_rowsnow use the merged view.get_message_rowgained alocal_typefilter (alocal_idis not unique across shards) and newget_message_rows_for_media()returns every shard row; media downloaders pass their type code so the right shard row is selected. - Reliable listener delivery (behavior change): the watermark now advances only after callbacks succeed (a new
_inflightboundary prevents re-dispatching unconfirmed messages), callbacks are retried (max_retries, default 3) before being logged as dropped, and the watermark is persisted tolistener_watermark.json. Messages that arrive while your process is down are delivered on the next start instead of being skipped. Passwatermark_file=""to disable persistence. - Text restore no longer requires CJK: pure English / digits / URLs / emoji container-format messages are decoded instead of degrading to
[文本]. Chat.GetNewMessage()no longer drops backlog: batches are pulled until caught up (>200 messages) and the watermark only moves to the last message actually returned, instead of jumping to the newest DB position.- UIA materialization self-heal (no child controls after a WeChat restart/upgrade): after a WeChat restart or upgrade the Qt accessibility gate byte resets to 0 and the
mmui::tree degrades to an empty Qt shell (Qt51514QWindowIcon+ 2 nodes). The driver now hot-writes the gate, verifies thatmmui::controls actually materialized, and retries other candidate RVAs on failure (the RVA that worked is cached per DLL identity)._get_uia()self-heals on a 30s throttle — no more "one failed wake and OCR forever", and no manualrefresh=True. Fallback table gained4.1.13.65 → 0x0AE2B0C8. - Moments scroll-positioning fixes: bounded reversals (at most one per run, then downward-only) and stall detection (the top-cell fingerprint now includes geometry — merged-layout ListItems can share the same Name, which previously looked like a stall and aborted mid-scroll); skip "scroll to top" when the DB ruler says the target is below; the stop criterion is now "the next moment's UIA control appeared"; direction/distance fixes (clipped pixels → wheel notches) plus a bottom margin so the "…" button is reachable.
- Message type table: 4.x composite
local_typeis decomposed by its low 32 bits; added50 音视频通话(VoIP bubble),11000 动画表情,8594229559345 红包(the library previously mislabeled it as an appmsg/file card via the low-byte mapping); empty bodies (stickers) now show[动画表情]instead of a blank line;demo_group_messagesdecodes zstd for every type and prints one-line summaries. demo_listen.py --allnow auto-discovers new sessions (previously limited to the 30 most recent at startup).- New anti-recall listener
RecallGuard(BETA): afterwatch(listener)every new message is mirrored into a local sqlite DB and attachments (image/voice/video/file) are backed up tomedia/; on arevokemsgit prints[撤回] <revoker> → <original text>and records it inrecall_events. Not fully field-tested — shipped as BETA. - New
MomentObserver(BETA): observe-and-freeze snapshots of Moments cache keys viasnapshot()/diff()(cache keys have no derivable mapping to feed md5 and the cache is evictable, so observing is the only way to keep them). Not fully field-tested — shipped as BETA.
v1.2.1 (2026-09-06)
- New "quote & send" message feature (BETA):
WeChatGUI.quote_msg(text, who, target_text=None, verify=False)right-clicks the target message → picks「引用」from the popup menu → types the content → sends; omittingtarget_textquotes the most recent message.quick_quote()is a one-liner entry point, demo scriptwechatauto/demo_quote.py.- BETA disclaimer: the feature uses a coordinate + OCR +
SendInputpipeline that depends on WeChat 4.1.x self-drawn layout; positioning may drift with window size / DPI / chat content. The right-click usesSendInputinjection (the render window ignoresmouse_eventright-clicks), and the cursor is first moved withSetCursorPosbefore injecting the click to avoid "moves but doesn't click / clicks but doesn't move" drift.
- BETA disclaimer: the feature uses a coordinate + OCR +
- Removed the
desktop_available()white-pixel screen check: control targeting is fully UIA-based now, so the full-window screenshot white-ratio sampling was dropped — it could falsely report "window not visible" while WeChat was fine.ensure_visible()now treats a live window handle as visible and keeps its "minimize blockers + bring-to-front" actions.
v1.2.0.3 (2026-08-31)
- Fix WAL-merged database cache corruption causing infinite loop:
_check_mergedpreviously usedSELECT count(*) FROM sqlite_masterwhich only checks the schema tree — corrupted data pages still passed validation, causing the cache stamp to mark the bad cache as "up-to-date" and every subsequent poll to reuse it, throwingdatabase disk image is malformedon a dead loop. Now usesPRAGMA quick_checkfor full database validation (data + index pages). New_invalidate_cache()clears all decrypted.db/.stampfiles. New_run_msg_query()unified entry point auto-retries once onmalformed(clear cache → rebuild → retry)._msg_connnow closes shard connections immediately to avoid Windows file-lock issues during cache cleanup.
v1.2.0 (2026-08-30)
Note: this release merges all changes made after 1.1.10.2 that were not yet published (1.1.10.3 → 1.1.10.7).
- Smart Moments positioning + auto like:
Moment.find_moment(publisher, keyword, ...)uses a hybrid of the DB route (computing the target offset) + UIA route (scrolling by offset) — it derives how many feeds the target is from the current view using the localsns.dbruler, then scrolls adaptively in the correct direction to land on the moment by author/keyword, eliminating blind downward scrolling and false "not found" results. - "…" overlay recognition:
Moment._locate_more_click/_find_more_buttonlocate the "…" button (bottom-right of a feed) via template matching (light/dark templates shipped inassets/) and click it; if not found it keeps nudging the scroll and retrying to pop up the like/comment overlay. - One-shot Like:
Moment.LikeMoment(publisher, keyword, ...)does "locate → tap "…" → like in the overlay"; the "赞/Comment" buttons in the overlay are found by a global deep traversal from the UIA root (matching by name) and clicked at their center. - Moments like/comment via UIA controls:
WeChatnow exposes aMomentproperty andSwitchToMoments()that hot-activate themmuiUIA tree and click the 朋友圈 nav button.Moment.Like(item, cancel=False)andMoment.Comment(item, content, reply_to=None)operate on UIA feed items — likes/comments are server-side actions, so they need the UI (the DB route stays read-only).WeChat.MomentisNonewhen the UIA tree is unavailable. Demowechatauto/demo_moments_interact.py. - Moments media download: new
MomentDB.download_media(media, save_dir, kind)copies a single picture/video from the local cache first (byte-for-byte, offline) and falls back to the CDN url;MomentDB.download_moment_media(feed, save_dir, ...)fetches all pictures/videos of one feed into a folder.find_local_media(md5, kind, size)locates the cache file by md5 and, for videos, bytotalSizeacross the wholeSns/Videotree (the video cache name is a content-hash unrelated to the feed md5, so size matching recovers real MP4s).parse_feednow distinguishes pictures vs videos viavideomd5/videoDuration/typeand records each media'ssize. Demowechatauto/demo_moments_download.py. - Moments read API (DB route):
MomentDB.get_moments()now supportssince/until(Unix-seconds time filter) andkeyword(text filter), pluslimit=0to return every row. New incremental-sync helperslatest_tid()/get_moments_since()make it easy to poll for new moments. New interaction notifierget_interactions()/interactions_unread_count()read the "likes/comments on my moments" table (SnsMessage_tmp3). Newcomment_tree()/comment_reply_to()organize a feed's comments into reply chains (built fromcomment_id/ref_comment_id). - Add group name ↔ ID lookup:
get_groups()now returns each group's realname(fromcontacttable, falling back to its wxid). Newgroup_name_to_id(name)(exact match first, then substring/fuzzy) andgroup_id_to_name(chatroom_wxid)let you resolve a group's wxid from its display name and vice versa — handy for combining withget_group_members()andat_member(). - Add group member enumeration & change watch (read-only, no UI): New
WeChatDB.get_groups()/get_group_members(chatroom_wxid)readchat_room+chatroom_member+contactfromcontact.dbto return each group's members (username / nick_name / remark / is_owner). NewGroupMemberWatcher(viaget_group_member_watcher) snapshots membership andpoll()diffs against the baseline to reportjoined/leftmembers, enabling polling-based membership-change monitoring. Useful together with the existing UI-automationat_member(). - New runnable demos
wechatauto/demo_moment_find.py,demo_moment_more.py,demo_moment_like.py; new depspyautogui,opencv-python.
v1.1.10.2 (2026-08-30)
- Fix long text still showing
[文本]on fresh installs: add requiredzstandarddependency: WeChat 4.x stores long-textmessage_contentas a zstd-compressed frame, decoded in_friendly_contentviaimport zstandard. That import silently failed whenzstandardwas absent (it was not inpyproject.tomlrequired deps), so long text degraded to the[文本]placeholder while listening worked normally.zstandardis now a required dependency;_friendly_contentalso gained lazy dual-package import (zstandard/zstd) via new_get_zstd_module()/_zstd_decompress()helpers.
v1.1.10.1 (2026-08-29)
- Fix
AttributeError: 'sqlite3.Row' object has no attribute 'get'in message reading:_msg_row_to_dictcalled.get("compress_content")on asqlite3.Row, which only supports[]access. Messages whose content stays a placeholder (e.g. emoji/special types) hit this branch and crashed the real-timeListenerpolling loop. Now uses[]access with a fallback, fixingget_messages/get_new_messages/get_message_row.
v1.1.9 (2026-08-27)
- Fix key extraction for WeChat 4.1.13+: Prioritized
Config.Ciphermemory scan overextract_master_key_from_cfgfor key extraction. The cfg-based extraction returns incorrect master keys on WeChat 4.1.13.12, while the Config.Cipher scan (which reads rawenc_keyvalues from XOR-decoded blobs) works correctly. This fixes the "0/24 keys verified" issue reported on newer WeChat versions.
v1.1.8 (2026-08-25)
- Fix missing
_derive_xor_keymethod in MediaDownloader: v1.1.7 release accidentally omitted the_derive_xor_key()method while code paths (_decrypt_v2,detect_image_key) still referenced it, causingAttributeErrorwhen decrypting images. Restored the method for XOR key derivation from thumbnail_t.dat/_h.datfiles. - Fix group-chat
sender_id→sender_usernameresolution:Listenercallbacks now receivesender_username(wxid format) in the message dict, resolved frommessage_resource.SenderName2Idmapping. Previously,sender_idwas a numeric ID that could not be used directly withsearch_contact(). - Thanks uiharukazari0105 for reporting the missing
_derive_xor_keyissue in v1.1.7.
v1.1.6.1 (2026-08-20)
- PyPI description fix: v1.1.6 was uploaded without the synced
README_pypi.md(description still showed 1.1.5.1); this patch restores the full v1.1.6 changelog and bumps the version marker.
v1.1.6 (2026-08-20)
- Auto-diagnosis on missing key:
数据库无可用密钥now runs a built-in check before raising — Python bitness (32-bit can't read 64-bit Weixin memory), per-PIDOpenProcess/ReadProcessMemorypermission, and multi-account mismatch (allwxid_*dirs vs. picked account, suggestingWeChatDB(account=...)). No need to rundiagnose_keysfirst. - New diagnostic tool:
wechatauto/diagnose_keys.py(python -m wechatauto.diagnose_keys, WeChat logged in) dumps lib version, Python bitness, Weixin PIDs with per-process read-permission checks, all accounts vs. picked account, cached keys, fresh in-memory extraction, and key verification — paste the output when reporting key-extraction failures. - Skip
migrate\unspportmsg.db: WeChat's reserved "unsupported message" DB has no in-memory key and is never queried; it was forcing a full process-memory scan on every init.
v1.1.5.1 (2026-08-18) — beta
- Fix real-time listening:
WeChatDB.get_new_messages()referenced an undefinedfound(NameError swallowed byListener._poll_once), so no message callbacks ever fired — including first messages from contacts you had never chatted with. - Dynamic message shards:
_message_dbs()now re-scans the disk so shards WeChat creates at runtime (e.g.message_5.db) are picked up and their keys extracted automatically.
v1.1.5 (2026-08-18)
- Version cleanup: normalized the patch version (1.1.4.2 → 1.1.5) after the
media_*.dbvoice fix.
v1.1.4.2 (2026-08-18)
- PyPI description cleanup: removed the demo default-group changelog line from the PyPI description.
v1.1.4.1 (2026-08-18)
- PyPI readme bilingual: merged the Chinese (
README.zh-CN.md) and English (README.md) into one PyPI description so the Chinese version is visible on the package page.
v1.1.4 (2026-08-18)
- Voice download across all media databases:
download_voice()now searches everymedia_*.db(not justmedia_0.db) — WeChat shards voice data across multiple media DBs; previously voices stored inmedia_1.dbetc. could not be found (thanks uiharukazari0105). demo_media.py --images N: download the latest N images of a chat directly from the DB (by local_type), bypassing the total-message--limit— no more "only a few images listed" when a group has thousands of messages.WeChatDB._find_media_rows(user, types): new helper returning all media local_ids of a chat for a set of local_types (batch download).- Group-chat image thumbnail fallback: original images in group chats are only downloaded after being opened in WeChat;
download_imagenow falls back to the thumbnail (_t.dat) when the original is missing, saving it with a_thumbsuffix.
v1.1.3 (2026-08-17)
v1.1.2 (2026-08-16)
- UIA driver thread-safety:
WeChatUIAnow initializes COM on the current thread (CoInitializeEx, idempotent) — fixes crashes when instantiated from background threads / host apps (e.g. WeChatBot) with "CoInitialize not called / cannot load UIAutomationCore.dll" errors. - Main-window filtering: only windows whose process loaded
Weixin.dllare considered — auxiliary processes without the DLL (whose hot-activation always fails) no longer produce noise warnings. - Forward-voice fix:
Chat.ForwardVoiceMessageusesselfwhen no target is given (the previous_cur()could resolve the wrong chat). - Re-entrant UI lock:
LockManageris now re-entrant per thread —@uilockfunctions calling each other (e.g.ForwardVoiceMessage→VoiceMessage.forward_to) no longer deadlock.
v1.1.1 (2026-08-16)
- Recall last message (
Chat.RecallLastMessage/uia_driver.recall_last_message): right-click the latest own message → UIA-first menu-item click (mmui::XMenuViewfound inside the main-window subtree), OCR fallback; fails cleanly when the 2-minute recall window has passed (menu only shows "Delete"). - UIA robustness: menu-item lookup scoped to the main-window subtree (avoids the Windows UIA root-traversal hang), removed the fragile
WindowControl(ClassName=...)fallback. - Media fix: video id bytes→str decoding in
MediaDownloader. demo_media.py --photosdefault 3 → 10.
v1.1.0 (2026-08-15)
- Image AES key auto-capture (
media.py): the V2 image key is only resident in memory while viewing an image (~5 min)._scan_aes_key()gained amonitormode — polls continuously and persists the key toimage_keys.jsononce found; users just open one image to finish setup. - Fixed the process-ordering scan bug (removed the memory-usage sort that pushed the main process last).
- Forward voice messages: SILK extraction from
media_0.db+ file-message send (demo_forward_voice.py). - New demos:
demo_group_messages.py(group + red-packet ZSTD parsing),demo_robust.py.
🤝 Acknowledgments
Thanks to vesio for sharing the WeChat 4.1.12 UIA control-tree approach and debugging ideas in issue #1 — it made the UIA hybrid driver (v1.0.8) possible.
Thanks to nanshanjack for finding the UI-lock re-entrancy problem (fixed in v1.1.2).
Thanks to maozhitao12450 for reporting the WXAM (wxgf) image download issue (fixed in v1.1.3).
Thanks to uiharukazari0105 for finding that voice data stored in media_1.db (and later) was never searched (fixed in v1.1.4).
📄 License & Disclaimer
Apache-2.0. This project is for personal learning and automation research only — please respect the WeChat software license agreement and applicable laws.
Contact: fanyuantaier@163.com
Release files for wechatauto-replica 1.2.2.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| wechatauto_replica-1.2.2.3.tar.gz | 363.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wechatauto_replica-1.2.2.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 676.5 kB
Release files / wechatauto_replica-1.2.2.3.tar.gz
| Download URL | wechatauto_replica-1.2.2.3.tar.gz |
|---|---|
| Size | 363.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b4597e4512d7c354a7f1d5ec39265413b69b9e9fbfb50f7ec4ade5a7fa7194d7
|
|
BLAKE2b-256 checksum How to use checksums |
0458f660569ed897c71654b1b17b16a9bcb223e7fc494d8bd86e318ded283576
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.9
|
Release files / wechatauto_replica-1.2.2.3-py3-none-any.whl
| Download URL | wechatauto_replica-1.2.2.3-py3-none-any.whl |
|---|---|
| Size | 313.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ac8402114735a2c313d98316444d7a23ff5fda9ff63e4a12297d2bf47b7b0147
|
|
BLAKE2b-256 checksum How to use checksums |
a40b1656996d4d47ce7ee53e35e32d950e553c5444788e68a3413f5891912580
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.9
|