Clawbot 多端接入教程
本文介绍了Moltbot Gateway本地部署及多平台对接方案。主要内容包括: 部署Moltbot Gateway并开启OpenAI兼容接口/v1/chat/completions 提供三种对接方案: Telegram原生渠道(最稳定) 飞书长连接接入(无需公网回调) 企业微信两种方式:群机器人Webhook推送(推荐)和应用回调(聊天式) 详细说明了Node.js桥接项目的实现方法,包括核心代
目标
-
✅ Moltbot(原 Clawdbot)Gateway 本地部署
-
✅ Telegram:优先用 Moltbot 原生渠道(最稳、最省心)
-
✅ 飞书:长连接(不需要公网回调)→ Moltbot
/v1/chat/completions→ 回发 -
✅ 微信:两条路
-
企业微信群机器人 Webhook(推送型):最稳、最好用
-
企业微信应用回调(聊天式):给你一套 Express 模板(可跑,但需要你按你们企业微信的实际回调字段微调)
-
(1) 总体架构
[飞书用户] <-> [Feishu 长连接 Bridge(Node)] ---> [Moltbot Gateway /v1/chat/completions] ---> [Agent & Tools]
[企微群] <-> [WeCom Webhook Push(Node)] ---> (仅推送通知)
[企微应用] <-> [WeCom Callback Bridge(Node)] ---> (聊天式:验签/解密/回包)
[Telegram] <-> (Moltbot 原生 Telegram Channel)
(2) 第一步:把 Moltbot Gateway 跑起来(并开启 OpenAI 兼容接口)
2.1 安装 & 启动
npm i -g moltbot@latest moltbot gateway --port 18789 --verbose
2.2 开启 /v1/chat/completions(必须)
Moltbot 的 Gateway 支持 OpenAI 兼容的 POST /v1/chat/completions,默认关闭,要在配置里打开。
示例(概念配置,按你实际配置文件位置修改):
{
"gateway": {
"http": {
"endpoints": {
"chatCompletions": {
"enabled": true
}
}
},
"auth": {
"mode": "token",
"token": "CHANGE_ME_TO_A_LONG_RANDOM"
}
}
}
这条接口和 Gateway 同端口:
http://127.0.0.1:18789/v1/chat/completions
2.3 先用 curl 验证(确保桥接必通)
curl http://127.0.0.1:18789/v1/chat/completions \ -H "Authorization: Bearer CHANGE_ME_TO_A_LONG_RANDOM" \ -H "Content-Type: application/json" \ -d '{ "model": "moltbot:main", "messages": [{"role":"user","content":"ping"}], "stream": false }'
(3) 第二步:创建 Node “桥接项目”(可直接复制成仓库)
3.1 初始化项目目录
mkdir moltbot-bridge && cd moltbot-bridge
npm init -y
npm i express dotenv zod
npm i @larksuiteoapi/node-sdk
npm i undici
npm i xml2js
npm i wxcrypt # 企微回调解密用(聊天式可选)
飞书 Node SDK 官方仓库在这里(用长连接能极大降低接入成本)。
3.2 目录结构(照抄)
moltbot-bridge/
.env
package.json
src/
index.js
molt.js
feishu.js
wecom_webhook.js
wecom_callback.js
3.3 .env(你只需要填这些)
# Moltbot Gateway
MOLT_BASE_URL=http://127.0.0.1:18789
MOLT_TOKEN=CHANGE_ME_TO_A_LONG_RANDOM
MOLT_AGENT=main
# Feishu (自建应用)
FEISHU_APP_ID=cli_xxx
FEISHU_APP_SECRET=xxx
FEISHU_DOMAIN=feishu # 可先不改
# WeCom 群机器人(推送)
WECOM_GROUP_WEBHOOK_KEY=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# WeCom 应用回调(聊天式,可选)
WECOM_TOKEN=your_callback_token
WECOM_ENCODING_AES_KEY=your_encoding_aes_key
WECOM_CORP_ID=wwxxxxxxxxxxxxxxxx
PORT=3000
(4) 代码:Moltbot ChatCompletions 调用封装
src/molt.js
import { request } from "undici";
export async function askMoltbot({ baseUrl, token, agent, text, userId }) {
const url = `${baseUrl.replace(/\/$/, "")}/v1/chat/completions`;
const body = {
model: `moltbot:${agent}`,
stream: false,
// 你也可以加上 system prompt,或做 per-user memory key
messages: [
{ role: "user", content: text }
]
};
const res = await request(url, {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": `Bearer ${token}`
},
body: JSON.stringify(body)
});
const json = await res.body.json();
const answer = json?.choices?.[0]?.message?.content ?? "";
return answer.trim() || "(empty)";
}
/v1/chat/completions这条接口与启用方式见官方文档。
(5) 飞书:长连接接入(不需要公网回调)
飞书支持事件订阅的长连接方式,并提供 Node 示例。
发送消息接口文档也有(im.message.create)。
src/feishu.js
import * as lark from "@larksuiteoapi/node-sdk";
import { askMoltbot } from "./molt.js";
export function startFeishu({ appId, appSecret, molt }) {
const client = new lark.Client({
appId,
appSecret,
appType: lark.AppType.SelfBuild,
domain: lark.Domain.Feishu,
});
// 防“自回声死循环”:缓存最近 bot 发的 msg_id(简单版)
const recentBotMsg = new Set();
const remember = (id) => {
recentBotMsg.add(id);
setTimeout(() => recentBotMsg.delete(id), 60_000);
};
client.wsClient.start({
eventDispatcher: async (event) => {
try {
// 1) 只处理“收到消息”事件(你在飞书后台订阅)
const ev = event?.event;
const msg = ev?.message;
const chatId = msg?.chat_id;
const msgId = msg?.message_id;
// 过滤:空消息、自己发的、非文本(你也可以扩展图片/富文本)
const contentStr = msg?.content;
if (!chatId || !contentStr) return;
if (msgId && recentBotMsg.has(msgId)) return;
let text = "";
try {
const parsed = JSON.parse(contentStr);
text = parsed?.text ?? "";
} catch {
// 如果不是 JSON,就忽略
return;
}
text = (text || "").trim();
if (!text) return;
// 2) 调 Moltbot
const answer = await askMoltbot({
baseUrl: molt.baseUrl,
token: molt.token,
agent: molt.agent,
text,
userId: ev?.sender?.sender_id?.open_id
});
// 3) 回发飞书(发到 chat_id)
const resp = await client.im.message.create({
params: { receive_id_type: "chat_id" },
data: {
receive_id: chatId,
msg_type: "text",
content: JSON.stringify({ text: answer })
}
});
// 记住 bot 发出去的 message_id(如果 SDK 返回)
const sentId = resp?.data?.message_id;
if (sentId) remember(sentId);
} catch (e) {
console.error("[feishu] error:", e);
}
},
});
console.log("[feishu] wsClient started");
}
飞书后台要做的 3 件事
-
创建自建应用,开启 Bot 能力
-
给应用加消息相关权限,并订阅 “接收消息”事件(如
im.message.receive_v1) -
选择 长连接订阅方式(无需公网回调 URL)
(6) 微信:优先给你“稳的”方案(企业微信群机器人推送)
src/wecom_webhook.js
import { request } from "undici";
export async function wecomGroupPush({ key, text }) {
const url = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${encodeURIComponent(key)}`;
const body = {
msgtype: "text",
text: { content: text }
};
const res = await request(url, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body)
});
const json = await res.body.json();
if (json.errcode !== 0) {
throw new Error(`WeCom webhook err: ${json.errcode} ${json.errmsg}`);
}
}
这个方案适合:任务完成通知 / 报警 / 日报,不适合微信里“对话”。(但稳定性最高。)
(7) 微信聊天式(企业微信应用回调):给你一套可跑模板
企业微信回调基本流程是:
-
保存回调 URL 时:企业微信发 GET 携带
msg_signature/timestamp/nonce/echostr,你需 验签 + 解密 echostr + 原样返回明文。 -
正式推送消息时:发 POST(一般 XML),你需 验签 + 解密 Encrypt + 解析消息 +(被动回复/主动发消息)。
src/wecom_callback.js(Express 路由模板)
import crypto from "crypto";
import WXBizMsgCrypt from "wxcrypt"; // 依赖库:wxcrypt
import { parseStringPromise } from "xml2js";
import { askMoltbot } from "./molt.js";
// 企业微信:签名 = SHA1(sort(token,timestamp,nonce,encrypt_or_echostr).join(''))
function calcSignature(token, timestamp, nonce, encrypt) {
const arr = [token, timestamp, nonce, encrypt].map(String).sort();
const str = arr.join("");
return crypto.createHash("sha1").update(str).digest("hex");
}
export function mountWecomCallback(app, { path = "/wecom/callback", wecom, molt }) {
const crypt = new WXBizMsgCrypt(wecom.token, wecom.encodingAESKey, wecom.corpId);
// 1) URL 验证(GET)
app.get(path, async (req, res) => {
try {
const { msg_signature, timestamp, nonce, echostr } = req.query;
if (!msg_signature || !timestamp || !nonce || !echostr) return res.status(400).send("bad request");
// verify & decrypt echostr
const expected = calcSignature(wecom.token, timestamp, nonce, echostr);
if (expected !== msg_signature) return res.status(403).send("forbidden");
let plain = "";
crypt.VerifyURL(msg_signature, timestamp, nonce, echostr, (err, result) => {
if (err) return res.status(500).send("fail");
plain = result;
// 企业微信要求:1 秒内“原样返回明文”,不能加引号换行等 :contentReference[oaicite:10]{index=10}
return res.status(200).send(plain);
});
} catch (e) {
console.error("[wecom][GET] error:", e);
res.status(500).send("fail");
}
});
// 2) 接收消息(POST)
// 注意:企业微信通常是 XML;你要用 raw body(这里用最简方式:让 express text() 接)
app.post(path, async (req, res) => {
try {
const { msg_signature, timestamp, nonce } = req.query;
const xml = req.body;
if (!msg_signature || !timestamp || !nonce || !xml) return res.status(400).send("bad request");
// 解密
let decrypted = "";
crypt.DecryptMsg(msg_signature, timestamp, nonce, xml, (err, result) => {
if (err) {
console.error("[wecom] decrypt err", err);
return res.status(500).send("fail");
}
decrypted = result;
});
// result 是明文 XML
const obj = await parseStringPromise(decrypted, { explicitArray: false });
const msg = obj?.xml;
// 这里只处理文本消息(Content)
const content = (msg?.Content || "").trim();
if (!content) return res.status(200).send("success"); // 不处理就 success
// 调 Moltbot
const answer = await askMoltbot({
baseUrl: molt.baseUrl,
token: molt.token,
agent: molt.agent,
text: content,
userId: msg?.FromUserName
});
// 被动回复(加密 XML 返回)
// 企业微信被动回复格式要求严格;如果你们要“被动回复”,建议后续我按你抓包的真实字段给你精修
const replyPlain =
`<xml>
<ToUserName><![CDATA[${msg.FromUserName}]]></ToUserName>
<FromUserName><![CDATA[${msg.ToUserName}]]></FromUserName>
<CreateTime>${Math.floor(Date.now()/1000)}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[${answer}]]></Content>
</xml>`;
crypt.EncryptMsg(replyPlain, timestamp, nonce, (err, encrypted) => {
if (err) return res.status(500).send("fail");
// 正确响应,否则会重试 :contentReference[oaicite:11]{index=11}
return res.status(200).type("application/xml").send(encrypted);
});
} catch (e) {
console.error("[wecom][POST] error:", e);
res.status(200).send("success"); // 避免企业微信重试风暴
}
});
console.log(`[wecom] callback mounted at ${path}`);
}
⚠️ 聊天式企业微信回调对“字段结构、被动回复格式、加密库行为”非常敏感。上面模板能跑,但你上生产前,最好用企业微信后台“回调验证 + 发送一条文本消息”做端到端验证(有问题我可以根据你发的回调明文 XML 直接把回复部分修到 100% 兼容)。
(8) 入口:启动 Bridge(飞书 + 企微推送/回调)
src/index.js
import "dotenv/config";
import express from "express";
import { z } from "zod";
import { startFeishu } from "./feishu.js";
import { wecomGroupPush } from "./wecom_webhook.js";
import { mountWecomCallback } from "./wecom_callback.js";
const env = z.object({
MOLT_BASE_URL: z.string(),
MOLT_TOKEN: z.string(),
MOLT_AGENT: z.string().default("main"),
FEISHU_APP_ID: z.string().optional(),
FEISHU_APP_SECRET: z.string().optional(),
WECOM_GROUP_WEBHOOK_KEY: z.string().optional(),
WECOM_TOKEN: z.string().optional(),
WECOM_ENCODING_AES_KEY: z.string().optional(),
WECOM_CORP_ID: z.string().optional(),
PORT: z.coerce.number().default(3000),
}).parse(process.env);
const molt = {
baseUrl: env.MOLT_BASE_URL,
token: env.MOLT_TOKEN,
agent: env.MOLT_AGENT
};
// HTTP server(企微回调需要)
const app = express();
// 企业微信 POST 需要 raw/xml:这里简单用 text() 收原始字符串
app.use("/wecom/callback", express.text({ type: "*/*" }));
// 健康检查
app.get("/healthz", (_, res) => res.json({ ok: true }));
// WeCom:群机器人推送测试
app.post("/wecom/push-test", express.json(), async (req, res) => {
try {
if (!env.WECOM_GROUP_WEBHOOK_KEY) return res.status(400).json({ error: "no WECOM_GROUP_WEBHOOK_KEY" });
const text = req.body?.text ?? "test";
await wecomGroupPush({ key: env.WECOM_GROUP_WEBHOOK_KEY, text });
res.json({ ok: true });
} catch (e) {
res.status(500).json({ ok: false, error: String(e) });
}
});
// WeCom:聊天式回调(可选)
if (env.WECOM_TOKEN && env.WECOM_ENCODING_AES_KEY && env.WECOM_CORP_ID) {
mountWecomCallback(app, {
path: "/wecom/callback",
wecom: {
token: env.WECOM_TOKEN,
encodingAESKey: env.WECOM_ENCODING_AES_KEY,
corpId: env.WECOM_CORP_ID
},
molt
});
}
app.listen(env.PORT, () => {
console.log(`[http] listening on :${env.PORT}`);
});
// Feishu:长连接(可选)
if (env.FEISHU_APP_ID && env.FEISHU_APP_SECRET) {
startFeishu({
appId: env.FEISHU_APP_ID,
appSecret: env.FEISHU_APP_SECRET,
molt
});
}
package.json 增加:
{
"type": "module",
"scripts": {
"start": "node src/index.js"
}
}
启动:
npm start
(9) 安全加固(必须照做)
9.1 先跑官方审计命令(强烈建议每次改配置后都跑)
moltbot security audit
moltbot security audit --deep
moltbot security audit --fix
这些命令与含义在官方 CLI / Security 文档里明确给出。
9.2 最小暴露面(关键)
-
Gateway 只监听本机(
127.0.0.1),让桥接与 Gateway 同机通信 -
如果企业微信回调必须公网:
-
只暴露 bridge 的
/wecom/callback,不要暴露 gateway -
用反代加 IP allowlist / 基本鉴权 / 限速(防重放/爆破)
-
9.3 最小权限(最容易翻车)
-
不要让 agent 默认拥有全盘读写/任意 shell
-
工具执行建议放容器里(Docker)并只挂载必要目录
-
密钥(飞书 app_secret、企微 AESKey、MOLT_TOKEN)用
.env+ 文件权限收紧,不要写进代码仓库
(10) 按这个顺序做
-
✅ 先把 Moltbot
/v1/chat/completions打通(curl) -
✅ 跑
npm start启动 bridge,访问http://localhost:3000/healthz -
✅ 飞书:订阅接收消息事件 + 长连接(不用公网回调)
-
✅ 微信:先用企业微信群机器人 webhook 推送跑通(
/wecom/push-test) -
🔒 全面加固:
moltbot security audit --deep -
(可选)再上企业微信聊天式回调(需要 HTTPS 回调 URL)
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)