Skip to content

Latest commit

 

History

History
 
 

README.md

@wecom/aibot-node-sdk

企业微信智能机器人 Node.js SDK —— 基于 WebSocket 长连接通道,提供消息收发、流式回复、模板卡片、事件回调、文件下载解密等核心能力。

✨ 特性

  • 🔗 WebSocket 长连接 — 基于 wss://openws.work.weixin.qq.com 内置默认地址,开箱即用
  • 🔐 自动认证 — 连接建立后自动发送认证帧(botId + secret)
  • 💓 心跳保活 — 自动维护心跳,连续未收到 ack 时自动判定连接异常
  • 🔄 断线重连 — 指数退避重连策略(1s → 2s → 4s → ... → 30s 上限),支持自定义最大重连次数
  • 📨 消息分发 — 自动解析消息类型并触发对应事件(text / image / mixed / voice / file)
  • 🌊 流式回复 — 内置流式回复方法,支持 Markdown 和图文混排
  • 🃏 模板卡片 — 支持回复模板卡片消息、流式+卡片组合回复、更新卡片
  • 📤 主动推送 — 支持向指定会话主动发送 Markdown 或模板卡片消息,无需依赖回调帧
  • 📡 事件回调 — 支持进入会话、模板卡片按钮点击、用户反馈等事件
  • 串行回复队列 — 同一 req_id 的回复消息串行发送,自动等待回执
  • 文件下载解密 — 内置 AES-256-CBC 文件解密,每个图片/文件消息自带独立的 aeskey
  • 🪵 可插拔日志 — 支持自定义 Logger,内置带时间戳的 DefaultLogger
  • 📦 双模块格式 — 同时输出 CJS / ESM,附带完整 TypeScript 类型声明

📦 安装

npm install @wecom/aibot-node-sdk
#
yarn add @wecom/aibot-node-sdk

🚀 快速开始

import AiBot from '@wecom/aibot-node-sdk';
import type { WsFrame } from '@wecom/aibot-node-sdk';
import { generateReqId } from '@wecom/aibot-node-sdk';

// 1. 创建客户端实例
const wsClient = new AiBot.WSClient({
  botId: 'your-bot-id',       // 企业微信后台获取的机器人 ID
  secret: 'your-bot-secret',  // 企业微信后台获取的机器人 Secret
});

// 2. 建立连接(支持链式调用)
wsClient.connect();

// 3. 监听认证成功
wsClient.on('authenticated', () => {
  console.log('🔐 认证成功');
});

// 4. 监听文本消息并进行流式回复
wsClient.on('message.text', (frame: WsFrame) => {
  const content = frame.body.text?.content;
  console.log(`收到文本: ${content}`);

  const streamId = generateReqId('stream');

  // 发送流式中间内容
  wsClient.replyStream(frame, streamId, '正在思考中...', false);

  // 发送最终结果
  setTimeout(() => {
    wsClient.replyStream(frame, streamId, `你好!你说的是: "${content}"`, true);
  }, 1000);
});

// 5. 监听进入会话事件(发送欢迎语)
wsClient.on('event.enter_chat', (frame: WsFrame) => {
  wsClient.replyWelcome(frame, {
    msgtype: 'text',
    text: { content: '您好!我是智能助手,有什么可以帮您的吗?' },
  });
});

// 6. 优雅退出
process.on('SIGINT', () => {
  wsClient.disconnect();
  process.exit(0);
});

📖 API 文档

WSClient

核心客户端类,继承自 EventEmitter,提供连接管理、消息收发等功能。

构造函数

const wsClient = new WSClient(options: WSClientOptions);

方法

方法 说明 返回值
connect() 建立 WebSocket 连接,连接后自动认证 this(支持链式调用)
disconnect() 主动断开连接 void
reply(frame, body, cmd?) 通过 WebSocket 通道发送回复消息(通用方法),frame 类型为 WsFrameHeaders(即 Pick<WsFrame, 'headers'>),直接传入完整 WsFrame 对象也可兼容 Promise<WsFrame>
replyStream(frame, streamId, content, finish?, msgItem?, feedback?) 发送流式文本回复(便捷方法,支持 Markdown),frame 类型同上 Promise<WsFrame>
replyWelcome(frame, body) 发送欢迎语回复(支持文本或模板卡片),需在收到事件 5s 内调用 Promise<WsFrame>
replyTemplateCard(frame, templateCard, feedback?) 回复模板卡片消息 Promise<WsFrame>
replyStreamWithCard(frame, streamId, content, finish?, options?) 发送流式消息 + 模板卡片组合回复 Promise<WsFrame>
updateTemplateCard(frame, templateCard, userids?) 更新模板卡片(响应 template_card_event),需在收到事件 5s 内调用 Promise<WsFrame>
sendMessage(chatid, body) 主动发送消息(支持 Markdown 或模板卡片),无需依赖回调帧 Promise<WsFrame>
downloadFile(url, aesKey) 下载文件并使用 AES 密钥解密(aesKey 取自消息体),返回 Buffer 及文件名 Promise<{ buffer: Buffer; filename?: string }>

属性

属性 说明 类型
isConnected 当前 WebSocket 连接状态 boolean
api 内部 API 客户端实例(高级用途) WeComApiClient

replyStream 详细说明

wsClient.replyStream(
  frame: WsFrameHeaders, // 收到的原始 WebSocket 帧(透传 req_id),也可直接传完整 WsFrame 对象
  streamId: string,     // 流式消息 ID(使用 generateReqId('stream') 生成)
  content: string,      // 回复内容(支持 Markdown)
  finish?: boolean,     // 是否结束流式消息,默认 false
  msgItem?: ReplyMsgItem[], // 图文混排项(仅 finish=true 时有效)
  feedback?: ReplyFeedback, // 反馈信息(仅首次回复时设置)
);

replyWelcome 详细说明

发送欢迎语回复,需在收到 event.enter_chat 事件 5 秒内调用,超时将无法发送。

// 文本欢迎语
wsClient.replyWelcome(frame, {
  msgtype: 'text',
  text: { content: '欢迎!' },
});

// 模板卡片欢迎语
wsClient.replyWelcome(frame, {
  msgtype: 'template_card',
  template_card: { card_type: 'text_notice', main_title: { title: '欢迎' } },
});

replyTemplateCard 详细说明

wsClient.replyTemplateCard(
  frame: WsFrameHeaders,    // 收到的原始 WebSocket 帧
  templateCard: TemplateCard, // 模板卡片内容
  feedback?: ReplyFeedback,  // 反馈信息(可选)
);

replyStreamWithCard 详细说明

wsClient.replyStreamWithCard(
  frame: WsFrameHeaders,  // 收到的原始 WebSocket 帧
  streamId: string,        // 流式消息 ID
  content: string,         // 回复内容(支持 Markdown)
  finish?: boolean,        // 是否结束流式消息,默认 false
  options?: {
    msgItem?: ReplyMsgItem[];       // 图文混排项(仅 finish=true 时有效)
    streamFeedback?: ReplyFeedback; // 流式消息反馈信息(首次回复时设置)
    templateCard?: TemplateCard;    // 模板卡片内容(同一消息只能回复一次)
    cardFeedback?: ReplyFeedback;   // 模板卡片反馈信息
  },
);

updateTemplateCard 详细说明

更新模板卡片,需在收到 event.template_card_event 事件 5 秒内调用。

wsClient.updateTemplateCard(
  frame: WsFrameHeaders,    // 对应事件的 WebSocket 帧(需包含该事件的 req_id)
  templateCard: TemplateCard, // 模板卡片内容(task_id 需与回调收到的 task_id 一致)
  userids?: string[],        // 要替换模版卡片消息的 userid 列表,不填则替换所有用户
);

sendMessage 详细说明

主动向指定会话推送消息,无需依赖收到的回调帧。

wsClient.sendMessage(
  chatid: string,  // 会话 ID,单聊填用户的 userid,群聊填对应群聊的 chatid
  body: SendMarkdownMsgBody | SendTemplateCardMsgBody, // 消息体
);

使用示例:

// 发送 Markdown 消息
await wsClient.sendMessage('userid_or_chatid', {
  msgtype: 'markdown',
  markdown: { content: '这是一条**主动推送**的消息' },
});

// 发送模板卡片消息
await wsClient.sendMessage('userid_or_chatid', {
  msgtype: 'template_card',
  template_card: { card_type: 'text_notice', main_title: { title: '通知' } },
});

downloadFile 使用示例

// aesKey 取自消息体中的 image.aeskey 或 file.aeskey
wsClient.on('message.image', async (frame: WsFrame) => {
  const body = frame.body;
  const { buffer, filename } = await wsClient.downloadFile(body.image?.url, body.image?.aeskey);
  console.log(`文件名: ${filename}, 大小: ${buffer.length} bytes`);
});

⚙️ 配置选项

WSClientOptions 完整配置:

参数 类型 必填 默认值 说明
botId string 机器人 ID(企业微信后台获取)
secret string 机器人 Secret(企业微信后台获取)
reconnectInterval number 1000 重连基础延迟(毫秒),实际延迟按指数退避递增(1s → 2s → 4s → ... → 30s 上限)
maxReconnectAttempts number 10 最大重连次数(-1 表示无限重连)
heartbeatInterval number 30000 心跳间隔(毫秒)
requestTimeout number 10000 HTTP 请求超时时间(毫秒)
wsUrl string wss://openws.work.weixin.qq.com 自定义 WebSocket 连接地址
logger Logger DefaultLogger 自定义日志实例

📡 事件列表

所有事件均通过 wsClient.on(event, handler) 监听:

