Note
📢 维护状态 / Maintenance Notice 本人因今年升高一,开学后几乎没有时间继续更新本项目(如果有时间,争取周日更新)。遇到问题请自行在 Issues 区讨论,或询问 AI 协助解决。感谢支持!
I'm starting senior high school and will register tomorrow (Aug 23). After school starts I'll have almost no time to keep updating (Sundays if possible). Please discuss issues in the Issues section or ask an AI. Thanks for your support!
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.0.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.
| 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 |
| Download original image (not thumbnail) | ✅ verified | MediaDownloader.download_image_original(): UI click triggers download |
| 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 |
📖 Full usage guide: GUIDE.md (中英对照 / bilingual)
pip install -e .
# extra deps for the OCR sending path:
pip install winsdk pypinyinfrom 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"])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")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.
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 (UIA controls) — Moments 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 and clicks the
朋友圈 nav button, then likes/comments a feed via its UIA controls:
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 — can't like/comment")
wx.SwitchToMoments() # click 朋友圈 in the nav bar
items = moments.GetMoments() # list feed items as UIA controls
first = items[0]
moments.Like(first) # thumb up
moments.Like(first, cancel=True) # undo
moments.Comment(first, "Nice!") # comment
moments.Comment(first, "Thanks!", reply_to="张三") # reply to a commentRunnable demo: python -m wechatauto.demo_moments_interact [--like N | --unlike N | --comment N 文字]
(plain run lists the latest feeds without touching the UI).
⚠️ 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.
- 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.
| 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 + like/comment (UIA) | read + like/comment (UIA), full history via DB |
- 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 desktop is locked (
desktop_available()returns False). - 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). Usedownload_image_original()to trigger WeChat to fetch the original via a UI click on the image message. - Moments likes/comments go through the UI (server-side actions) and need the hot-activated
mmuiUIA tree plus an unlocked desktop; they fail cleanly when the tree is unavailable. Moments posting stays dropped (4.x self-drawn UI, unreliable).
- 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
- 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.
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.
- 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.
- 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 code path and crashed the real-timeListenerpolling loop. Now uses[]access with a fallback, fixingget_messages/get_new_messages/get_message_row.
- Add original image download via UI automation: New
MediaDownloader.download_image_original()method triggers WeChat to download original images by simulating UI clicks on image messages. This solves the limitation where group chat images only have thumbnails available. - Fix long text message content extraction: Added zstd decompression support,
compress_contentfallback, and fixed newline character handling.
- 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.
- 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_key issue in v1.1.7.
- 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.
- 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.
- 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.
- Version cleanup: normalized the patch version (1.1.4.2 → 1.1.5) after the
media_*.dbvoice fix.
- PyPI description cleanup: removed the demo default-group changelog line from the PyPI description.
- 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.
- 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.
- 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.
- 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.
- 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.
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).
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
