目标

  • ✅ 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 件事

  1. 创建自建应用,开启 Bot 能力

  2. 给应用加消息相关权限,并订阅 “接收消息”事件(如 im.message.receive_v1

  3. 选择 长连接订阅方式(无需公网回调 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) 按这个顺序做

  1. ✅ 先把 Moltbot /v1/chat/completions 打通(curl)

  2. ✅ 跑 npm start 启动 bridge,访问 http://localhost:3000/healthz

  3. ✅ 飞书:订阅接收消息事件 + 长连接(不用公网回调)

  4. ✅ 微信:先用企业微信群机器人 webhook 推送跑通(/wecom/push-test

  5. 🔒 全面加固:moltbot security audit --deep

  6. (可选)再上企业微信聊天式回调(需要 HTTPS 回调 URL)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