平台概述

BotFox 是一个微信机器人管理平台。用户在控制台绑定微信 Bot 后,通过安装插件来扩展 Bot 的能力。

作为插件开发者,你编写的代码会在用户的 Bot 收到消息时被调用。平台提供了丰富的上下文和工具,让你专注于业务逻辑。

插件能做什么

  • 接收并处理文字、图片、语音、视频、文件消息
  • 回复文字、发送图片/语音/视频/文件
  • 调用外部 API(AI 对话、天气查询、翻译等)
  • 持久化存储数据(游戏存档、对话历史、用户偏好)
  • 处理引用消息,获取语音转文字结果
  • 控制打字状态("对方正在输入")

能力边界

开发插件前请先了解平台的能力边界。

✅ 插件可以做的

接收所有类型消息文字、图片、语音、视频、文件,平台自动下载媒体
语音转文字SDK 自动识别,通过 msg.voice.text 获取
AI 看图收到图片时可获取 mediaBuffer,转 base64 发给视觉模型
引用消息通过 quotedMessagemsg.textWithQuote 获取引用内容
回复文字bot.reply(msg, text)
发送图片/语音/视频/文件bot.sendImage/sendVoice/sendVideo/sendFile
打字状态bot.sendTyping() / bot.cancelTyping()
调用外部 API可以使用 fetch(仅 HTTPS)
持久化存储每用户 5MB,storage.get/set/delete

❌ 插件不能做的

访问文件系统沙箱禁止 fsprocessrequirechild_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 };   // 不处理,交给下一个插件
}

提交流程

  1. 在插件库页面点击「提交插件」
  2. 填写名称、描述、代码
  3. 提交后等待管理员审核
  4. 审核通过后,所有用户都能在插件库看到并安装

插件上下文

插件 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)

字段类型说明
typestringtext | image | voice | file | video
textstring文本内容。语音消息时为语音转文字结果
textWithQuotestring?带引用上下文:[引用: xxx]\n原文
fromstring发送者用户 ID
tostring接收者 ID(Bot 自身)
timestampnumber消息时间戳(毫秒)
contextTokenstring回复用的上下文 token
messageIdnumberSDK 原始消息 ID
imageImageItem?{ thumb_width, thumb_height, mid_size, media }
voiceVoiceItem?{ playtime, text, media } — playtime 毫秒,text 语音转文字
fileFileItem?{ file_name, len, md5, media }
videoVideoItem?{ play_length, video_size, thumb_width, thumb_height, media }
quotedMessageobject?{ title, item, text } — text 是 SDK 提取的引用文本
rawobjectSDK 原始消息对象

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:textpasswordnumberbooleantextareaselect

用户配置的值通过 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 之前)。

代码安全规范

  • 插件在沙箱中运行,禁止访问 fsprocessrequirechild_process
  • 可以使用 fetch 调用外部 API
  • 禁止修改全局变量或原型链
  • 不得收集或传输用户隐私数据
  • 代码中不得包含混淆或加密内容
  • 违规插件将被下架并封禁开发者账号

🎤 语音能力

📥 接收语音 + 语音转文字

  1. 用户发送语音,SDK 自动识别内容,转写结果存入 msg.voice.text
  2. 平台自动下载语音文件,通过 mediaBuffer 传给插件
  3. 语音时长通过 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 提交:

POST /api/plugins/submit

需要登录。提交后需管理员审核通过才会在插件库显示。

字段类型必填说明
slugstring唯一标识,小写字母/数字/连字符
namestring插件名称
descriptionstring简短描述
iconstringEmoji 图标,默认 🔌
categorystringmessage / command / scheduled / utility
versionstring版本号,默认 1.0.0
config_schemaarray配置项定义(见上方)
codestring插件代码
readmestring详细说明(Markdown)
repository_urlstring源码仓库地址

升级插件

PUT /api/plugins/my/:id

更新自己提交的插件。两种模式:

  • 修改描述/图标/README/源码地址 — 不影响发布状态
  • 修改 code 或 version — 重置为待审核,需重新审核
GET /api/plugins/my

查看自己提交的所有插件(含审核中和已发布)。

免费模型

GET /api/plugins/free-models

获取平台提供的免费 AI 模型列表。无需认证。插件开发者可用此接口在配置中提供模型选择。

// 响应
[
  { "id": "gpt-4o-mini", "provider": "AI7Bot" },
  { "id": "claude-haiku-4-5", "provider": "AI7Bot" },
  ...
]
POST /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 };
}