Skip to content

中间件

中间件(Middleware) 是 Kotori 中另一种监听消息事件的语法糖,与指令系统类似,它也是对 on_message 事件的再处理与封装。中间件的主要用途是提前判断或者过滤掉不必要的消息事件,这样后续的指令和正则表达式等位于下游的设施也不会被这些消息事件触发,从而提高效率。

中间件的工作原理与 Express 等后端框架中的中间件概念基本一致。每次收到消息时,Kotori 会依次执行所有已注册的中间件,只有当所有中间件都通过时,该消息事件才会真正被处理。

注册中间件

通过 ctx.midware() 注册一个中间件,该方法接受两个参数:

  1. 中间件回调函数
  2. 可选的中间件优先级,默认为 50

优先级数字越小(但不能为负数)则优先级越高,如果两个中间件的优先级相同,则按照注册顺序执行,先注册的中间件会先执行。

WARNING

如无特殊需求建议请勿更改优先级,否则可能会导致一些意料之外的问题。

typescript
ctx.midware((next, session) => {
  // 中间件逻辑...
  next(); // 通过此中间件
}, 80); // 优先级为 80

中间件回调函数接收两个参数:

  1. next 函数,调用它将执行下一个中间件
  2. session 对象,包含当前消息事件的上下文信息

在中间件内部,你可以根据消息内容或发送者等信息决定是否调用 next() 函数。如果调用了 next() 则通过此中间件,否则此消息事件将被过滤掉,不再执行后续的中间件和其他处理逻辑。

移除中间件

ctx.midware() 方法的返回值是一个可以用于移除该中间件的函数。

typescript
const dispose = ctx.midware((next) => {
  // ...
  next();
});

// 移除中间件
dispose();

使用示例

基本使用

typescript
ctx.midware((next, session) => {
  console.log('收到一条消息');
  next();
});

ctx.midware((next, session) => {
  console.log('这是另一个中间件');
  session.quick('这条消息将被发送');
  next();
});

上述代码注册了两个中间件,每当收到一条消息时,它们都会被执行。第一个中间件只打印日志,第二个则先打印日志,然后发送一条消息。由于两个中间件都调用了 next() 函数,因此该消息事件会继续被处理。

过滤消息

typescript
ctx.midware((next, session) => {
  if (session.message !== 'hello') return;
  next();
});

ctx.command('hello').action(() => 'Hello World!');

这个示例中的中间件会过滤掉消息内容不是「hello」的消息事件。只有当消息是「hello」时,中间件才会调用 next()让该消息事件继续被处理。通过使用中间件,我们可以在消息流经 Kotori 的各个环节进行拦截和处理,实现更加灵活和可控的消息处理逻辑。

限制命令使用频率

WARNING

以下内容由 Claude3 生成,不保证可用性。

有时我们需要限制某些命令的使用频率,以防止被滥用。这时可以使用中间件来实现这一功能。

typescript
// 用于存储命令使用记录
const cmdUsageRecord = new Map();

ctx.midware((next, session) => {
  // 检查是否为命令消息
  if (!session.message.startsWith('/')) {
    next(); // 非命令消息,直接通过
    return;
  }

  const cmd = session.message.slice(1); // 获取命令名
  const userId = session.userId; // 获取发送者ID

  // 如果此命令无使用记录,则新建一个记录
  if (!cmdUsageRecord.has(cmd)) {
    cmdUsageRecord.set(cmd, new Map());
  }
  const userRecord = cmdUsageRecord.get(cmd);

  // 获取该用户对此命令的最后使用时间
  const lastUsedAt = userRecord.get(userId) || 0;

  // 计算距离最后一次使用的时间间隔(单位:秒)
  constInterval = (Date.now() - lastUsedAt) / 1000;

  // 如果间隔小于10秒,则拒绝执行该命令
  if (Interval < 10) {
    session.quick('命令使用过于频繁,请稍后再试');
    return;
  }

  // 更新该用户对此命令的最后使用时间
  userRecord.set(userId, Date.now());

  next(); // 通过中间件
}, 10); // 设置较高优先级

上述代码定义了一个中间件,用于限制命令的使用频率。具体逻辑如下:

  1. 首先检查收到的消息是否以 / 开头,如果不是则直接调用 next()通过该中间件。
  2. 获取命令名和发送者 ID。
  3. 检查是否存在该命令的使用记录,如果没有则新建一个记录。
  4. 获取该用户对此命令的最后使用时间,如果不存在则认为是第一次使用,最后使用时间设为 0。
  5. 计算距离上次使用的时间间隔(单位为秒)。
  6. 如果时间间隔小于 10 秒,则拒绝执行该命令,发送 '命令使用过于频繁,请稍后再试'
  7. 如果时间间隔大于等于 10 秒,则更新该用户对此命令的最后使用时间,并调用 next()通过该中间件。

该中间件的优先级设为 10,这是为了让它能够比大多数命令优先执行。我们使用 Map 来存储命令使用记录,外层 Map 的键为命令名,值为另一个 Map,内层 Map 的键为用户 ID,值为该用户最后一次使用该命令的时间戳。

通过这种方式,我们可以精确地控制每个用户对每个命令的使用频率,并且只对命令消息生效,不会影响到其他普通消息的处理。需要注意的是,这个示例使用了内存来存储命令使用记录,因此在重启 Bot 后记录会被清空。在实际应用中,你可以将记录持久化存储到数据库中。