# WeChat [简体中文](./README.zh_CN.md) OpenClaw's WeChat channel plugin, supporting login authorization via QR code scanning. ## Prerequisites [OpenClaw](https://docs.openclaw.ai/install) must be installed (the `openclaw` CLI needs to be available). ## Quick Install ```bash npx -y @tencent-weixin/openclaw-weixin-cli install ``` ## Manual Installation If the quick install doesn't work, follow these steps manually: ### 1. Install the plugin ```bash openclaw plugins install "@tencent-weixin/openclaw-weixin" ``` ### 2. Enable the plugin ```bash openclaw config set plugins.entries.openclaw-weixin.enabled true ``` ### 3. QR code login ```bash openclaw channels login --channel openclaw-weixin ``` A QR code will appear in the terminal. Scan it with your phone and confirm the authorization. Once confirmed, the login credentials will be saved locally automatically — no further action is needed. ### 4. Restart the gateway ```bash openclaw gateway restart ``` ## Adding More WeChat Accounts ```bash openclaw channels login --channel openclaw-weixin ``` Each QR code login creates a new account entry, supporting multiple WeChat accounts online simultaneously. ## Multi-Account Context Isolation By default, all channels share the same AI conversation context. To isolate conversation context for each WeChat account: ```bash openclaw config set agents.mode per-channel-per-peer ``` This gives each "WeChat account + message sender" combination its own independent AI memory, preventing context cross-talk between accounts. ## Backend API Protocol This plugin communicates with the backend gateway via HTTP JSON API. Developers integrating with their own backend need to implement the following interfaces. All endpoints use `POST` with JSON request and response bodies. Common request headers: | Header | Description | |--------|-------------| | `Content-Type` | `application/json` | | `AuthorizationType` | Fixed value `ilink_bot_token` | | `Authorization` | `Bearer ` (obtained after login) | | `X-WECHAT-UIN` | Base64-encoded random uint32 | ### Endpoint List | Endpoint | Path | Description | |----------|------|-------------| | getUpdates | `getupdates` | Long-poll for new messages | | sendMessage | `sendmessage` | Send a message (text/image/video/file) | | getUploadUrl | `getuploadurl` | Get CDN upload pre-signed URL | | getConfig | `getconfig` | Get account config (typing ticket, etc.) | | sendTyping | `sendtyping` | Send/cancel typing status indicator | ### getUpdates Long-polling endpoint. The server responds when new messages arrive or on timeout. **Request body:** ```json { "get_updates_buf": "" } ``` | Field | Type | Description | |-------|------|-------------| | `get_updates_buf` | `string` | Sync cursor from the previous response; empty string for the first request | **Response body:** ```json { "ret": 0, "msgs": [...], "get_updates_buf": "", "longpolling_timeout_ms": 35000 } ``` | Field | Type | Description | |-------|------|-------------| | `ret` | `number` | Return code, `0` = success | | `errcode` | `number?` | Error code (e.g., `-14` = session timeout) | | `errmsg` | `string?` | Error description | | `msgs` | `WeixinMessage[]` | Message list (structure below) | | `get_updates_buf` | `string` | New sync cursor to pass in the next request | | `longpolling_timeout_ms` | `number?` | Server-suggested long-poll timeout for the next request (ms) | ### sendMessage Send a message to a user. **Request body:** ```json { "msg": { "to_user_id": "", "context_token": "", "item_list": [ { "type": 1, "text_item": { "text": "Hello" } } ] } } ``` ### getUploadUrl Get CDN upload pre-signed parameters. Call this endpoint before uploading a file to obtain `upload_param` and `thumb_upload_param`. **Request body:** ```json { "filekey": "", "media_type": 1, "to_user_id": "", "rawsize": 12345, "rawfilemd5": "", "filesize": 12352, "thumb_rawsize": 1024, "thumb_rawfilemd5": "<thumbnail plaintext MD5>", "thumb_filesize": 1040 } ``` | Field | Type | Description | |-------|------|-------------| | `media_type` | `number` | `1` = IMAGE, `2` = VIDEO, `3` = FILE | | `rawsize` | `number` | Original file plaintext size | | `rawfilemd5` | `string` | Original file plaintext MD5 | | `filesize` | `number` | Ciphertext size after AES-128-ECB encryption | | `thumb_rawsize` | `number?` | Thumbnail plaintext size (required for IMAGE/VIDEO) | | `thumb_rawfilemd5` | `string?` | Thumbnail plaintext MD5 (required for IMAGE/VIDEO) | | `thumb_filesize` | `number?` | Thumbnail ciphertext size (required for IMAGE/VIDEO) | **Response body:** ```json { "upload_param": "<original image upload encrypted parameters>", "thumb_upload_param": "<thumbnail upload encrypted parameters>" } ``` ### getConfig Get account configuration, including the typing ticket. **Request body:** ```json { "ilink_user_id": "<user ID>", "context_token": "<optional, conversation context token>" } ``` **Response body:** ```json { "ret": 0, "typing_ticket": "<base64-encoded typing ticket>" } ``` ### sendTyping Send or cancel the typing status indicator. **Request body:** ```json { "ilink_user_id": "<user ID>", "typing_ticket": "<obtained from getConfig>", "status": 1 } ``` | Field | Type | Description | |-------|------|-------------| | `status` | `number` | `1` = typing, `2` = cancel typing | ### Message Structure #### WeixinMessage | Field | Type | Description | |-------|------|-------------| | `seq` | `number?` | Message sequence number | | `message_id` | `number?` | Unique message ID | | `from_user_id` | `string?` | Sender ID | | `to_user_id` | `string?` | Receiver ID | | `create_time_ms` | `number?` | Creation timestamp (ms) | | `session_id` | `string?` | Session ID | | `message_type` | `number?` | `1` = USER, `2` = BOT | | `message_state` | `number?` | `0` = NEW, `1` = GENERATING, `2` = FINISH | | `item_list` | `MessageItem[]?` | Message content list | | `context_token` | `string?` | Conversation context token, must be passed back when replying | #### MessageItem | Field | Type | Description | |-------|------|-------------| | `type` | `number` | `1` TEXT, `2` IMAGE, `3` VOICE, `4` FILE, `5` VIDEO | | `text_item` | `{ text: string }?` | Text content | | `image_item` | `ImageItem?` | Image (with CDN reference and AES key) | | `voice_item` | `VoiceItem?` | Voice (SILK encoded) | | `file_item` | `FileItem?` | File attachment | | `video_item` | `VideoItem?` | Video | | `ref_msg` | `RefMessage?` | Referenced message | #### CDN Media Reference (CDNMedia) All media types (image/voice/file/video) are transferred via CDN using AES-128-ECB encryption: | Field | Type | Description | |-------|------|-------------| | `encrypt_query_param` | `string?` | Encrypted parameters for CDN download/upload | | `aes_key` | `string?` | Base64-encoded AES-128 key | ### CDN Upload Flow 1. Calculate the file's plaintext size, MD5, and ciphertext size after AES-128-ECB encryption 2. If a thumbnail is needed (image/video), calculate the thumbnail's plaintext and ciphertext parameters as well 3. Call `getUploadUrl` to get `upload_param` (and `thumb_upload_param`) 4. Encrypt the file content with AES-128-ECB and PUT upload to the CDN URL 5. Encrypt and upload the thumbnail in the same way 6. Use the returned `encrypt_query_param` to construct a `CDNMedia` reference, include it in the `MessageItem`, and send > For complete type definitions, see [`src/api/types.ts`](src/api/types.ts). For API call implementations, see [`src/api/api.ts`](src/api/api.ts).