wxmon
by code2rich
README.md
# wxmon
> Monitor new **WeChat Official Account** articles on macOS and deliver them to your own tools — as a JSONL stream, signed webhooks, or an **MCP server** for AI assistants.
[](LICENSE)
[](https://www.python.org/)
[](#requirements)
[](https://github.com/code2rich/wxmon/actions/workflows/ci.yml)
English | [简体中文](README.zh-CN.md)
---
`wxmon` watches the local WeChat database on your Mac and surfaces articles from the
Official Accounts **your own WeChat follows**. Instead of scraping any remote endpoint,
it reads messages that WeChat has already synced to this machine — so there is no remote
polling and no risk of tripping account rate limits.
It is designed to feed downstream automation: pipe the JSONL stream into a script, POST
signed events to your backend, or attach the built-in MCP server to an AI assistant such
as Claude Code.
## Table of contents
- [Features](#features)
- [How it works](#how-it-works)
- [Requirements](#requirements)
- [Installation](#installation)
- [Obtaining the database key](#obtaining-the-database-key)
- [Usage](#usage)
- [Integrating with AI tools](#integrating-with-ai-tools)
- [Configuration](#configuration)
- [Project layout](#project-layout)
- [Development](#development)
- [Legal and ethical use](#legal-and-ethical-use)
- [License](#license)
## Features
- **Local-first.** Reads WeChat's on-disk database; no remote scraping, no rate-limit risk.
- **Incremental and deduplicated.** Tracks a per-database cursor and a stable `article_key`
(`__biz` + `mid` + `idx`) so each article is reported exactly once.
- **Schema-adaptive parser.** Discovers message tables and columns at runtime rather than
hard-coding a layout, making it resilient to WeChat version differences.
- **Optional full text.** Fetches the article page and converts the body to Markdown, using
a plain browser identity that is decoupled from your WeChat account.
- **Multiple outputs.** A JSONL event stream, HMAC-signed webhooks, and an MCP server — use
any combination.
- **AI-ready.** The MCP server exposes `list_recent`, `search_articles`, and `get_article`
so assistants can query collected articles directly.
## How it works
```
biz_message_*.db (SQLCipher-encrypted)
│ 1. Watch for changes (size/mtime, including the -wal file)
▼
Decrypt 2. Export a plaintext copy via the official sqlcipher CLI + your key
│ (the copy is temporary and deleted after each scan)
▼
Parse 3. Scan tables adaptively for appmsg / mmreader message payloads
│ and extract one record per article
▼
Dedupe 4. article_key = __biz_mid_idx; a cursor stores each DB's max timestamp
▼
Output JSONL append · webhook (HMAC-SHA256) · local store for MCP and `list`
```
> **wxmon does not implement any cryptography and does not extract keys.** Decryption is
> delegated to the standard `sqlcipher` command-line tool, and the key is supplied by you.
> In other words, wxmon only ever *opens your own database with a key you already hold.*
## Requirements
- macOS with the WeChat desktop client installed and signed in.
- Python 3.11 or newer.
- [`sqlcipher`](https://www.zetetic.net/sqlcipher/) available on `PATH`
(`brew install sqlcipher`).
- The SQLCipher key for your own WeChat database — see
[Obtaining the database key](#obtaining-the-database-key).
The monitored scope is limited to Official Accounts that **this WeChat account follows and
whose messages have reached this machine.**
## Installation
```bash
git clone https://github.com/code2rich/wxmon.git
cd wxmon
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
brew install sqlcipher # skip if already installed
```
## Obtaining the database key
`wxmon` requires the SQLCipher key for your own WeChat database (a 32-byte value, i.e. 64
hexadecimal characters).
**The extraction itself is out of scope for this project.** The key lives in the running
WeChat process's memory, and reading it requires a dedicated tool. Use a well-maintained,
community WeChat local-database key tool to retrieve it from your own account on your own
machine (e.g. `wechat-dump-rs`, `PyWxDump`, or a comparable actively-maintained project —
check that it supports your macOS and WeChat version, and follow its own documentation).
Extraction typically requires WeChat to be running and signed in, and may involve
temporarily adjusting system security settings such as SIP; restore them afterwards.
Once you have the hex string, `wxmon` manages it for you. The key is validated (format and
an actual decrypt check) and stored in the **macOS Keychain** — never in a plaintext config
file:
```bash
wxmon key set # paste the key interactively (hidden input), verify, store
wxmon key import -- <extractor-cmd> # run your extractor tool and capture its key automatically
wxmon key status # show whether a key is stored / present in the environment
wxmon key verify # confirm the current key can actually decrypt
wxmon key rm # remove the key from the Keychain
```
`wxmon key import` runs whatever extractor command you point it at, parses the 64-hex key
out of its output, verifies it, and stores it — for example:
```bash
wxmon key import -- wechat-dump-rs --key-only
```
Key resolution order at run time: explicit `--key` > the `WXMON_KEY` environment variable >
the Keychain. The environment variable remains available as a per-session override:
```bash
export WXMON_KEY=<64-hex-characters> # optional; not persisted
```
> `wxmon` never extracts keys or reads process memory itself — it only validates, stores,
> and uses a key you already hold, and (for `import`) shells out to the external extractor
> you choose.
## Usage
```bash
wxmon doctor # Environment check: WeChat dir, account, sqlcipher, key
wxmon probe # Detect the working cipher profile and print DB structure
wxmon scan # Scan once; print newly discovered articles as JSON events
wxmon scan --dry-run # Scan without writing JSONL or pushing webhooks
wxmon watch # Run continuously; stream new articles to stdout
wxmon list -n 20 # List recently collected articles
wxmon list -q keyword --json # Search collected articles
wxmon mcp # Run as an MCP stdio server for AI tools
wxmon config --set fetch_body=false webhook_url=https://... webhook_token=xxx
```
### First run
1. `wxmon doctor` — confirm the account and `sqlcipher` are detected and the key is set.
2. `wxmon probe` — decrypt with your key. On success, the working cipher profile is saved
to the config and the matched tables/columns plus parsed samples are printed. **If the
samples are empty or incomplete**, share that output so the parser in `wxmon/parse.py`
can be tuned to your WeChat version's real schema.
3. `wxmon watch` — start monitoring. On startup it only establishes a baseline (existing
articles are recorded but not pushed); only articles published afterwards are streamed.
## Integrating with AI tools
### A. MCP server (recommended for Claude Code / Codex)
Run `wxmon watch` in the background to collect articles, and register `wxmon mcp` as an MCP
server. Assistants can then call:
| Tool | Purpose |
| --- | --- |
| `list_recent(limit, since_hours, account)` | Most recently discovered articles |
| `search_articles(query, limit)` | Keyword search over title / account / digest |
| `get_article(article_key, with_body)` | A single article, including Markdown body |
The server supports two transports:
```bash
wxmon mcp # stdio (default; local process)
wxmon mcp --transport http --port 8765 # streamable-http at http://127.0.0.1:8765/mcp
wxmon mcp --transport http --host 0.0.0.0 --port 8765 --path /mcp # expose to the network
```
**stdio** — example Claude Code configuration (`~/.claude.json` or a project `.mcp.json`):
```json
{
"mcpServers": {
"wxmon": {
"command": "/absolute/path/to/wxmon/.venv/bin/wxmon",
"args": ["mcp"],
"env": { "WXMON_HOME": "/Users/you/.wxmon" }
}
}
}
```
**HTTP** — run `wxmon mcp --transport http` as a long-lived service, then point clients at
the URL:
```json
{
"mcpServers": {
"wxmon": { "type": "http", "url": "http://127.0.0.1:8765/mcp" }
}
}
```
> The HTTP transport binds to `127.0.0.1` by default. Only use `--host 0.0.0.0` on a
> trusted network; the server has no built-in authentication, so place it behind a reverse
> proxy or firewall if it must be reachable remotely.
> The MCP server only reads the already-collected local store and never triggers
> decryption, so it does not need the key. Decryption is handled by the background
> `wxmon watch` process, which holds `WXMON_KEY`.
### B. JSONL stream / webhook
```bash
wxmon watch > articles.jsonl # any agent or script can tail this file
```
Or configure a webhook. Each new article is POSTed with HMAC-SHA256-signed headers:
```bash
wxmon config --set webhook_url=https://your/api webhook_token=yourkey customer=your-id
wxmon watch
```
Request headers:
| Header | Value |
| --- | --- |
| `Content-Type` | `application/json; charset=utf-8` |
| `X-Api-Token` | the configured token |
| `X-Signature` | `hex(HMAC_SHA256(token, raw_body))`, lowercase |
| `X-Machine` | `hostname\|12-hex-MAC` |
| `X-Request-Id` | the event's `event_id` |
Verify the signature against the **raw** request body (do not re-serialize the JSON first):
```python
import hmac, hashlib
def verify(raw: bytes, token: str, sig: str) -> bool:
expect = hmac.new(token.encode(), raw, hashlib.sha256).hexdigest()
return hmac.compare_digest(expect, sig)
```
## Configuration
Configuration lives at `~/.wxmon/config.json` (override the base directory with
`WXMON_HOME`). Inspect or change it with `wxmon config [--set key=value ...]`.
| Key | Default | Description |
| --- | --- | --- |
| `wx_root` | WeChat container path | WeChat data root directory |
| `account` | `""` | Specific `wxid` directory; empty selects the most recently active |
| `cipher_profile` | `wechat4` | SQLCipher parameter set (set automatically by `probe`) |
| `key_source` | `env` | `env` / `config` / `arg` |
| `fetch_body` | `true` | Fetch article body and convert to Markdown |
| `webhook_url` | `""` | Destination for push events |
| `webhook_token` | `""` | HMAC key / `X-Api-Token` |
| `customer` | `""` | Customer identifier included in events |
| `poll_idle_seconds` | `60` | Poll interval when idle |
| `poll_active_seconds` | `5` | Poll interval right after a change |
| `accounts_filter` | `[]` | Keep only these account names; empty keeps all |
The key is read from `WXMON_KEY` by default and is not written to the config file.
State files:
- `~/.wxmon/state.db` — cursor, dedup set, collected articles
- `~/.wxmon/articles.jsonl` — event stream
## Project layout
```
wxmon/
├── article.py Article model and article_key derivation
├── cli.py Command-line entry point
├── config.py Paths, config file, cipher profiles
├── core.py Scan orchestration (decrypt → parse → dedupe → fetch → output)
├── decrypt.py sqlcipher CLI backend (plaintext export)
├── fetch.py Article body → Markdown
├── locate.py Locate WeChat accounts and message databases
├── mcp_server.py MCP stdio server
├── output.py JSONL writer and signed webhook push
├── parse.py Schema-adaptive article extraction
├── state.py SQLite state store
└── watch.py Monitoring loop
```
## Development
```bash
pip install -e ".[dev]"
pytest
```
The test suite validates the parser, deduplication, and output against a synthetic
plaintext database, so it runs without `sqlcipher` or a real WeChat key.
## Legal and ethical use
This project is intended solely for notifying and archiving content you are already
subscribed to, using your own WeChat account on your own machine. You are responsible for
complying with applicable laws and with WeChat's platform rules. Do not use it to access
data that is not yours.
## License
Released under the [MIT License](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessUnresponsive