平台概述
BotFox 是一个微信机器人管理平台。用户在控制台绑定微信 Bot 后,通过安装插件来扩展 Bot 的能力。
作为插件开发者,你编写的代码会在用户的 Bot 收到消息时被调用。平台提供了丰富的上下文和工具,让你专注于业务逻辑。
插件能做什么
- 接收并处理文字、图片、语音、视频、文件消息
- 回复文字、发送图片/语音/视频/文件
- 调用外部 API(AI 对话、天气查询、翻译等)
- 持久化存储数据(游戏存档、对话历史、用户偏好)
- 处理引用消息,获取语音转文字结果
- 控制打字状态("对方正在输入")
能力边界
开发插件前请先了解平台的能力边界。
✅ 插件可以做的
| 接收所有类型消息 | 文字、图片、语音、视频、文件,平台自动下载媒体 |
| 语音转文字 | SDK 自动识别,通过 msg.voice.text 获取 |
| AI 看图 | 收到图片时可获取 mediaBuffer,转 base64 发给视觉模型 |
| 引用消息 | 通过 quotedMessage 和 msg.textWithQuote 获取引用内容 |
| 回复文字 | bot.reply(msg, text) |
| 发送图片/语音/视频/文件 | bot.sendImage/sendVoice/sendVideo/sendFile |
| 打字状态 | bot.sendTyping() / bot.cancelTyping() |
| 调用外部 API | 可以使用 fetch(仅 HTTPS) |
| 持久化存储 | 每用户 5MB,storage.get/set/delete |
❌ 插件不能做的
| 访问文件系统 | 沙箱禁止 fs、process、require、child_process |
| 群聊 | Bot 只支持 1 对 1 单聊 |
| 获取好友/群列表 | SDK 不提供通讯录接口 |
| 发送名片/位置/小程序 | 仅支持文字、图片、语音、视频、文件 |
| 消息撤回/已读 | SDK 不支持 |
5 分钟上手
一个最简单的插件:
export async function handler({ bot, msg }) {
if (msg.text === '你好') {
await bot.reply(msg, '你好呀~ 😊');
return { handled: true }; // 阻止后续插件执行
}
return { handled: false }; // 不处理,交给下一个插件
}
提交流程
- 在插件库页面点击「提交插件」
- 填写名称、描述、代码
- 提交后等待管理员审核
- 审核通过后,所有用户都能在插件库看到并安装
插件上下文
插件 handler 函数接收一个对象,包含所有你需要的工具:
export async function handler({
bot, // WeixinBot 实例 — 发消息、下载媒体等
msg, // ParsedMessage — 收到的消息
config, // object — 用户在控制台配置的参数
botDbId, // number — Bot 数据库 ID
userId, // number — 用户 ID
storage, // StorageHelper — 持久化存储
mediaBuffer, // Buffer | null — 平台已下载的媒体数据
quotedMessage, // object | null — 引用消息 { title, item, text }
messageId, // number | null — SDK 原始消息 ID
}) {
// 你的业务逻辑
return { handled: true }; // 已处理
// 或
return { handled: false }; // 未处理,继续下一个插件
}
消息对象 (msg)
| 字段 | 类型 | 说明 |
|---|---|---|
| type | string | text | image | voice | file | video |
| text | string | 文本内容。语音消息时为语音转文字结果 |
| textWithQuote | string? | 带引用上下文:[引用: xxx]\n原文 |
| from | string | 发送者用户 ID |
| to | string | 接收者 ID(Bot 自身) |
| timestamp | number | 消息时间戳(毫秒) |
| contextToken | string | 回复用的上下文 token |
| messageId | number | SDK 原始消息 ID |
| image | ImageItem? | { thumb_width, thumb_height, mid_size, media } |
| voice | VoiceItem? | { playtime, text, media } — playtime 毫秒,text 语音转文字 |
| file | FileItem? | { file_name, len, md5, media } |
| video | VideoItem? | { play_length, video_size, thumb_width, thumb_height, media } |
| quotedMessage | object? | { title, item, text } — text 是 SDK 提取的引用文本 |
| raw | object | SDK 原始消息对象 |
Bot 方法
| 方法 | 说明 |
|---|---|
bot.reply(msg, text) | 回复文字消息(最常用) |
bot.sendText(userId, text, contextToken) | 主动发送文字 |
bot.sendImage(userId, buffer) | 发送图片 |
bot.sendVoice(userId, buffer, contextToken, options?) | 发送语音(SILK 格式) |
bot.sendVideo(userId, buffer) | 发送视频 |
bot.sendFile(userId, buffer, filename) | 发送文件 |
bot.sendTyping(userId) | 显示"正在输入" |
bot.cancelTyping(userId) | 取消"正在输入" |
bot.downloadImage(imageItem) | 下载图片 → Buffer |
bot.downloadVoice(voiceItem) | 下载语音 → Buffer |
bot.downloadFile(fileItem) | 下载文件 → Buffer |
bot.downloadVideo(videoItem) | 下载视频 → Buffer |
bot.downloadRaw(encryptQueryParam) | 下载原始 CDN 数据(不解密) |
💡 平台自动下载媒体并通过 mediaBuffer 传入,通常不需要手动调用 download。
💡 SDK 提供 markdownToPlainText(text),可将 Markdown 转为微信友好的纯文本。
配置 Schema
通过 config_schema 定义插件的可配置项,用户在控制台可视化配置:
[
{ "key": "api_key", "label": "API Key", "type": "password", "placeholder": "输入你的 API Key" },
{ "key": "enabled", "label": "启用功能", "type": "boolean", "default": true },
{ "key": "mode", "label": "模式", "type": "select",
"options": [{ "value": "auto", "label": "自动" }, { "value": "manual", "label": "手动" }],
"default": "auto" },
{ "key": "prompt", "label": "系统提示词", "type": "textarea", "default": "" }
]
支持的 type:text、password、number、boolean、textarea、select
用户配置的值通过 config 参数传入 handler。password 类型的值会加密存储。
优先级体系
插件按 priority 升序执行(数字越小越先执行)。返回 { handled: true } 可中断后续插件。
| 插件类型 | 默认优先级 | 说明 |
|---|---|---|
| bot-menu(菜单) | 5 | 最先执行,处理"菜单"命令 |
| message-filter(过滤) | 10 | 过滤敏感词等 |
| keyword-reply(关键词) | 30 | 关键词自动回复 |
| auto-greeting(问候) | 40 | 自动问候新用户 |
| webhook-forward(转发) | 50 | 转发消息到 Webhook |
| 游戏类插件 | 80 | 大富翁等互动游戏 |
| ai-chat(AI 聊天) | 100 | 兜底,其他插件不处理时由 AI 回复 |
💡 你的插件优先级建议设在 60-90 之间(在 Webhook 之后、AI 之前)。
代码安全规范
- 插件在沙箱中运行,禁止访问
fs、process、require、child_process - 可以使用
fetch调用外部 API - 禁止修改全局变量或原型链
- 不得收集或传输用户隐私数据
- 代码中不得包含混淆或加密内容
- 违规插件将被下架并封禁开发者账号
🎤 语音能力
📥 接收语音 + 语音转文字
- 用户发送语音,SDK 自动识别内容,转写结果存入
msg.voice.text - 平台自动下载语音文件,通过
mediaBuffer传给插件 - 语音时长通过
msg.voice.playtime获取(毫秒)
export async function handler({ bot, msg, mediaBuffer }) {
if (msg.type === 'voice') {
const text = msg.voice?.text; // SDK 自动转写
const duration = msg.voice?.playtime; // 毫秒
if (text) {
await bot.reply(msg, `🎤 你说了: "${text}" (${Math.round(duration/1000)}秒)`);
} else {
await bot.reply(msg, '🎤 语音识别失败');
}
return { handled: true };
}
return { handled: false };
}
📤 发送语音
// 发送语音(SILK 格式)
await bot.sendVoice(msg.from, voiceBuffer, msg.contextToken);
// 带参数
await bot.sendVoice(msg.from, voiceBuffer, msg.contextToken, {
encodeType: 6, // SILK 格式(默认)
sampleRate: 24000, // 采样率
playtime: 3000, // 时长(毫秒)
});
👁️ AI 看图
用户发送图片时,mediaBuffer 包含已下载的图片数据。你可以转 base64 发给视觉模型:
export async function handler({ bot, msg, config, mediaBuffer }) {
if (msg.type === 'image' && mediaBuffer) {
const b64 = mediaBuffer.toString('base64');
// 调用视觉模型(OpenAI 格式)
const resp = await fetch(config.api_url + '/chat/completions', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${config.api_key}` },
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: [{
role: 'user',
content: [
{ type: 'image_url', image_url: { url: `data:image/jpeg;base64,${b64}` } },
{ type: 'text', text: msg.text || '描述这张图片' }
]
}]
})
});
const data = await resp.json();
await bot.reply(msg, data.choices[0].message.content);
return { handled: true };
}
return { handled: false };
}
💡 内置 AI 聊天插件已自动实现看图功能,需要支持视觉的模型(如 gpt-4o-mini)。
💬 引用消息
用户引用(长按回复)某条消息时:
// quotedMessage 结构
{
title: "被引用消息的发送者名称",
item: { /* 原始消息项 */ },
text: "发送者 | 被引用的文字内容" // SDK v1.1.0 提取
}
// msg.textWithQuote — SDK 自动合并
"[引用: 被引用内容]\n用户实际发送的文字"
export async function handler({ bot, msg, quotedMessage }) {
if (quotedMessage) {
const quoted = quotedMessage.text || quotedMessage.title || '';
await bot.reply(msg, `你引用了: "${quoted}"\n你说: ${msg.text}`);
return { handled: true };
}
return { handled: false };
}
📁 媒体处理
平台自动下载所有媒体消息,你可以直接使用:
export async function handler({ bot, msg, mediaBuffer }) {
switch (msg.type) {
case 'image':
if (mediaBuffer) {
const sizeKB = Math.round(mediaBuffer.length / 1024);
const w = msg.image?.thumb_width;
const h = msg.image?.thumb_height;
await bot.reply(msg, `📸 图片 ${w}x${h}, ${sizeKB}KB`);
}
return { handled: true };
case 'voice':
await bot.reply(msg, `🎤 语音 ${Math.round(msg.voice?.playtime/1000)}秒: ${msg.voice?.text || '(无法识别)'}`);
return { handled: true };
case 'file':
await bot.reply(msg, `📎 文件: ${msg.file?.file_name} (${msg.file?.len} bytes)`);
return { handled: true };
case 'video':
await bot.reply(msg, `🎬 视频 ${msg.video?.play_length}秒`);
return { handled: true };
}
return { handled: false };
}
插件存储
每个用户 5MB 存储空间。每个插件的数据按 plugin_slug 隔离。
- Key-Value 存储,Value 支持字符串或 JSON 对象(自动序列化)
- 单个 Value 最大 1MB
- 卸载插件不会自动删除数据
Storage API
// 读取(不存在返回 null)
const data = await storage.get('game_state');
// 写入(对象自动 JSON 序列化)
await storage.set('game_state', { level: 3, score: 1500 });
// 删除
await storage.delete('game_state');
// 列出所有 key
const keys = await storage.keys();
// 清空该插件的所有数据
await storage.clear();
示例:游戏存档
export async function handler({ bot, msg, storage }) {
if (msg.text === '开始游戏') {
let save = await storage.get('save');
if (save) {
await bot.reply(msg, `欢迎回来!等级: ${save.level}, 金币: ${save.gold}`);
} else {
save = { level: 1, gold: 100 };
await storage.set('save', save);
await bot.reply(msg, '新游戏开始!初始金币: 100');
}
return { handled: true };
}
return { handled: false };
}
示例:AI 对话历史
export async function handler({ bot, msg, storage }) {
let history = await storage.get('chat_history') || [];
history.push({ role: 'user', content: msg.text });
// 调用 AI...
const reply = await callAI(history.slice(-20));
history.push({ role: 'assistant', content: reply });
if (history.length > 50) history = history.slice(-50);
await storage.set('chat_history', history);
await bot.reply(msg, reply);
return { handled: true };
}
提交插件
在插件库页面点击「提交插件」,或通过 API 提交:
/api/plugins/submit
需要登录。提交后需管理员审核通过才会在插件库显示。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| slug | string | 是 | 唯一标识,小写字母/数字/连字符 |
| name | string | 是 | 插件名称 |
| description | string | 否 | 简短描述 |
| icon | string | 否 | Emoji 图标,默认 🔌 |
| category | string | 否 | message / command / scheduled / utility |
| version | string | 否 | 版本号,默认 1.0.0 |
| config_schema | array | 否 | 配置项定义(见上方) |
| code | string | 否 | 插件代码 |
| readme | string | 否 | 详细说明(Markdown) |
| repository_url | string | 否 | 源码仓库地址 |
升级插件
/api/plugins/my/:id
更新自己提交的插件。两种模式:
- 修改描述/图标/README/源码地址 — 不影响发布状态
- 修改 code 或 version — 重置为待审核,需重新审核
/api/plugins/my
查看自己提交的所有插件(含审核中和已发布)。
免费模型
/api/plugins/free-models
获取平台提供的免费 AI 模型列表。无需认证。插件开发者可用此接口在配置中提供模型选择。
// 响应
[
{ "id": "gpt-4o-mini", "provider": "AI7Bot" },
{ "id": "claude-haiku-4-5", "provider": "AI7Bot" },
...
]
/api/plugins/ai-models
用自己的 API Key 获取可用模型列表(需登录)。
{ "api_base_url": "https://api.example.com/v1", "api_key": "sk-xxx" }
// 响应: { "models": ["gpt-4o", "gpt-4o-mini", ...] }
完整示例
天气查询插件
export const slug = 'weather-query';
export const name = '天气查询';
export const description = '发送"天气 城市名"查询天气';
export const icon = '🌤️';
export const config_schema = [];
export async function handler({ bot, msg }) {
if (msg.type !== 'text') return { handled: false };
const match = msg.text.match(/^天气\s+(.+)/);
if (!match) return { handled: false };
const city = match[1].trim();
try {
const resp = await fetch(`https://wttr.in/${encodeURIComponent(city)}?format=3`);
const text = await resp.text();
await bot.reply(msg, `🌤️ ${text.trim()}`);
} catch {
await bot.reply(msg, '❌ 查询失败,请稍后再试');
}
return { handled: true };
}
图片尺寸检测插件
export const slug = 'image-info';
export const name = '图片信息';
export const description = '收到图片自动回复尺寸和大小';
export const icon = '📐';
export async function handler({ bot, msg, mediaBuffer }) {
if (msg.type !== 'image' || !mediaBuffer) return { handled: false };
const w = msg.image?.thumb_width || '?';
const h = msg.image?.thumb_height || '?';
const sizeKB = Math.round(mediaBuffer.length / 1024);
await bot.reply(msg, `📐 图片信息\n尺寸: ${w} × ${h}\n大小: ${sizeKB} KB`);
return { handled: true };
}
语音复读机插件
export const slug = 'voice-echo';
export const name = '语音复读机';
export const description = '收到语音自动回复转写文字';
export const icon = '🦜';
export async function handler({ bot, msg }) {
if (msg.type !== 'voice') return { handled: false };
const text = msg.voice?.text;
const sec = Math.round((msg.voice?.playtime || 0) / 1000);
if (text) {
await bot.reply(msg, `🦜 你说了 (${sec}秒):\n"${text}"`);
} else {
await bot.reply(msg, `🦜 收到 ${sec} 秒语音,但没听清~`);
}
return { handled: true };
}
带存储的计数器插件
export const slug = 'msg-counter';
export const name = '消息计数器';
export const description = '统计你发了多少条消息';
export const icon = '🔢';
export async function handler({ bot, msg, storage }) {
if (msg.text === '统计') {
const count = await storage.get('count') || 0;
await bot.reply(msg, `📊 你一共发了 ${count} 条消息`);
return { handled: true };
}
// 每条消息 +1(不拦截,让后续插件继续处理)
const count = (await storage.get('count') || 0) + 1;
await storage.set('count', count);
return { handled: false };
}