
Wechat Channel
- 14 installs
- 82 repo stars
- Updated August 2, 2026
- aaaaqwq/agi-super-skills
wechat-channel is a Claude Code skill that builds a bidirectional WeChat message channel using Wechaty and the PadLocal protocol for private and group chats.
About
wechat-channel connects WeChat to an AI assistant as a bidirectional message channel using Wechaty plus the PadLocal protocol. It receives WeChat private and group messages, detects @-mentions, and sends text, image and file replies through an HTTP API. A developer uses it to trigger AI responses from WeChat or push notifications to WeChat contacts and groups.
- Bidirectional WeChat message channel via Wechaty and the PadLocal protocol
- Handles private and group chats, @-mention detection, and image/file transfer
- Includes allowlist, group-mention and pairing security policies
Wechat Channel by the numbers
- 14 all-time installs (skills.sh)
- Ranked #1,419 of 2,719 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
wechat-channel capabilities & compatibility
Requires a paid PadLocal Token, about ¥200/month, plus a running OpenClaw Gateway.
- Capabilities
- wechat integration · chatbot · message bridge · notifications
- Pricing
- Paid
What wechat-channel says it does
将微信接入 OpenClaw,实现双向消息通道。
PADLOCAL_TOKEN=YOUR_PADLOCAL_TOKEN
npx skills add https://github.com/aaaaqwq/agi-super-skills --skill wechat-channelAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 14 |
|---|---|
| repo stars | ★ 82 |
| Last updated | August 2, 2026 |
| Repository | aaaaqwq/agi-super-skills ↗ |
What it does
Wire WeChat private and group chats into an AI agent as a bidirectional message channel with mention detection.
Who is it for?
Building a two-way WeChat bridge so an agent can receive and send WeChat messages
Skip if: Voice-to-text, WeChat video-channel content, or red-packet/transfer features (explicitly unsupported)
When should I use this skill?
When you need WeChat messages to trigger AI responses or send messages from the agent to WeChat
By the numbers
- 3 message types: text, image, file
- 3 dmPolicy options: open, allowlist, pairing
- file size limit ~100MB
Files
微信 Channel 集成
- Author: Daniel Li
- Copyright © Daniel Li. All rights reserved.
将微信接入 OpenClaw,实现双向消息通道。
架构概述
┌─────────────┐ ┌──────────────────┐ ┌─────────────┐
│ 微信用户 │ ←→ │ Wechaty Bridge │ ←→ │ OpenClaw │
│ (私聊/群聊) │ │ (PadLocal协议) │ │ Gateway │
└─────────────┘ └──────────────────┘ └─────────────┘
↓
┌──────────────────┐
│ 消息格式转换 │
│ - 文本/图片/文件 │
│ - @提及检测 │
│ - 群聊/私聊路由 │
└──────────────────┘核心组件
1. Wechaty Bridge (消息桥接服务)
独立运行的 Node.js 服务,负责:
- 微信登录(扫码)
- 消息收发
- 联系人/群组管理
- 与 OpenClaw Gateway 通信
2. OpenClaw Webhook 接收器
接收来自 Wechaty Bridge 的消息,转发给 AI Agent。
3. 消息发送 API
OpenClaw Agent 通过 HTTP API 发送消息到微信。
快速开始
前置条件
- Node.js >= 18
- PadLocal Token(付费服务,约 ¥200/月)
- OpenClaw Gateway 运行中
1. 安装依赖
cd /home/aa/clawd/skills/wechat-channel
npm init -y
npm install wechaty wechaty-puppet-padlocal axios dotenv2. 配置环境变量
cp .env.example .env
# 编辑 .env 填入配置3. 启动服务
node scripts/wechat-bridge.js
# 扫描终端显示的二维码登录配置说明
环境变量 (.env)
# PadLocal Token (必需)
# 获取方式: https://pad-local.com
PADLOCAL_TOKEN=YOUR_PADLOCAL_TOKEN
# OpenClaw Gateway 配置
OPENCLAW_GATEWAY_URL=http://127.0.0.1:18789
OPENCLAW_WEBHOOK_SECRET=your_webhook_secret
# 微信 Bot 配置
WECHAT_BOT_NAME=OpenClaw助手
# 安全配置
# 允许的用户微信ID (逗号分隔,留空允许所有)
ALLOWED_USERS=wxid_xxx,wxid_yyy
# 允许的群聊ID (逗号分隔,留空允许所有)
ALLOWED_GROUPS=xxx@chatroom,yyy@chatroom
# 群聊行为
# 是否需要@才响应群消息
REQUIRE_MENTION_IN_GROUP=true
# 日志级别
LOG_LEVEL=infoOpenClaw 配置 (openclaw.json)
{
"channels": {
"wechat": {
"enabled": true,
"webhookUrl": "http://localhost:3001/webhook",
"webhookSecret": "your_webhook_secret",
"dmPolicy": "allowlist",
"allowFrom": ["wxid_xxx", "wxid_yyy"],
"groups": {
"xxx@chatroom": {
"name": "工作群",
"requireMention": true
}
}
}
}
}消息格式
接收消息 (Webhook Payload)
{
"type": "message",
"channel": "wechat",
"messageId": "msg_123456",
"from": {
"id": "wxid_sender",
"name": "张三",
"alias": "zhangsan"
},
"chat": {
"id": "wxid_sender",
"type": "private"
},
"text": "你好,帮我查一下天气",
"timestamp": 1706745600000,
"mentions": [],
"replyTo": null
}群聊消息
{
"type": "message",
"channel": "wechat",
"messageId": "msg_789012",
"from": {
"id": "wxid_sender",
"name": "张三"
},
"chat": {
"id": "xxx@chatroom",
"type": "group",
"name": "工作群"
},
"text": "@OpenClaw助手 帮我总结一下今天的会议",
"mentions": ["bot_wxid"],
"isMentioned": true
}发送消息 (API)
# 发送文本
curl -X POST http://localhost:3001/api/send \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_SECRET" \
-d '{
"to": "wxid_receiver",
"type": "text",
"content": "收到,正在处理..."
}'
# 发送图片
curl -X POST http://localhost:3001/api/send \
-H "Content-Type: application/json" \
-d '{
"to": "wxid_receiver",
"type": "image",
"url": "https://example.com/image.png"
}'
# 发送文件
curl -X POST http://localhost:3001/api/send \
-d '{
"to": "wxid_receiver",
"type": "file",
"path": "/path/to/file.pdf",
"filename": "report.pdf"
}'安全策略
私聊策略 (dmPolicy)
| 策略 | 说明 |
|---|---|
open | 允许所有人私聊(危险) |
allowlist | 仅允许 allowFrom 列表中的用户 |
pairing | 需要配对审批 |
群聊策略
| 配置 | 说明 |
|---|---|
requireMention: true | 必须@机器人才响应 |
allowFrom | 群内允许触发的用户列表 |
使用场景
1. 个人助手
用户: 帮我查一下明天北京的天气
Bot: 明天北京天气:晴,温度 -5°C ~ 5°C,建议穿羽绒服。2. 群聊助手
用户: @OpenClaw助手 总结一下刚才的讨论
Bot: 刚才讨论的要点:
1. 项目进度需要加快
2. 下周三前完成设计稿
3. 周五进行代码评审3. 自动化通知
// 从 OpenClaw Agent 发送通知
await sendWechatMessage({
to: 'xxx@chatroom',
text: '⚠️ 服务器 CPU 使用率超过 90%,请检查!'
});故障排查
登录问题
问题: 扫码后无法登录 解决: 1. 检查 PadLocal Token 是否有效 2. 确认微信账号未被限制 3. 尝试重新获取 Token
消息收发问题
问题: 消息发送失败 解决: 1. 检查网络连接 2. 确认目标用户/群组 ID 正确 3. 查看日志中的错误信息
连接断开
问题: 服务运行一段时间后断开 解决: 1. 使用 PM2 管理进程,自动重启 2. 检查 PadLocal 服务状态 3. 实现心跳检测和重连机制
限制说明
PadLocal 限制
- 需要付费 Token(约 ¥200/月)
- 单 Token 只能登录一个微信号
- 可能受微信风控影响
微信平台限制
- 发送频率限制(建议间隔 1-2 秒)
- 群聊人数限制
- 文件大小限制(约 100MB)
- 不支持小程序消息
功能限制
- 不支持语音消息转文字(需额外集成)
- 不支持视频号内容
- 红包、转账等敏感功能不可用
相关文件
scripts/wechat-bridge.js- 主服务代码scripts/message-handler.js- 消息处理逻辑.env.example- 环境变量模板references/wechaty-api.md- Wechaty API 参考
TODO
- [ ] 获取 PadLocal Token
- [ ] 配置 OpenClaw Webhook 接收
- [ ] 测试私聊消息收发
- [ ] 测试群聊 @提及
- [ ] 配置安全策略
- [ ] 部署为系统服务
- [ ] 实现断线重连
- [ ] 添加消息队列(高并发场景)
{
"name": "openclaw-wechat-bridge",
"version": "1.0.0",
"description": "WeChat Bridge for OpenClaw - 基于 Wechaty + PadLocal",
"main": "scripts/wechat-bridge.js",
"scripts": {
"start": "node scripts/wechat-bridge.js",
"dev": "LOG_LEVEL=verbose node scripts/wechat-bridge.js"
},
"dependencies": {
"wechaty": "^1.20.2",
"wechaty-puppet-padlocal": "^1.17.0",
"axios": "^1.6.0",
"express": "^4.18.2",
"dotenv": "^16.3.1",
"qrcode-terminal": "^0.12.0",
"file-box": "^1.4.15"
},
"engines": {
"node": ">=18.0.0"
},
"keywords": [
"wechat",
"openclaw",
"wechaty",
"padlocal",
"chatbot"
],
"license": "MIT"
}
Wechaty API 参考
核心概念
Wechaty
Wechaty 是一个开源的对话式 AI SDK,支持多种 IM 平台。
Puppet
Puppet 是 Wechaty 的底层协议实现。PadLocal 是目前最稳定的微信协议实现。
常用 API
Bot 实例
const { WechatyBuilder } = require('wechaty');
const bot = WechatyBuilder.build({
name: 'my-bot',
puppet: new PuppetPadlocal({ token: 'xxx' }),
});
// 启动
await bot.start();
// 停止
await bot.stop();
// 登出
await bot.logout();事件
// 扫码
bot.on('scan', (qrcode, status) => {});
// 登录
bot.on('login', (user) => {});
// 登出
bot.on('logout', (user) => {});
// 消息
bot.on('message', (message) => {});
// 好友请求
bot.on('friendship', (friendship) => {});
// 群邀请
bot.on('room-invite', (roomInvitation) => {});
// 群成员变动
bot.on('room-join', (room, inviteeList, inviter) => {});
bot.on('room-leave', (room, leaverList, remover) => {});
bot.on('room-topic', (room, newTopic, oldTopic, changer) => {});
// 错误
bot.on('error', (error) => {});Message 消息
// 消息类型
message.type(); // Text, Image, Video, Audio, File, Emoticon, etc.
// 发送者
message.talker();
// 所在群聊 (私聊为 null)
message.room();
// 消息文本
message.text();
// 是否自己发的
message.self();
// 消息时间
message.date();
// @提及列表
await message.mentionList();
// 是否@了自己
await message.mentionSelf();
// 转发消息
await message.forward(contact);
// 回复消息
await message.say('回复内容');Contact 联系人
// 查找联系人
const contact = await bot.Contact.find({ id: 'wxid_xxx' });
const contact = await bot.Contact.find({ name: '张三' });
// 所有联系人
const contacts = await bot.Contact.findAll();
// 联系人属性
contact.id;
contact.name();
contact.alias();
contact.type(); // Unknown, Individual, Official
contact.gender(); // Unknown, Male, Female
contact.province();
contact.city();
contact.avatar();
// 发送消息
await contact.say('Hello');
await contact.say(fileBox); // 发送文件/图片Room 群聊
// 查找群聊
const room = await bot.Room.find({ id: 'xxx@chatroom' });
const room = await bot.Room.find({ topic: '工作群' });
// 所有群聊
const rooms = await bot.Room.findAll();
// 群聊属性
room.id;
await room.topic(); // 群名
await room.owner(); // 群主
await room.memberAll(); // 所有成员
await room.member({ name: '张三' }); // 查找成员
// 发送消息
await room.say('Hello');
await room.say('Hello', contact); // @某人
// 群管理
await room.add(contact); // 添加成员
await room.del(contact); // 移除成员
await room.topic('新群名'); // 修改群名
await room.quit(); // 退出群聊FileBox 文件
const { FileBox } = require('file-box');
// 从 URL 创建
const fileBox = FileBox.fromUrl('https://example.com/image.png');
// 从本地文件创建
const fileBox = FileBox.fromFile('/path/to/file.pdf');
// 从 Base64 创建
const fileBox = FileBox.fromBase64(base64String, 'image.png');
// 从 Buffer 创建
const fileBox = FileBox.fromBuffer(buffer, 'file.pdf');
// 发送
await contact.say(fileBox);
await room.say(fileBox);Friendship 好友请求
bot.on('friendship', async (friendship) => {
const type = friendship.type();
if (type === bot.Friendship.Type.Receive) {
// 收到好友请求
await friendship.accept();
}
if (type === bot.Friendship.Type.Confirm) {
// 好友请求已确认
}
});
// 主动添加好友
await bot.Friendship.add(contact, 'Hello, I am OpenClaw');消息类型枚举
const MessageType = {
Unknown: 0,
Attachment: 1,
Audio: 2,
Contact: 3,
ChatHistory: 4,
Emoticon: 5,
Image: 6,
Text: 7,
Location: 8,
MiniProgram: 9,
GroupNote: 10,
Transfer: 11,
RedEnvelope: 12,
Recalled: 13,
Url: 14,
Video: 15,
};最佳实践
消息发送间隔
// 避免发送过快被限制
async function sendWithDelay(target, messages, delayMs = 1000) {
for (const msg of messages) {
await target.say(msg);
await new Promise(r => setTimeout(r, delayMs));
}
}错误重试
async function sendWithRetry(target, content, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
await target.say(content);
return true;
} catch (error) {
console.error(`发送失败 (${i + 1}/${maxRetries}):`, error.message);
await new Promise(r => setTimeout(r, 2000 * (i + 1)));
}
}
return false;
}断线重连
bot.on('error', async (error) => {
console.error('Bot error:', error);
// 等待后重启
await new Promise(r => setTimeout(r, 5000));
try {
await bot.stop();
await bot.start();
} catch (e) {
console.error('重启失败:', e);
}
});参考链接
- Wechaty 官网: https://wechaty.js.org/
- Wechaty GitHub: https://github.com/wechaty/wechaty
- PadLocal: https://pad-local.com/
- Wechaty Puppet 列表: https://wechaty.js.org/docs/puppet-providers/
#!/usr/bin/env node
/**
* WeChat Bridge for OpenClaw
*
* 基于 Wechaty + PadLocal 的微信消息桥接服务
* 将微信消息转发到 OpenClaw Gateway,并接收 OpenClaw 的回复发送到微信
*/
require('dotenv').config();
const { WechatyBuilder, ScanStatus, log } = require('wechaty');
const { PuppetPadlocal } = require('wechaty-puppet-padlocal');
const axios = require('axios');
const express = require('express');
const qrcode = require('qrcode-terminal');
// ============ 配置 ============
const config = {
// PadLocal Token
padlocalToken: process.env.PADLOCAL_TOKEN || 'YOUR_PADLOCAL_TOKEN',
// OpenClaw Gateway
openclawGatewayUrl: process.env.OPENCLAW_GATEWAY_URL || 'http://127.0.0.1:18789',
openclawWebhookSecret: process.env.OPENCLAW_WEBHOOK_SECRET || '',
// Bot 配置
botName: process.env.WECHAT_BOT_NAME || 'OpenClaw助手',
// 安全配置
allowedUsers: process.env.ALLOWED_USERS?.split(',').filter(Boolean) || [],
allowedGroups: process.env.ALLOWED_GROUPS?.split(',').filter(Boolean) || [],
requireMentionInGroup: process.env.REQUIRE_MENTION_IN_GROUP !== 'false',
// 服务端口
apiPort: parseInt(process.env.API_PORT || '3001'),
// 日志级别
logLevel: process.env.LOG_LEVEL || 'info',
};
// ============ 日志 ============
log.level(config.logLevel);
// ============ Wechaty 实例 ============
const puppet = new PuppetPadlocal({
token: config.padlocalToken,
});
const bot = WechatyBuilder.build({
name: 'openclaw-wechat-bridge',
puppet,
});
// Bot 自身信息
let botInfo = null;
// ============ 事件处理 ============
// 扫码登录
bot.on('scan', (qrcodeUrl, status) => {
if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) {
console.log('\n========================================');
console.log('请使用微信扫描下方二维码登录:');
console.log('========================================\n');
qrcode.generate(qrcodeUrl, { small: true });
console.log(`\n或访问: ${qrcodeUrl}\n`);
}
});
// 登录成功
bot.on('login', async (user) => {
botInfo = user;
console.log('\n========================================');
console.log(`✅ 登录成功: ${user.name()}`);
console.log(` 微信ID: ${user.id}`);
console.log('========================================\n');
});
// 登出
bot.on('logout', (user) => {
console.log(`\n❌ 已登出: ${user.name()}\n`);
botInfo = null;
});
// 收到消息
bot.on('message', async (message) => {
try {
await handleMessage(message);
} catch (error) {
console.error('处理消息失败:', error);
}
});
// 错误处理
bot.on('error', (error) => {
console.error('Bot 错误:', error);
});
// ============ 消息处理 ============
async function handleMessage(message) {
// 忽略自己发的消息
if (message.self()) return;
// 只处理文本消息(可扩展支持其他类型)
const msgType = message.type();
if (msgType !== bot.Message.Type.Text) {
log.info('忽略非文本消息:', msgType);
return;
}
const talker = message.talker();
const room = message.room();
const text = message.text();
// 安全检查
if (!await checkPermission(talker, room)) {
log.info('权限检查未通过,忽略消息');
return;
}
// 群聊 @提及检查
if (room && config.requireMentionInGroup) {
const mentionSelf = await message.mentionSelf();
if (!mentionSelf) {
log.verbose('群消息未@机器人,忽略');
return;
}
}
// 构建消息 payload
const payload = await buildMessagePayload(message, talker, room, text);
// 发送到 OpenClaw
await forwardToOpenClaw(payload);
}
async function checkPermission(talker, room) {
const talkerId = talker.id;
// 私聊权限检查
if (!room) {
if (config.allowedUsers.length === 0) return true;
return config.allowedUsers.includes(talkerId);
}
// 群聊权限检查
const roomId = room.id;
if (config.allowedGroups.length === 0) return true;
return config.allowedGroups.includes(roomId);
}
async function buildMessagePayload(message, talker, room, text) {
const mentions = await message.mentionList();
const mentionIds = mentions.map(m => m.id);
// 移除 @xxx 文本,获取纯净消息
let cleanText = text;
for (const mention of mentions) {
const mentionName = mention.name();
cleanText = cleanText.replace(new RegExp(`@${mentionName}\\s*`, 'g'), '');
}
cleanText = cleanText.trim();
const payload = {
type: 'message',
channel: 'wechat',
messageId: message.id,
timestamp: message.date().getTime(),
from: {
id: talker.id,
name: talker.name(),
alias: await talker.alias() || null,
},
text: cleanText,
rawText: text,
mentions: mentionIds,
isMentioned: botInfo ? mentionIds.includes(botInfo.id) : false,
};
if (room) {
payload.chat = {
id: room.id,
type: 'group',
name: await room.topic(),
};
} else {
payload.chat = {
id: talker.id,
type: 'private',
};
}
return payload;
}
async function forwardToOpenClaw(payload) {
try {
const headers = {
'Content-Type': 'application/json',
};
if (config.openclawWebhookSecret) {
headers['Authorization'] = `Bearer ${config.openclawWebhookSecret}`;
}
const response = await axios.post(
`${config.openclawGatewayUrl}/webhook/wechat`,
payload,
{ headers, timeout: 30000 }
);
log.info('消息已转发到 OpenClaw:', response.status);
} catch (error) {
console.error('转发消息到 OpenClaw 失败:', error.message);
}
}
// ============ API 服务 ============
const app = express();
app.use(express.json());
// 健康检查
app.get('/health', (req, res) => {
res.json({
status: 'ok',
loggedIn: !!botInfo,
botName: botInfo?.name() || null,
});
});
// 发送消息 API
app.post('/api/send', async (req, res) => {
try {
// 验证 secret
const authHeader = req.headers.authorization;
if (config.openclawWebhookSecret) {
if (authHeader !== `Bearer ${config.openclawWebhookSecret}`) {
return res.status(401).json({ error: 'Unauthorized' });
}
}
const { to, type, content, url, path, filename } = req.body;
if (!to) {
return res.status(400).json({ error: 'Missing "to" field' });
}
if (!botInfo) {
return res.status(503).json({ error: 'Bot not logged in' });
}
let target;
// 判断是群聊还是私聊
if (to.endsWith('@chatroom')) {
target = await bot.Room.find({ id: to });
} else {
target = await bot.Contact.find({ id: to });
}
if (!target) {
return res.status(404).json({ error: 'Target not found' });
}
// 根据类型发送消息
switch (type || 'text') {
case 'text':
await target.say(content);
break;
case 'image':
if (url) {
const { FileBox } = require('file-box');
const fileBox = FileBox.fromUrl(url);
await target.say(fileBox);
} else if (path) {
const { FileBox } = require('file-box');
const fileBox = FileBox.fromFile(path);
await target.say(fileBox);
}
break;
case 'file':
if (path) {
const { FileBox } = require('file-box');
const fileBox = FileBox.fromFile(path, filename);
await target.say(fileBox);
}
break;
default:
return res.status(400).json({ error: `Unknown message type: ${type}` });
}
res.json({ success: true, message: 'Message sent' });
} catch (error) {
console.error('发送消息失败:', error);
res.status(500).json({ error: error.message });
}
});
// 获取联系人列表
app.get('/api/contacts', async (req, res) => {
try {
if (!botInfo) {
return res.status(503).json({ error: 'Bot not logged in' });
}
const contacts = await bot.Contact.findAll();
const result = contacts.map(c => ({
id: c.id,
name: c.name(),
type: c.type(),
}));
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 获取群聊列表
app.get('/api/rooms', async (req, res) => {
try {
if (!botInfo) {
return res.status(503).json({ error: 'Bot not logged in' });
}
const rooms = await bot.Room.findAll();
const result = await Promise.all(rooms.map(async r => ({
id: r.id,
topic: await r.topic(),
})));
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// ============ 启动 ============
async function main() {
console.log('\n========================================');
console.log('🤖 WeChat Bridge for OpenClaw');
console.log('========================================\n');
// 检查配置
if (config.padlocalToken === 'YOUR_PADLOCAL_TOKEN') {
console.error('❌ 请配置 PADLOCAL_TOKEN 环境变量');
console.error(' 获取 Token: https://pad-local.com');
process.exit(1);
}
// 启动 API 服务
app.listen(config.apiPort, () => {
console.log(`📡 API 服务已启动: http://localhost:${config.apiPort}`);
console.log(` - 健康检查: GET /health`);
console.log(` - 发送消息: POST /api/send`);
console.log(` - 联系人列表: GET /api/contacts`);
console.log(` - 群聊列表: GET /api/rooms`);
});
// 启动 Wechaty
console.log('\n正在启动微信 Bot...\n');
await bot.start();
}
// 优雅退出
process.on('SIGINT', async () => {
console.log('\n正在关闭...');
await bot.stop();
process.exit(0);
});
process.on('SIGTERM', async () => {
await bot.stop();
process.exit(0);
});
main().catch(console.error);
WeChat Channel TODO
必需配置项
1. PadLocal Token
- 获取方式: https://pad-local.com
- 费用: 约 ¥200/月
- 说明: 单 Token 只能登录一个微信号
2. OpenClaw Webhook 配置
- 需要在 OpenClaw Gateway 中配置 Webhook 接收端点
- 当前 OpenClaw 可能不原生支持自定义 channel,需要:
- 方案 A: 开发 OpenClaw 插件 (参考 telegram/whatsapp 插件)
- 方案 B: 使用 Webhook 转发到现有 channel
待解决问题
架构问题
1. OpenClaw Channel 插件开发
- 当前 OpenClaw 的 channel 插件是 TypeScript 编写
- 需要研究如何创建自定义 channel 插件
- 或者使用 Webhook 方式桥接
2. 消息路由
- 如何将微信消息正确路由到 OpenClaw Agent
- 如何将 Agent 回复发送回微信
3. 会话管理
- 如何维护微信用户与 OpenClaw 会话的对应关系
- 多轮对话上下文保持
技术问题
1. 登录状态持久化
- PadLocal 登录状态如何持久化
- 服务重启后是否需要重新扫码
2. 消息可靠性
- 消息发送失败的重试机制
- 消息队列(高并发场景)
3. 媒体消息处理
- 图片/文件的上传下载
- 语音消息转文字(需要额外服务)
安全问题
1. 权限控制
- 私聊白名单
- 群聊白名单
- 群内用户白名单
2. 敏感信息
- Token 安全存储
- 日志脱敏
后续优化方向
功能增强
- [ ] 支持语音消息(转文字)
- [ ] 支持图片识别
- [ ] 支持文件处理
- [ ] 支持表情包
- [ ] 支持小程序卡片解析
稳定性
- [ ] 断线自动重连
- [ ] 心跳检测
- [ ] 消息队列
- [ ] 日志监控告警
部署
- [ ] Docker 容器化
- [ ] PM2 进程管理
- [ ] 系统服务配置
- [ ] 健康检查端点
替代方案
如果 PadLocal 不可用,可考虑:
1. itchat (Python)
- 免费,但不稳定
- 容易被封号
2. WxPusher
- 仅支持发送,不支持接收
- 适合单向通知场景
3. 企业微信
- 官方 API,稳定
- 需要企业认证
- 功能受限
4. 微信公众号
- 官方 API
- 需要认证
- 只能被动回复
参考资源
- Wechaty 文档: https://wechaty.js.org/docs/
- PadLocal 官网: https://pad-local.com/
- OpenClaw 插件开发: 参考
~/.npm-global/lib/node_modules/openclaw/extensions/
Related skills
FAQ
What does it require to run?
Node.js >= 18, a paid PadLocal Token (about ¥200/month), and a running OpenClaw Gateway.
What message types are supported?
Text, image and file messages in both private and group chats, with @-mention detection.