@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
- 📎 媒体素材上传 — 支持分片上传临时素材(file/image/voice/video),自动管理并发与重试
- 🪵 可插拔日志 — 支持自定义 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';
const wsClient = new AiBot.WSClient({
botId: 'your-bot-id',
secret: 'your-bot-secret',
});
wsClient.connect();
wsClient.on('authenticated', () => {
console.log('🔐 认证成功');
});
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);
});
wsClient.on('event.enter_chat', (frame: WsFrame) => {
wsClient.replyWelcome(frame, {
msgtype: 'text',
text: { content: '您好!我是智能助手,有什么可以帮您的吗?' },
});
});
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 通道发送回复消息(通用方法) | Promise<WsFrame> |
replyStream(frame, streamId, content, finish?, msgItem?, feedback?) | 发送流式文本回复(支持 Markdown) | 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> |
uploadMedia(fileBuffer, options) | 上传临时素材(三步分片上传),返回 media_id | Promise<UploadMediaFinishResult> |
replyMedia(frame, mediaType, mediaId, videoOptions?) | 被动回复媒体消息(file/image/voice/video) | Promise<WsFrame> |
sendMediaMessage(chatid, mediaType, mediaId, videoOptions?) | 主动发送媒体消息 | Promise<WsFrame> |
downloadFile(url, aesKey) | 下载文件并 AES 解密,返回 Buffer 及文件名 | Promise<{ buffer: Buffer; filename?: string }> |
属性
isConnected | 当前 WebSocket 连接状态 | boolean |
api | 内部 API 客户端实例(高级用途) | WeComApiClient |
replyStream 详细说明
发送流式文本回复(便捷方法,支持 Markdown)。
wsClient.replyStream(
frame: WsFrameHeaders,
streamId: string,
content: string,
finish?: boolean,
msgItem?: ReplyMsgItem[],
feedback?: ReplyFeedback,
);
使用示例:
const streamId = generateReqId('stream');
await wsClient.replyStream(frame, streamId, '正在处理中...', false);
await wsClient.replyStream(frame, streamId, '处理完成!结果是...', true);
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,
templateCard: TemplateCard,
feedback?: ReplyFeedback,
);
replyStreamWithCard 详细说明
发送流式消息 + 模板卡片组合回复。首次回复时必须返回 stream 的 id;template_card 同一消息只能回复一次。
wsClient.replyStreamWithCard(
frame: WsFrameHeaders,
streamId: string,
content: string,
finish?: boolean,
options?: {
msgItem?: ReplyMsgItem[];
streamFeedback?: ReplyFeedback;
templateCard?: TemplateCard;
cardFeedback?: ReplyFeedback;
},
);
使用示例:
const streamId = generateReqId('stream');
await wsClient.replyStreamWithCard(frame, streamId, '正在处理...', false, {
templateCard: {
card_type: 'button_interaction',
main_title: { title: '操作面板' },
button_list: [{ text: '确认', key: 'confirm' }],
task_id: `task_${Date.now()}`,
},
});
await wsClient.replyStreamWithCard(frame, streamId, '处理完成!', true);
updateTemplateCard 详细说明
更新模板卡片,需在收到 event.template_card_event 事件 5 秒内调用。
wsClient.updateTemplateCard(
frame: WsFrameHeaders,
templateCard: TemplateCard,
userids?: string[],
);
sendMessage 详细说明
主动向指定会话推送消息,无需依赖收到的回调帧。
wsClient.sendMessage(
chatid: string,
body: SendMarkdownMsgBody | SendTemplateCardMsgBody | SendMediaMsgBody,
);
使用示例:
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: '通知' } },
});
uploadMedia 详细说明
通过 WebSocket 长连接执行三步分片上传:init → chunk × N → finish。
- 单个分片不超过 512KB(Base64 编码前),最多 100 个分片(约 50MB 上限)
- 自动根据分片数调整并发数(1~4 分片全并发;5~10 分片并发 3;>10 分片并发 2)
- 单分片上传失败自动重试(最多 2 次)
wsClient.uploadMedia(
fileBuffer: Buffer,
options: UploadMediaOptions,
): Promise<UploadMediaFinishResult>;
使用示例:
import fs from 'fs';
const imageBuffer = fs.readFileSync('/path/to/image.png');
const result = await wsClient.uploadMedia(imageBuffer, {
type: 'image',
filename: 'image.png',
});
console.log(`上传成功,media_id: ${result.media_id}`);
await wsClient.replyMedia(frame, 'image', result.media_id);
replyMedia 详细说明
被动回复媒体消息(通过 aibot_respond_msg 通道)。
wsClient.replyMedia(
frame: WsFrameHeaders,
mediaType: WeComMediaType,
mediaId: string,
videoOptions?: {
title?: string;
description?: string;
},
);
sendMediaMessage 详细说明
主动发送媒体消息(通过 aibot_send_msg 通道推送)。
wsClient.sendMediaMessage(
chatid: string,
mediaType: WeComMediaType,
mediaId: string,
videoOptions?: {
title?: string;
description?: string;
},
);
downloadFile 使用示例
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 连接地址 |
wsOptions | string | — | string | 自签证书地址 |
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> | 收到文件消息 |
message.video | frame: WsFrame<VideoMessage> | 收到视频消息 |
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 解密) |
Video | 'video' | 视频消息(URL 已加密,使用消息中的 video.aeskey 解密) |
SDK 支持以下事件类型(EventType 枚举):
EnterChat | 'enter_chat' | 进入会话事件:用户当天首次进入机器人单聊会话 |
TemplateCardEvent | 'template_card_event' | 模板卡片事件:用户点击模板卡片按钮 |
FeedbackEvent | 'feedback_event' | 用户反馈事件:用户对机器人回复进行反馈 |
SDK 支持以下媒体类型(WeComMediaType 类型):
| — | 'file' | 文件 |
| — | 'image' | 图片 |
| — | 'voice' | 语音 |
| — | 'video' | 视频 |
🃏 模板卡片类型
SDK 支持以下模板卡片类型(TemplateCardType 枚举):
TextNotice | 'text_notice' | 文本通知模版卡片 |
NewsNotice | 'news_notice' | 图文展示模版卡片 |
ButtonInteraction | 'button_interaction' | 按钮交互模版卡片 |
VoteInteraction | 'vote_interaction' | 投票选择模版卡片 |
MultipleInteraction | 'multiple_interaction' | 多项选择模版卡片 |
🔀 消息帧结构
WsFrame<T>
interface WsFrame<T = any> {
cmd?: string;
headers: {
req_id: string;
[key: string]: any;
};
body?: T;
errcode?: number;
errmsg?: string;
}
BaseMessage(消息体基础结构)
interface BaseMessage {
msgid: string;
aibotid: string;
chatid?: string;
chattype: 'single' | 'group';
from: { userid: string };
create_time?: number;
response_url?: string;
msgtype: string;
quote?: QuoteContent;
}
EventMessage(事件消息结构)
interface EventMessage {
msgid: string;
create_time: number;
aibotid: string;
chatid?: string;
chattype?: 'single' | 'group';
from: EventFrom;
msgtype: '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: () => {},
info: console.log,
warn: console.warn,
error: console.error,
},
});
私有部署企业使用示例:
const wsClient = new AiBot.WSClient({
botId: 'your-bot-id',
secret: 'your-bot-secret',
wsUrl: 'your-wsUrl',
wsOptions: {
ca: fs.readFileSync('your-ca-path'),
},
logger: {
debug: () => {},
info: console.log,
warn: console.warn,
error: console.error,
},
});
🔧 WebSocket 命令协议
以下为 SDK 内部使用的 WebSocket 命令常量(WsCmd),了解底层协议有助于高级调试:
| 开发者 → 企微 | SUBSCRIBE | aibot_subscribe | 认证订阅 |
| 开发者 → 企微 | HEARTBEAT | ping | 心跳 |
| 开发者 → 企微 | RESPONSE | aibot_respond_msg | 回复消息 |
| 开发者 → 企微 | RESPONSE_WELCOME | aibot_respond_welcome_msg | 回复欢迎语 |
| 开发者 → 企微 | RESPONSE_UPDATE | aibot_respond_update_msg | 更新模板卡片 |
| 开发者 → 企微 | SEND_MSG | aibot_send_msg | 主动发送消息 |
| 开发者 → 企微 | UPLOAD_MEDIA_INIT | aibot_upload_media_init | 上传素材 - 初始化 |
| 开发者 → 企微 | UPLOAD_MEDIA_CHUNK | aibot_upload_media_chunk | 上传素材 - 分片 |
| 开发者 → 企微 | UPLOAD_MEDIA_FINISH | aibot_upload_media_finish | 上传素材 - 完成 |
| 企微 → 开发者 | CALLBACK | aibot_msg_callback | 消息推送回调 |
| 企微 → 开发者 | EVENT_CALLBACK | aibot_event_callback | 事件推送回调 |
📂 项目结构
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/
│ ├── 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
🧩 完整使用示例
流式回复 + 图文混排
import AiBot from '@wecom/aibot-node-sdk';
import type { WsFrame, ReplyMsgItem } from '@wecom/aibot-node-sdk';
import { generateReqId } from '@wecom/aibot-node-sdk';
import { createHash } from 'crypto';
import fs from 'fs';
const wsClient = new AiBot.WSClient({
botId: 'your-bot-id',
secret: 'your-bot-secret',
});
wsClient.connect();
wsClient.on('message.text', async (frame: WsFrame) => {
const streamId = generateReqId('stream');
await wsClient.replyStream(frame, streamId, '正在生成图文内容...', false);
const imageData = fs.readFileSync('/path/to/image.jpg');
const base64 = imageData.toString('base64');
const md5 = createHash('md5').update(imageData).digest('hex');
const msgItem: ReplyMsgItem[] = [
{ msgtype: 'image', image: { base64, md5 } },
];
await wsClient.replyStream(frame, streamId, '这是最终结果', true, msgItem);
});
上传素材 + 回复媒体消息
import AiBot from '@wecom/aibot-node-sdk';
import type { WsFrame } from '@wecom/aibot-node-sdk';
import fs from 'fs';
const wsClient = new AiBot.WSClient({
botId: 'your-bot-id',
secret: 'your-bot-secret',
});
wsClient.connect();
wsClient.on('message.text', async (frame: WsFrame) => {
const fileBuffer = fs.readFileSync('/path/to/document.pdf');
const result = await wsClient.uploadMedia(fileBuffer, {
type: 'file',
filename: 'document.pdf',
});
await wsClient.replyMedia(frame, 'file', result.media_id);
});
主动推送消息
wsClient.on('authenticated', async () => {
await wsClient.sendMessage('target_userid', {
msgtype: 'markdown',
markdown: { content: '# 通知\n\n这是一条**主动推送**的消息。' },
});
const imageBuffer = fs.readFileSync('/path/to/photo.jpg');
const result = await wsClient.uploadMedia(imageBuffer, {
type: 'image',
filename: 'photo.jpg',
});
await wsClient.sendMediaMessage('target_userid', 'image', result.media_id);
});
模板卡片交互
wsClient.on('message.text', async (frame: WsFrame) => {
await wsClient.replyTemplateCard(frame, {
card_type: 'button_interaction',
main_title: { title: '请选择操作', desc: '点击下方按钮进行操作' },
button_list: [
{ text: '确认', key: 'btn_confirm', style: 1 },
{ text: '取消', key: 'btn_cancel', style: 2 },
],
task_id: `task_${Date.now()}`,
});
});
wsClient.on('event.template_card_event', async (frame: WsFrame) => {
const eventKey = frame.body.event?.event_key;
const taskId = frame.body.event?.task_id;
await wsClient.updateTemplateCard(frame, {
card_type: 'text_notice',
main_title: { title: eventKey === 'btn_confirm' ? '已确认 ✅' : '已取消 ❌' },
task_id: taskId,
});
});
文件下载解密
import fs from 'fs';
import path from 'path';
wsClient.on('message.image', async (frame: WsFrame) => {
const body = frame.body;
const imageUrl = body.image?.url;
if (!imageUrl) return;
const { buffer, filename } = await wsClient.downloadFile(imageUrl, body.image?.aeskey);
const savePath = path.join(__dirname, filename || `image_${Date.now()}.jpg`);
fs.writeFileSync(savePath, buffer);
console.log(`图片已保存: ${savePath} (${buffer.length} bytes)`);
});
wsClient.on('message.file', async (frame: WsFrame) => {
const body = frame.body;
const fileUrl = body.file?.url;
if (!fileUrl) return;
const { buffer, filename } = await wsClient.downloadFile(fileUrl, body.file?.aeskey);
const savePath = path.join(__dirname, filename || `file_${Date.now()}`);
fs.writeFileSync(savePath, buffer);
console.log(`文件已保存: ${savePath} (${buffer.length} bytes)`);
});
🔧 开发
yarn install
yarn dev
yarn build
yarn example
🔗 导出说明
SDK 同时支持默认导出和具名导出:
import AiBot from '@wecom/aibot-node-sdk';
const wsClient = new AiBot.WSClient({ ... });
import { WSClient, generateReqId } from '@wecom/aibot-node-sdk';
const wsClient = new WSClient({ ... });
import type { WsFrame, BaseMessage, TextMessage, TemplateCard } from '@wecom/aibot-node-sdk';
完整导出列表:
| 类 | WSClient、WeComApiClient、WsConnectionManager、MessageHandler、DefaultLogger |
| 函数 | generateReqId、generateRandomString、decryptFile |
| 枚举 | MessageType、EventType、TemplateCardType、WsCmd |
| 类型 | WSClientOptions、WSClientEventMap、WsFrame、WsFrameHeaders、BaseMessage、TextMessage、ImageMessage、MixedMessage、VoiceMessage、FileMessage、VideoMessage、EventMessage、TemplateCard、StreamReplyBody、ReplyMsgItem、ReplyFeedback、Logger 等 |
📄 License
MIT