WeChat Assistant is a macOS Python automation project for controlling the already logged-in WeChat for Mac UI with pyautogui, screenshots, OCR, and local configuration files.
License: MIT
The project now includes a safe CLI foundation, a Tkinter GUI dashboard, local SQLite support, OCR/contact tooling, dry-run schedulers, local templates, audit logs, plugin manifest discovery, and macOS packaging support.
Implemented:
- macOS environment and permission checks
- YAML configuration loading with defaults and type validation
- terminal and file logging
- WeChat for Mac launch, activation, and shortcut-based contact search
- screenshot capture
- safe dry-run test message flow
- OCR contact candidate extraction into
data/contacts_cache.csv - local SQLite database owned by this project
- contact manager for reviewed/disabled local contacts
- birthday, festival, and custom reminder dry-run planning
- local message template rendering
- structured audit events for dry-run and blocked send decisions
- Tkinter dashboard with settings, contacts, tasks, and log viewer windows
- manifest-only local plugin skeleton
- macOS packaging script and packaging exclusion rules
- pytest coverage for safety, config, UI automation, OCR cleanup, database, GUI view models, schedulers, templates, plugins, and packaging helpers
- This project does not read WeChat databases.
- This project does not crack, bypass, or modify WeChat.
- This project does not collect account names, passwords, cookies, or tokens.
- It only controls the visible WeChat for Mac interface that the user has already logged into.
- Default target is only
文件传输助手. - Default mode is
dry_run: true, so no real message is sent. - Real sending is allowed only when both settings are true:
dry_run: falseallow_real_send: true
- Birthday and future batch features are dry-run first and must not be used for uncontrolled group sending.
- GUI views do not expose normal-contact real-send actions.
- Plugin manifests cannot enable direct sending or bypass safety gates.
cd ~/wechat-assistant
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtpython -m src.main check
python -m src.main screenshot
python -m src.main test-send
python -m src.main ocr
python -m src.main scan-contacts
python -m src.main birthday-check
python -m src.main guiTo register the daily birthday job with macOS launchd:
./scripts/install_birthday_launchagent.shThis installs a user LaunchAgent that calls scripts/birthday_cron.sh at 00:00
while you are logged in. To remove it:
./scripts/uninstall_birthday_launchagent.shRun tests:
pytestWeChat Assistant needs two macOS permissions to take screenshots and control the keyboard/mouse. Open System Settings → Privacy & Security and grant both to the app you run Python from.
Path: System Settings → Privacy & Security → Screen Recording
Add and enable the app you use to run Python:
| App | Common path |
|---|---|
| Terminal | built-in, listed automatically |
| iTerm2 | /Applications/iTerm.app |
| Visual Studio Code | /Applications/Visual Studio Code.app |
| Cursor | /Applications/Cursor.app |
Without Screen Recording permission, python -m src.main screenshot and
python -m src.main check will fail with a permission error (safe failure — the
project does not crash, it logs and exits).
Path: System Settings → Privacy & Security → Accessibility
Add and enable the same app (Terminal / iTerm2 / VS Code / Cursor).
Without Accessibility permission, pyautogui cannot move the mouse or type, so
test-send and manual-test will not be able to interact with WeChat windows.
Important: After enabling either permission, you must completely quit the terminal app (Cmd+Q, not just close the window) and reopen it. macOS does not apply the new permission to an already-running process. Skipping this step is the most common reason the permission appears granted but commands still fail.
Quick checklist:
- Open System Settings → Privacy & Security → Screen Recording, add your terminal app, toggle it on.
- Open System Settings → Privacy & Security → Accessibility, add your terminal app, toggle it on.
- Quit the terminal completely (Cmd+Q).
- Reopen the terminal and reactivate the virtual environment:
source .venv/bin/activate - Run
python -m src.main checkto verify.
python -m src.main check prints the Python version, verifies that the platform is
macOS, attempts a screenshot, and attempts a no-op mouse move. Permission failures are
logged to logs/app.log.
The default configuration is safe:
dry_run: true
allow_real_send: false
test_contact: "文件传输助手"python -m src.main test-send searches for 文件传输助手 and prepares the configured test message. In dry-run mode it logs what would happen and does not press Enter to send.
Real sending requires both:
dry_run: false
allow_real_send: trueThe sender refuses real sending unless the target is explicitly present in
allowed_real_contacts. 文件传输助手 is always included as the built-in safe
test target.
For a real-send test, use only:
test_contact: "文件传输助手"
dry_run: false
allow_real_send: true
require_known_screen_state_for_real_send: trueFor a real birthday send to a personal contact, keep dry_run: true in
config/settings.yaml and add the exact WeChat remark/display name to
allowed_real_contacts. The scheduled script uses --force-send to enable real
sending in memory for that run only, then the same whitelist and screen-state
checks still apply.
Then run:
python -m src.main test-sendThe command must print REAL SEND ENABLED, the target, and the message before pressing Enter. If the visible screen state cannot be confirmed, the send is blocked. Do not use this path for normal contacts.
Real sends now verify the active WeChat chat before pasting or pressing Enter. After contact search, the sender captures a fresh screenshot and OCR-checks both:
- the selected row in the left chat list
- the chat title above the message history
Both regions must match the target contact name or a known alias. This prevents the common failure mode where automation searches or activates the wrong chat after a restart, wake-up, or focus change.
To test the fix without sending a real message:
source .venv/bin/activate
python -m src.main check
python -m src.main manual-test --plan-only
python -m src.main test-send
pytestTo do a controlled real-send test, keep a clean commit with dry_run: true and
allow_real_send: false as the repository default. Only for the local run, edit
config/settings.yaml to set:
dry_run: false
allow_real_send: true
test_contact: "文件传输助手"
allowed_real_contacts:
- "File Transfer"
- "文件传输助手"
require_known_screen_state_for_real_send: trueThen run:
python -m src.main test-sendIf OCR cannot confirm both regions, the command blocks before paste/Enter and
writes the failure to logs/app.log.
Start the local dashboard:
python -m src.main guiThe dashboard shows dry-run status and opens safe local tools:
- environment check
- screenshot
- dry-run test send
- settings editor
- contacts reviewer
- birthday task manager
- read-only log viewer
The GUI calls existing services. It does not duplicate or weaken sending safety rules.
Project-owned local data lives under data/. The tracked CSV files in this
repository are sample placeholders for local development, not production
records. Before publishing your own fork, replace any personal names, remarks,
dates, or messages with neutral examples.
contacts_cache.csv: OCR candidate cachebirthday_tasks.csv: birthday dry-run task inputmessage_templates.csv: local message templatesfestival_tasks.csv: festival dry-run task inputreminders.csv: custom reminder dry-run inputwechat_assistant.sqlite3: optional project-owned runtime database, ignored by Git
These files are not WeChat internal data.
The optional SQLite database at data/wechat_assistant.sqlite3 is owned by WeChat Assistant. It stores only project data such as reviewed contacts, local tasks, message templates, and audit events.
It must never read, import, decrypt, mirror, or inspect WeChat internal databases.
The plugins/ directory supports local manifest-only plugin discovery. The current skeleton validates plugin.json files but does not execute plugin code. Plugins cannot bypass dry_run, cannot call direct send actions, and cannot read WeChat databases.
Local macOS packaging is documented in packaging/README.md. Packaging must exclude runtime logs, screenshots, local SQLite databases, caches, and virtual environments. The packaged app keeps dry-run defaults and still requires macOS Accessibility and Screen Recording permissions.
Packaging helper:
./scripts/build_macos_app.shThe script requires PyInstaller to be installed and runs pytest before building.
Keep this default in config/settings.yaml:
test_contact: "文件传输助手"
test_message: "WeChat Assistant test message"
dry_run: true
allow_real_send: falseThen run:
python -m src.main test-sendWith the default dry-run settings, the command prints and logs the planned action without touching the WeChat input box or sending a message.
- Screenshot fails: enable Screen Recording permission for your terminal, then restart the terminal app.
pyautoguihas no permission: enable Accessibility permission for your terminal, then restart the terminal app.- WeChat is not open: the project attempts
open -a WeChat; if that fails, open WeChat manually and make sure you are logged in. - Chinese input fails: messages are pasted with
pyperclipto avoid input-method issues. - OCR is inaccurate: OCR is best-effort and writes cleaned candidate results to
data/contacts_cache.csv; take a clearer screenshot and retry. easyocrdownloads models slowly: this is expected on first use.pytestis missing: activate the virtual environment and runpip install -r requirements.txt.- GUI does not open: verify Python was installed with Tkinter support.
- Real-send test is blocked: confirm both config flags are set, the target is in
allowed_real_contacts, and screen state can be recognized; blocked is the safe default. - Packaging fails: install PyInstaller in the active virtual environment and retry.
- Improve computer vision templates with sanitized assets
- Improve OCR accuracy with crop regions and manual review
- Expand GUI polish
- Add controlled packaging verification
- Keep normal-contact real sending disabled unless a future safety prompt explicitly authorizes it