事件 回调参数 说明
connected WebSocket 连接建立
authenticated 认证成功
disconnected reason: string 连接断开
reconnecting attempt: number 正在重连(第 N 次)
error error: Error 发生错误
message frame: WsFrame<BaseMessage> 收到消息(所有类型)
message.text frame: WsFrame<TextMessage> 收到文本消息
message.image frame: WsFrame<ImageMessage> 收到图片消息
message.mixed frame: WsFrame<MixedMessage> 收到图文混排消息
message.voice frame: WsFrame<VoiceMessage> 收到语音消息
message.file frame: WsFrame<FileMessage> 收到文件消息
event frame: WsFrame<EventMessage> 收到事件回调(所有事件类型)
event.enter_chat frame: WsFrame<EventMessage> 收到进入会话事件(用户当天首次进入单聊会话)
event.template_card_event frame: WsFrame<EventMessage> 收到模板卡片事件(用户点击卡片按钮)
event.feedback_event frame: WsFrame<EventMessage> 收到用户反馈事件

📋 消息类型

SDK 支持以下消息类型(MessageType 枚举):

类型 说明
Text 'text' 文本消息
Image 'image' 图片消息(URL 已加密,使用消息中的 image.aeskey 解密)
Mixed 'mixed' 图文混排消息(包含 text / image 子项)
Voice 'voice' 语音消息(已转文本)
File 'file' 文件消息(URL 已加密,使用消息中的 file.aeskey 解密)

SDK 支持以下事件类型(EventType 枚举):

类型 说明
EnterChat 'enter_chat' 进入会话事件:用户当天首次进入机器人单聊会话
TemplateCardEvent 'template_card_event' 模板卡片事件:用户点击模板卡片按钮
FeedbackEvent 'feedback_event' 用户反馈事件:用户对机器人回复进行反馈

消息帧结构(WsFrame<T>

interface WsFrame<T = any> {
  cmd?: string;              // 命令类型
  headers: {
    req_id: string;          // 请求 ID(回复时需透传)
    [key: string]: any;
  };
  body?: T;                  // 消息体(泛型,默认 any)
  errcode?: number;          // 响应错误码
  errmsg?: string;           // 响应错误信息
}

消息体结构(BaseMessage

interface BaseMessage {
  msgid: string;             // 消息唯一标识
  aibotid: string;           // 机器人 ID
  chatid?: string;           // 群聊 ID(群聊时返回)
  chattype: 'single' | 'group';  // 会话类型
  from: { userid: string };  // 发送者信息
  create_time?: number;      // 事件产生的时间戳
  response_url?: string;     // 支持主动回复消息的临时 url
  msgtype: string;           // 消息类型
  quote?: QuoteContent;      // 引用消息内容
}

事件消息结构(EventMessage

interface EventMessage {
  msgid: string;             // 本次回调的唯一性标志
  create_time: number;       // 事件产生的时间戳
  aibotid: string;           // 智能机器人 ID
  chatid?: string;           // 会话 ID(仅群聊时返回)
  chattype?: 'single' | 'group';  // 会话类型
  from: EventFrom;           // 事件触发者信息(含 userid、corpid?)
  msgtype: 'event';          // 消息类型,固定为 event
  event: EventContent;       // 事件内容
}

🪵 自定义日志

实现 Logger 接口即可自定义日志输出:

interface Logger {
  debug(message: string, ...args: any[]): void;
  info(message: string, ...args: any[]): void;
  warn(message: string, ...args: any[]): void;
  error(message: string, ...args: any[]): void;
}

使用示例:

const wsClient = new AiBot.WSClient({
  botId: 'your-bot-id',
  secret: 'your-bot-secret',
  logger: {
    debug: () => {},  // 静默 debug 日志
    info: console.log,
    warn: console.warn,
    error: console.error,
  },
});

📂 项目结构

aibot-node-sdk/
├── src/
│   ├── index.ts             # 入口文件,统一导出
│   ├── client.ts            # WSClient 核心客户端
│   ├── ws.ts                # WebSocket 长连接管理器
│   ├── message-handler.ts   # 消息解析与事件分发
│   ├── api.ts               # HTTP API 客户端(文件下载)
│   ├── crypto.ts            # AES-256-CBC 文件解密
│   ├── logger.ts            # 默认日志实现
│   ├── utils.ts             # 工具方法(generateReqId 等)
│   ├── types.ts             # 类型重导出
│   └── types/
│       ├── index.ts          # 类型统一导出
│       ├── config.ts         # 配置选项类型
│       ├── event.ts          # 事件映射类型
│       ├── message.ts        # 消息相关类型
│       ├── api.ts            # API/WebSocket 帧类型
│       └── common.ts         # 通用类型(Logger)
├── examples/
│   └── basic.ts             # 基础使用示例
├── package.json
├── tsconfig.json
├── rollup.config.mjs        # Rollup 构建配置
└── yarn.lock

🔧 开发

# 安装依赖
yarn install

# 开发模式(监听文件变化)
yarn dev

# 构建
yarn build

# 运行示例
yarn example

🦐 其他语言

📄 License

MIT