Your personal WeChat account, exposed as a tiny event pipe. Scan QR. Listen forever. Ship payloads anywhere.
wechat-relay is a minimal CLI that turns a WeChat personal account into a programmable message bridge.
- QR login with local session persistence
- Long-poll listener with crash-safe queue replay
- Hook command execution for every inbound message
- Reply from the CLI with text, image, or audio
- NuGet tool, npm wrapper, and native AOT binaries
npm install -g @slaveoftime/wechat-relay
wechat-relayThe npm package ships a bundled native binary for:
linux-x64win32-x64darwin-arm64
dotnet tool install -g wechat-relay
wechat-relayGrab a release artifact and run it directly:
wechat-relay-{version}-linux-x64.tar.gzwechat-relay-{version}-win-x64.zipwechat-relay-{version}-osx-arm64.tar.gz
Native builds are self-contained. No SDK required.
# 1. Pair the account
wechat-relay login
# 2. Start the stream
wechat-relay listen
# 3. Reply with text
wechat-relay send --text "hello from the terminal"
# 4. Reply with media
wechat-relay send friend@im.wechat --image ./cat.jpg
wechat-relay send friend@im.wechat --audio ./reply.silk --audio-playtime-ms 4210The first time you reply to a user, WeChat expects a context_token from an inbound message.
Translation: run listen, let them message you once, then send works.
| Command | What it does |
|---|---|
login |
Starts QR login, or reuses the stored local session |
login --force |
Clears stored session state and pairs again |
listen |
Long-polls WeChat and fires your hook for each inbound message |
listen --hook "..." |
Overrides Hook:Command for the current run |
list-send-to |
Prints send targets from WeChat:UserId and WeChat:ToUsers |
history --tail 10 |
Shows the latest saved received/sent messages |
history --json |
Outputs saved history as a JSON array |
send [target] --text "..." |
Sends a text message |
send [target] --image ./file.jpg |
Uploads and sends an image |
send [target] --audio ./file.silk |
Uploads and sends audio or voice |
--verbose |
Enables debug logging on any command |
send accepts exactly one payload mode at a time: --text, --image, or --audio.
Audio flags currently supported:
wechat-relay send friend@im.wechat \
--audio ./reply.ogg \
--audio-format ogg \
--audio-sample-rate 16000 \
--audio-bits-per-sample 16 \
--audio-playtime-ms 4210Supported audio format hints: pcm, wav, adpcm, feature, speex, amr, silk, mp3, ogg.
Put appsettings.json in your per-user wechat-relay directory:
- Windows:
C:\Users\<you>\wechat-relay\appsettings.json - Linux/macOS:
~/wechat-relay/appsettings.json
{
"WeChat": {
"BaseUrl": "https://ilinkai.weixin.qq.com/",
"BotType": "3",
"UserId": "self@im.wechat",
"ToUsers": "friend1@im.wechat,friend2@im.wechat"
},
"Hook": {
"Command": "echo {payload}",
"WorkingDirectory": null
}
}Notes:
Hook:Commanddefaults toecholisten --hookoverridesHook:Commandwithout editing configUserIdis the defaultsendtargetToUsersadds extra IDs forlist-send-toHistory:DirectoryPathis optional; by default, received/sent history JSON files are saved directly in the same per-userwechat-relaydirectory. Relative custom paths are resolved under that directory.
Every inbound message is queued, persisted, and then passed to your hook command as JSON.
Minimal example:
{
"seq": 42,
"message_id": 7447467781622590088,
"from_user_id": "friend@im.wechat",
"to_user_id": "bot@im.bot",
"create_time_ms": 1775614686361,
"message_type": 1,
"text": "hello",
"summary": "text=\"hello\" [image] [audio 4210ms]",
"items": [
{
"item_type": 1,
"kind": "text",
"text": "hello"
},
{
"item_type": 2,
"kind": "image",
"local_path": "C:\\Users\\you\\wechat-relay\\inbound-media\\20260409\\msg-7447467781622590088-42\\00-image-image.jpg"
}
],
"context_token": "AARzJWAFAAABAAAA..."
}Storage lives under your per-user wechat-relay directory, for example C:\Users\<you>\wechat-relay on Windows or ~/wechat-relay on Linux/macOS:
appsettings.jsonstores CLI configurationsession-state.jsonstores login state and cached context tokens*.jsonand*.sent.jsonstore received and sent message history; unprocessed received history entries are also used for hook crash recoveryinbound-media/stores downloaded inbound images, audio, files, and video before the hook runs
If the process dies after receipt but before hook execution, the next listen run replays unprocessed received entries from history.
Typical listener output:
Listening
Bridge WeChat messages to any webhook
Press Ctrl+C to stop.
[14:23:01] 42 friend@im.wechat type=1 text="hello"
[14:23:15] 43 friend@im.wechat type=1 [image]
[14:23:22] 44 friend@im.wechat type=1 text="see attached" [audio 4210ms]
git clone https://github.com/slaveoftime/wechat-relay.git
cd wechat-relay/WeChatRelay
# local build
dotnet build
# package as a dotnet tool
dotnet pack -c Release
# native AOT
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishAot=true
dotnet publish -c Release -r linux-x64 --self-contained true -p:PublishAot=true
dotnet publish -c Release -r osx-arm64 --self-contained true -p:PublishAot=trueTarget framework: .NET 10.