Version: 2.0.0

事件响应器进阶

指南深入中,我们已经介绍了事件响应器的基本用法以及响应规则、权限控制等功能。在这一节中,我们将介绍事件响应器的组成,内置的响应规则,与第三方响应规则拓展。

事件响应器组成

事件响应器类型

事件响应器类型 type 即是该响应器所要响应的事件类型,只有在接收到的事件类型与该响应器的类型相同时,才会触发该响应器。如果类型为空字符串 "",则响应器将会响应所有类型的事件。事件响应器类型的检查在所有其他检查(权限控制、响应规则)之前进行。

NoneBot 内置了四种常用事件类型:meta_eventmessagenoticerequest,分别对应元事件、消息、通知、请求。通常情况下,协议适配器会将事件合理地分类至这四种类型中。如果有其他类型的事件需要响应,可以自行定义新的类型。

事件触发权限

事件触发权限 permission 是一个 Permission 对象,这在权限控制一节中已经介绍过。事件触发权限会在事件响应器的类型检查通过后进行检查,如果权限检查通过,则执行响应规则检查。

事件响应规则

事件响应规则 rule 是一个 Rule 对象,这在响应规则一节中已经介绍过。事件响应器的响应规则会在事件响应器的权限检查通过后进行匹配,如果响应规则检查通过,则触发该响应器。

响应优先级

响应优先级 priority 是一个正整数,用于指定响应器的优先级。响应器的优先级越小,越先被触发。如果响应器的优先级相同,则按照响应器的注册顺序进行触发。

阻断

阻断 block 是一个布尔值,用于指定响应器是否阻断事件的传播。如果阻断为 True,则在该响应器被触发后,事件将不会再传播给其他下一优先级的响应器。

NoneBot 内置的事件响应器中,所有非 command 规则的 message 类型的事件响应器都会阻断事件传递,其他则不会。

在部分情况中,可以使用 stop_propagation 方法动态阻止事件传播,该方法需要 handler 在参数中获取 matcher 实例后调用方法。

有效期

事件响应器的有效期分为 tempexpire_timetemp 是一个布尔值,用于指定响应器是否为临时响应器。如果为 True,则该响应器在被触发后会被自动销毁。expire_time 是一个 datetime 对象,用于指定响应器的过期时间。如果 expire_time 不为 None,则在该时间点后,该响应器会被自动销毁。

默认状态

事件响应器的默认状态 default_state 是一个 dict 对象,用于指定响应器的默认状态。在响应器被触发时,响应器将会初始化默认状态然后开始执行事件处理流程。

基本辅助函数

NoneBot 为四种类型的事件响应器提供了五个基本的辅助函数:

  • on:创建任何类型的事件响应器。
  • on_metaevent:创建元事件响应器。
  • on_message:创建消息事件响应器。
  • on_request:创建请求事件响应器。
  • on_notice:创建通知事件响应器。

除了 on 函数具有一个 type 参数外,其余参数均相同:

  • rule:响应规则,可以是 Rule 对象或者 RuleChecker 函数。
  • permission:事件触发权限,可以是 Permission 对象或者 PermissionChecker 函数。
  • handlers:事件处理函数列表。
  • temp:是否为临时响应器。
  • expire_time:响应器的过期时间。
  • priority:响应器的优先级。
  • block:是否阻断事件传播。
  • state:响应器的默认状态。

在消息类型的事件响应器的基础上,NoneBot 还内置了一些常用的响应规则,并结合为辅助函数来方便我们快速创建指定功能的响应器。下面我们逐个介绍。

内置响应规则

startswith

startswith 响应规则用于匹配消息纯文本部分的开头是否与指定字符串(或一系列字符串)相同。可选参数 ignorecase 用于指定是否忽略大小写,默认为 False

例如,我们可以创建一个匹配消息开头为 ! 或者 / 的规则:

from nonebot.rule import startswith

rule = startswith(("!", "/"), ignorecase=False)

也可以直接使用辅助函数新建一个响应器:

from nonebot import on_startswith

matcher = on_startswith(("!", "/"), ignorecase=False)

endswith

endswith 响应规则用于匹配消息纯文本部分的结尾是否与指定字符串(或一系列字符串)相同。可选参数 ignorecase 用于指定是否忽略大小写,默认为 False

例如,我们可以创建一个匹配消息结尾为 . 或者 的规则:

from nonebot.rule import endswith

rule = endswith((".", "。"), ignorecase=False)

也可以直接使用辅助函数新建一个响应器:

from nonebot import on_endswith

matcher = on_endswith((".", "。"), ignorecase=False)

fullmatch

fullmatch 响应规则用于匹配消息纯文本部分是否与指定字符串(或一系列字符串)完全相同。可选参数 ignorecase 用于指定是否忽略大小写,默认为 False

例如,我们可以创建一个匹配消息为 ping 或者 pong 的规则:

from nonebot.rule import fullmatch

rule = fullmatch(("ping", "pong"), ignorecase=False)

也可以直接使用辅助函数新建一个响应器:

from nonebot import on_fullmatch

matcher = on_fullmatch(("ping", "pong"), ignorecase=False)

keyword

keyword 响应规则用于匹配消息纯文本部分是否包含指定字符串(或一系列字符串)。

例如,我们可以创建一个匹配消息中包含 hello 或者 hi 的规则:

from nonebot.rule import keyword

rule = keyword("hello", "hi")

也可以直接使用辅助函数新建一个响应器:

from nonebot import on_keyword

matcher = on_keyword("hello", "hi")

command

command 是最常用的响应规则,它用于匹配消息是否为命令。它会根据配置中的 Command Start 和 Command Separator 来判断消息是否为命令。

例如,当我们配置了 Command Start/Command Separator. 时:

from nonebot.rule import command

# 匹配 "/help" 或者 "/帮助" 开头的消息
rule = command("help", "帮助")
# 匹配 "/help.cmd" 开头的消息
rule = command(("help", "cmd"))

也可以直接使用辅助函数新建一个响应器:

from nonebot import on_command

matcher = on_command("help", aliases={"帮助"})

此外,command 响应规则默认允许消息命令与参数间不加空格,如果需要严格匹配命令与参数间的空白符,可以使用 command 函数的 force_whitespace 参数。force_whitespace 参数可以是 bool 类型或者具体的字符串,默认为 False。如果为 True,则命令与参数间必须有任意个数的空白符;如果为字符串,则命令与参数间必须有且与给定字符串一致的空白符。

rule = command("help", force_whitespace=True)
rule = command("help", force_whitespace=" ")

命令解析后的结果可以通过 CommandRawCommandCommandArgCommandStartCommandWhitespace 依赖注入获取。

shell_command

shell_command 响应规则用于匹配类 shell 命令形式的消息。它首先与 command 响应规则一样进行命令匹配,如果匹配成功,则会进行进一步的参数解析。参数解析采用 argparse 标准库进行,在此基础上添加了消息序列 Message 支持。

例如,我们可以创建一个匹配 /cmd 命令并且带有 -v 选项与默认 -h 帮助选项的规则:

from nonebot.rule import shell_command, ArgumentParser

parser = ArgumentParser()
parser.add_argument("-v", "--verbose", action="store_true")

rule = shell_command("cmd", parser=parser)

更多关于 argparse 的使用方法请参考 argparse 文档。我们也可以选择不提供 parser 参数,这样 shell_command 将不会解析参数,但会提供参数列表 argv

直接使用辅助函数新建一个响应器:

from nonebot import on_shell_command
from nonebot.rule import ArgumentParser

parser = ArgumentParser()
parser.add_argument("-v", "--verbose", action="store_true")

matcher = on_shell_command("cmd", parser=parser)

参数解析后的结果可以通过 ShellCommandArgvShellCommandArgs 依赖注入获取。

regex

regex 响应规则用于匹配消息是否与指定正则表达式匹配。

提示

正则表达式匹配使用 search 而非 match,如需从头匹配请使用 r"^xxx" 模式来确保匹配开头。

例如,我们可以创建一个匹配消息中包含字母并且忽略大小写的规则:

from nonebot.rule import regex

rule = regex(r"[a-z]+", flags=re.IGNORECASE)

也可以直接使用辅助函数新建一个响应器:

from nonebot import on_regex

matcher = on_regex(r"[a-z]+", flags=re.IGNORECASE)

正则匹配后的结果可以通过 RegexStrRegexGroupRegexDict 依赖注入获取。

to_me

to_me 响应规则用于匹配事件是否与机器人相关。

例如:

from nonebot.rule import to_me

rule = to_me()

is_type

is_type 响应规则用于匹配事件类型是否为指定类型(或者一系列类型)。

例如,我们可以创建一个匹配 OneBot v11 私聊和群聊消息事件的规则:

from nonebot.rule import is_type
from nonebot.adapters.onebot.v11 import PrivateMessageEvent, GroupMessageEvent

rule = is_type(PrivateMessageEvent, GroupMessageEvent)

响应器组

为了更方便的管理一系列功能相近的响应器,NoneBot 提供了两种响应器组,它们可以帮助我们进行响应器的统一管理。

CommandGroup

CommandGroup 可以用于管理一系列具有相同前置命令的子命令响应器。

例如,我们创建 /cmd/cmd.sub/cmd.help 三个命令,他们具有相同的优先级:

from nonebot import CommandGroup

group = CommandGroup("cmd", priority=10)

cmd = group.command(tuple())
sub_cmd = group.command("sub")
help_cmd = group.command("help")

MatcherGroup

MatcherGroup 可以用于管理一系列具有相同属性的响应器。

例如,我们创建一个具有相同响应规则的响应器组:

from nonebot.rule import to_me
from nonebot import MatcherGroup

group = MatcherGroup(rule=to_me())

matcher1 = group.on_message()
matcher2 = group.on_message()

第三方响应规则

Alconna

nonebot-plugin-alconna 是一类提供了拓展响应规则的插件。 该插件使用 Alconna 作为命令解析器, 是一个简单、灵活、高效的命令参数解析器, 并且不局限于解析命令式字符串。

特点包括:

  • 高效
  • 直观的命令组件创建方式
  • 强大的类型解析与类型转换功能
  • 自定义的帮助信息格式
  • 多语言支持
  • 易用的快捷命令创建与使用
  • 可创建命令补全会话, 以实现多轮连续的补全提示
  • 可嵌套的多级子命令
  • 正则匹配支持

该插件提供了一类新的事件响应器辅助函数 on_alconna,以及 AlconnaResult 等依赖注入函数。

同时,基于 Annotated 支持, 添加了两类注解 AlcMatchesAlcResult

该插件还可以通过 handle(parameterless) 来控制一个具体的响应函数是否在不满足条件时跳过响应:

  • pip.handle([Check(assign("add.name", "nb"))]) 表示仅在命令为 role-group add 并且 name 为 nb 时响应
  • pip.handle([Check(assign("list"))]) 表示仅在命令为 role-group list 时响应
  • pip.handle([Check(assign("add"))]) 表示仅在命令为 role-group add 时响应

基于 Alconna 的特性,该插件同时提供了一系列便捷的消息段标注。 标注可用于在 Alconna 中匹配消息中除 text 外的其他消息段,也可用于快速创建各适配器下的消息段。所有标注位于 nonebot_plugin_alconna.adapters 中。

插件安装

nb plugin install nonebot-plugin-alconna

pip install nonebot-plugin-alconna

示例

from nonebot_plugin_alconna.adapters import At
from nonebot.adapters.onebot.v12 import Message
from nonebot_plugin_alconna.adapters.onebot12 import Image
from nonebot_plugin_alconna import AlconnaMatches, on_alconna
from nonebot.adapters.onebot.v12 import MessageSegment as Ob12MS
from arclet.alconna import Args, Option, Alconna, Arparma, MultiVar, Subcommand

alc = Alconna(
"role-group",
Subcommand(
"add",
Args["name", str],
Option("member", Args["target", MultiVar(At)]),
),
Option("list"),
)
rg = on_alconna(alc, auto_send_output=True)


@rg.handle()
async def _(result: Arparma = AlconnaMatches()):
if result.find("list"):
img = await gen_role_group_list_image()
await rg.finish(Message([Image(img)]))
if result.find("add"):
group = await create_role_group(result["add.name"])
if result.find("add.member"):
ats: tuple[Ob12MS, ...] = result["add.member.target"]
group.extend(member.data["user_id"] for member in ats)
await rg.finish("添加成功")

我们可以看到主要的两大组件:OptionSubcommand

Option 可以传入一组别名,如 Option("--foo|-F|--FOO|-f")Option("--foo", alias=["-F"]

Subcommand 则可以传入自己的 OptionSubcommand

他们拥有如下共同参数:

  • help_text: 传入该组件的帮助信息
  • dest: 被指定为解析完成时标注匹配结果的标识符,不传入时默认为选项或子命令的名称 (name)
  • requires: 一段指定顺序的字符串列表,作为唯一的前置序列与命令嵌套替换
  • default: 默认值,在该组件未被解析时使用使用该值替换。

其次使用了消息段标注,其中 At 属于通用标注,而 Image 属于 onebot12 适配器下的标注。

on_alconna 的所有参数如下:

  • command: Alconna | str: Alconna 命令
  • skip_for_unmatch: bool = True: 是否在命令不匹配时跳过该响应
  • auto_send_output: bool = False: 是否自动发送输出信息并跳过响应
  • output_converter: TConvert | None = None: 输出信息字符串转换为消息序列方法
  • aliases: set[str | tuple[str, ...]] | None = None: 命令别名, 作用类似于 on_command 中的 aliases
  • comp_config: CompConfig | None = None: 补全会话配置, 不传入则不启用补全会话

AlconnaMatches 是一个依赖注入函数,可注入 Alconna 命令解析结果。

参考

插件文档: 📦 这里

官方文档: 👉 指路

QQ 交流群: 🔗 链接

友链: 📚 文档