插件概述
插件用于将机器人的功能拆成独立模块。一个插件可以注册指令、监听 QQ 事件,也可以调用 QQ API。
Kokkoro 支持本地插件和 npm 插件。两类插件使用相同的 API,区别在于存放位置和安装方式。
本地插件
Kokkoro 将项目根目录 plugins 下的每个一级子文件夹识别为一个本地插件。使用 CLI 创建的本地插件采用以下结构:
plugins/
└── example/
├── src/
│ └── index.ts
└── package.jsonnpm 插件
发布到 npm 的插件可以通过 Bun 安装。在项目根目录中运行:
bun add kokkoro-plugin-hitokotoBun 会将插件记录在 package.json 的 dependencies 中。Kokkoro 会在下一次启动时自动加载该插件。
插件标识
每个插件都有一个唯一标识。插件包含 package.json 时,Kokkoro 使用其中的 name 字段作为插件标识。
本地插件没有 package.json,或该文件没有 name 字段时,Kokkoro 使用插件的文件夹名称作为标识。
使用 CLI 创建 example 插件时,生成的包名是 kokkoro-plugin-example。
两个插件的标识相同时,Kokkoro 会停止启动并报告重复的插件名称。
插件入口
每个插件都需要默认导出一个同步函数。这个函数是插件入口,类型为 PluginSetup:
import { useCommand } from '@kokkoro/core';
export default () => {
useCommand('/ping', () => 'pong');
};Kokkoro 将 useCommand() 和 useEvent() 这类注册插件功能的函数称为 Hook。示例中的 useCommand() 注册了 /ping 指令,用户发送该指令后,Kokkoro 会把处理函数返回的「pong」回复到当前会话。
useCommand() 和 useEvent() 只能在 PluginSetup 执行期间调用。调用语句可以直接写在函数体中,也可以写在它同步调用的其他函数中。PluginSetup 不能是异步函数。
useLogger() 用于获取插件日志记录器,useDispose() 用于注册模块资源的清理函数。它们必须在插件模块加载期间调用,通常放在模块顶层。不同阶段的执行顺序参阅 插件生命周期。
PluginSetup 的参数是当前挂载插件的 Bot。插件需要调用 QQ API 时,可以使用这个参数:
import { type Bot, useCommand } from '@kokkoro/core';
export default (bot: Bot) => {
useCommand('/机器人', async () => {
const info = await bot.getBotInfo();
return `我是 ${info.username}`;
});
};type Bot 只导入 TypeScript 类型,不会产生运行时代码。
同一个插件可以挂载到多个 Bot。Kokkoro 会为每个 Bot 分别执行一次 PluginSetup,每次执行时注册的 Hook 只属于对应的 Bot。共享资源和清理副作用的规则参阅 插件生命周期。
插件加载规则
Kokkoro 只检索 plugins 下的一级子文件夹。本地插件的 package.json 声明了 name 时,Kokkoro 会按照其中的 exports 等模块配置解析程序入口。没有 package.json,或该文件没有 name 字段时,Kokkoro 会按照 Bun 的模块解析规则 解析插件目录,并查找 index.ts 等入口文件。
npm 插件来自项目 package.json 的 dependencies,包名需要以 kokkoro-plugin- 开头。Kokkoro 不会遍历 node_modules,也不会加载 devDependencies 中的插件。
本地插件先于 npm 插件加载。本地插件按照文件夹路径排序,npm 插件按照包名排序。
每个插件模块只会加载一次,模块导出的 PluginSetup 会分别挂载到配置中的每个 Bot。如果模块加载失败,或者没有有效的默认导出,Kokkoro 会记录该插件的错误并继续加载其他插件。插件挂载到某个 Bot 失败时,也不会影响其他 Bot。
Kokkoro 会在加载插件前读取本地插件和项目根目录的 package.json。其中任何一个文件不是有效的 JSON 时,插件检索会失败,Kokkoro 也会停止启动。
接下来可以继续扩展快速上手中创建的 example 插件,具体步骤见 编写第一个插件。