平台概述

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", ...] }

🤖 AI 代理

平台提供免费 AI 代理接口,插件可以直接调用,无需自备 API Key。

POST /api/plugins/ai-proxy

无需认证。兼容 OpenAI Chat Completions 格式。

请求参数

字段类型必填说明
modelstring模型名称,默认 gpt-4o-mini。可通过 /api/plugins/free-models 查看可用模型
messagesarray消息数组,格式 [{ role: "system"|"user"|"assistant", content: "..." }]。最多保留最近 10 条
max_tokensnumber最大生成 token 数,默认 800,上限 2000
temperaturenumber温度参数,默认 0.7,范围 0-1.5

system role 处理

支持发送 system role 消息。由于部分免费模型不原生支持 system role,代理会自动将其合并到第一条 user 消息中,确保兼容性。你只需正常传 system 消息,无需手动处理。

限制

  • 每 IP 每小时 60 次请求
  • 所有消息总长度不超过 4000 字符
  • 最多 10 条消息(超出自动截取最近 10 条)
  • max_tokens 上限 2000
  • 请求超时 30 秒
  • 超限返回 429 状态码

响应格式

// 成功
{
  "choices": [{
    "message": { "role": "assistant", "content": "AI 回复内容" }
  }]
}

// 错误
{ "error": "错误描述" }

示例:基础调用

// 简单调用(直接 user 消息)
async function askAI(prompt) {
  const resp = await fetch('https://botfox.ai/api/plugins/ai-proxy', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'gpt-4o-mini',
      messages: [{ role: 'user', content: prompt }],
      max_tokens: 500,
    }),
  });
  if (!resp.ok) return null;
  const data = await resp.json();
  return data.choices?.[0]?.message?.content || null;
}

示例:带 system 角色设定 + 多轮对话

// 带角色设定和对话历史(适合游戏/角色扮演类插件)
async function callAI(systemPrompt, userMsg, history) {
  const messages = [{ role: 'system', content: systemPrompt }];
  // 添加对话历史(assistant/user 交替)
  if (history?.length) {
    for (let i = 0; i < history.length; i++) {
      messages.push({
        role: i % 2 === 0 ? 'assistant' : 'user',
        content: history[i]
      });
    }
  }
  messages.push({ role: 'user', content: userMsg });

  const resp = await fetch('https://botfox.ai/api/plugins/ai-proxy', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'gpt-4o-mini',
      messages,
      max_tokens: 800,
      temperature: 0.85,
    }),
  });
  if (!resp.ok) return null;
  const data = await resp.json();
  return data.choices?.[0]?.message?.content || null;
}

// 使用示例
const reply = await callAI(
  '你是一个海盗船长,用海盗语气说话',
  '你好啊船长',
  ['哈哈,欢迎上船!', '这船要去哪?']
);

💡 建议:AI 调用需要 2-10 秒,可以先 bot.sendTyping() 显示"正在输入",完成后 bot.cancelTyping()

完整示例

天气查询插件

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 };
}