Skip to content

SaltifyDev/milkroute

Repository files navigation

Milkroute

轻量级 Milky 命令路由库

创建 Router

你可以通过创建一个 Router 实例来开始使用 Milkroute:

import { Router, IncomingMessage } from "@saltify/milkroute";

const router = new Router<{
  raw: IncomingMessage;
}>();

Router 的构造器接受一个类型参数,定义了路由处理函数的上下文类型。在上面的例子中,我们定义了一个包含 raw 字段的上下文,该字段是 Milky 的 IncomingMessage 类型。上下文的字段可以在后续的处理函数中访问。

定义指令

可以使用 router.command 方法来定义一个指令:

import { param } from "@saltify/milkroute";

router.command(
  "foo",
  {
    bar: param.str(),
    baz: param.num(),
  },
  async (ctx, { bar, baz }) => {
    // Do something with ctx, bar, and baz
  },
);

在上面的例子中,我们定义了一个名为 foo 的指令,它接受两个参数:一个字符串参数 bar 和一个数字参数 baz。pattern 是一个对象,key 是参数名,value 是参数类型;Milkroute 会按对象属性的插入顺序解析输入,并把解析结果作为同名对象传给处理函数。我们使用解构赋值来获取 barbaz 的值,并且二者经过 TypeScript 的类型推断,具有正确的类型 stringnumber

认识参数类型

Milkroute 提供了多种参数类型来满足不同的需求:

  • param.literal(literal): 匹配一个字面量参数,只有当输入与指定的字面量完全匹配时才会成功。
  • param.str(): 匹配一个字符串参数。
  • param.num(): 匹配一个数字参数。
  • param.greedy(): 匹配一个贪婪参数,捕获输入的剩余部分。这个参数只能作为 pattern 的最后一个属性,并且只匹配后续输入只剩下纯文本的情况。
  • param.union(...literals): 匹配一个联合参数,输入必须与提供的字面量列表中的一个完全匹配。
  • param.segment(type): 匹配一个给定类型的非文本消息段参数,例如 mentionimage。这在解析一些富文本输入时非常有用,例如,一些指令可能会接受一张图片作为参数。

定义指令组

有时我们会希望将相关的指令组织在一起,作为同一个指令的不同子命令。我们可以使用 router.group 方法来定义一个指令组:

router
  .group("parent")
  .command("child1", { arg1: param.str() }, async (ctx, { arg1 }) => {
    // Handle child1 command
  })
  .command("child2", { arg2: param.num() }, async (ctx, { arg2 }) => {
    // Handle child2 command
  });

这样,我们就定义了一个名为 parent 的指令组,其中包含两个子命令 child1child2。每个子命令都有自己的参数和处理函数。当用户输入 parent child1 <arg1> 时,child1 的处理函数将被调用;当用户输入 parent child2 <arg2> 时,child2 的处理函数将被调用。

指令组可以嵌套:

const parentGroup = router.group("parent");
parentGroup
  .command("child1", { arg1: param.str() }, async (ctx, { arg1 }) => {
    // Handle child1 command
  })
  .command("child2", { arg2: param.num() }, async (ctx, { arg2 }) => {
    // Handle child2 command
  });

const subGroup = parentGroup.group("sub");
subGroup.command(
  "child3",
  { arg3: param.union("option1", "option2") },
  async (ctx, { arg3 }) => {
    // Handle child3 command
  },
);

这个 router 可以接受以下的输入:

  • parent child1 <arg1> - 调用 child1 的处理函数
  • parent child2 <arg2> - 调用 child2 的处理函数
  • parent sub child3 <arg3> - 调用 child3 的处理函数

定义 Raw Pattern

上面介绍的情况都是基于一个假设,即每种指令都只有一种可能的参数形式,例如 foo <bar: string> <baz: number>。而在实际应用中,我们可能会遇到一些更复杂的情况,这时需要引入 Raw Pattern。

命令重载

在很多情况下,我们可能希望命令能够重载,也就是说同一个指令可以接受不同形式的参数。以《Minecraft》中的 tp 命令为例,它可以直接接受三个坐标参数,也可以接受一个玩家名和三个坐标参数,或者两个玩家名参数:

tp 1 2 3            // 将玩家传送到坐标 (1, 2, 3)
tp Steve 1 2 3      // 将玩家 Steve 传送到坐标 (1, 2, 3)
tp Steve Alex       // 将玩家 Steve 传送到玩家 Alex 的位置

在 Milkroute 中,我们可以定义一个指令组,并为每个子命令定义一个 Raw Pattern 来处理这些不同的参数形式:

router
  .group("tp")
  .rawPattern(
    "self to coords",
    {
      x: param.num(),
      y: param.num(),
      z: param.num(),
    },
    async (ctx, { x, y, z }) => {
      // Handle tp with coordinates only
    },
  )
  .rawPattern(
    "player to coords",
    {
      player: param.str(),
      x: param.num(),
      y: param.num(),
      z: param.num(),
    },
    async (ctx, { player, x, y, z }) => {
      // Handle tp with player and coordinates
    },
  )
  .rawPattern(
    "player to player",
    {
      player1: param.str(),
      player2: param.str(),
    },
    async (ctx, { player1, player2 }) => {
      // Handle tp with player to player
    },
  );

不由文本开头的指令

在一些情况下,我们可能希望指令能够以非文本的消息段开头,例如在用户回复一条消息的同时触发一条指令。这时也可以定义 raw pattern:

router.rawPattern(
  "reply with text",
  {
    reply: param.segment("reply"),
    text: param.greedy(),
  },
  async (ctx, { reply, text }) => {
    console.log(
      `The user replied to ${reply.data.message_seq} and sent the text: ${text}`,
    );
  },
);

定义过滤器

有时候我们可能希望某些指令只在特定条件下被触发,例如只有管理员才能使用某个指令。我们可以使用 router.filter,它会返回一个新的 Router 实例,只有满足过滤条件的输入才能触发该实例下定义的指令:

const adminRouter = router.filter(
  (ctx) =>
    ctx.raw.message_scene === "group" && ctx.raw.group_member.role !== "member",
);

adminRouter.command("admincmd", { arg: param.str() }, async (ctx, { arg }) => {
  // This command can only be triggered by group owner and administrators
});

在上面的例子中,我们创建了一个新的 Router 实例 adminRouter,它只会在用户是群主或管理员时触发指令。我们在 adminRouter 上定义了一个指令 admincmd,只有满足过滤条件的输入才能触发这个指令。

触发指令

在组装好 Router 之后,我们可以使用 router.emit 来传入上下文和消息段,从而触发相应的指令:

// you got a message somewhere
router.emit({ raw: message }, message.segments);

About

轻量级 Milky 命令路由库

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors