- 后端
- 即时通讯
【免费下载链接】nonebot2
跨平台 Python 异步聊天机器人框架 / Asynchronous multi-platform chatbot framework written in Python
导读:
nonebot.consts是 NoneBot2 框架中一个"小而关键"的模块,它集中定义了事件处理过程中用于存取状态数据的全部常量键(key)。无论是Matcher.receive/got的多轮对话状态、pause/reject的暂停与重试机制,还是命令、Shell 命令、正则、前缀/后缀/关键字等各类响应规则(Rule)的触发结果,最终都以这些常量作为 key 写入事件响应器的state字典中。读完本文,你将掌握每个常量的取值、类型、语义、在源码中的写入与读取位置,以及如何通过nonebot.params中的依赖注入参数在插件中安全地消费这些状态数据。
nonebot.consts模块的全部源码位于 nonebot/consts.py,对应 2.4.3 版本的 API 文档为 website/versioned_docs/version-2.4.3/api/consts.md。本文以该文档列出的 24 个常量为骨架,逐组拆解其底层实现与典型用法。
一、模块定位:事件响应器状态(state)的"命名空间协议"
在 NoneBot2 中,每个事件响应器(Matcher)都持有一个state: T_State字典,用于在事件处理流程的各个阶段之间传递数据。nonebot.consts的全部常量,本质上就是这套state字典的预留键名协议:
- 所有以
_开头的键(如"_receive_{id}"、"_matched")都是内部保留键,插件自定义状态应避免与之冲突; - 不含
_前缀的键(如"command"、"command_arg")虽然也由框架写入,但语义上更接近"解析产物",同样不建议手动覆盖; - 常量声明使用
typing.Literal进行类型标注(见 nonebot/consts.py),让静态类型检查器能对state[key]的读写给出更精确的提示。
从源码结构看,这些常量按用途被划分为两大部分(nonebot/consts.py 中的注释也明确标注了used by Matcher与used by Rule):
| 分组 | 常量 | 使用方 |
|---|---|---|
| Matcher 交互状态 | RECEIVE_KEY、LAST_RECEIVE_KEY、ARG_KEY、REJECT_TARGET、REJECT_CACHE_TARGET、PAUSE_PROMPT_RESULT_KEY、REJECT_PROMPT_RESULT_KEY | nonebot/internal/matcher/matcher.py |
| Rule 触发结果 | PREFIX_KEY、CMD_KEY、RAW_CMD_KEY、CMD_ARG_KEY、CMD_START_KEY、CMD_WHITESPACE_KEY、SHELL_ARGS、SHELL_ARGV、REGEX_MATCHED、STARTSWITH_KEY、ENDSWITH_KEY、FULLMATCH_KEY、KEYWORD_KEY | nonebot/rule.py |
此外,模块末尾还定义了一个与事件处理无直接关系的平台判断常量WINDOWS = sys.platform.startswith("win") or (sys.platform == "cli" and os.name == "nt")(nonebot/consts.py),用于在 Windows 与 CLR(IronPython 等)环境下做系统级判断,它不属于本文讨论的事件处理常量范畴。
二、Matcher 交互状态常量:多轮对话与暂停/重试的存储协议
这一组常量直接支撑 NoneBot2 的多轮会话机制,全部由 nonebot/internal/matcher/matcher.py 读写。
2.1 RECEIVE_KEY 与 LAST_RECEIVE_KEY:接收事件的存取
RECEIVE_KEY: Literal["_receive_{id}"]——receive存储 key;LAST_RECEIVE_KEY: Literal["_last_receive"]——last_receive存储 key。
RECEIVE_KEY是带占位符{id}的模板,实际使用时通过format(id=id)生成具体键。在Matcher.receive()装饰器生成的依赖中(nonebot/internal/matcher/matcher.py):
async def _receive(event: Event, matcher: "Matcher") -> None: matcher.set_target(RECEIVE_KEY.format(id=id)) if matcher.get_target() == RECEIVE_KEY.format(id=id): matcher.set_receive(id, event) return if matcher.get_receive(id, ...) is not ...: return await matcher.reject()set_receive(id, event)将事件写入state[RECEIVE_KEY.format(id=id)],同时更新state[LAST_RECEIVE_KEY](nonebot/internal/matcher/matcher.py),因此LAST_RECEIVE_KEY永远指向最近一次接收的事件;get_receive/get_last_receive分别读取这两个键(nonebot/internal/matcher/matcher.py)。
典型应用:@Matcher.receive("id")配合依赖参数Received(id)与LastReceived()(见 nonebot/params.py),可实现"先接收一次消息、再继续处理"的流程。
2.2 ARG_KEY:got 参数的存储协议
ARG_KEY: Literal["{key}"]——arg存储 key,同样为带占位符模板。@Matcher.got(key)的依赖逻辑(nonebot/internal/matcher/matcher.py):
async def _key_getter(event: Event, matcher: "Matcher"): matcher.set_target(ARG_KEY.format(key=key)) if matcher.get_target() == ARG_KEY.format(key=key): matcher.set_arg(key, event.get_message()) return if matcher.get_arg(key, ...) is not ...: return await matcher.reject(prompt)set_arg/get_arg分别写入、读取state[ARG_KEY.format(key=key)](nonebot/internal/matcher/matcher.py)。插件侧可通过Arg(key)、ArgStr(key)、ArgPlainText(key)等依赖参数消费(见 nonebot/params.py),也可以直接用state[ARG_KEY.format(key=key)]手工读取。
2.3 REJECT_TARGET 与 REJECT_CACHE_TARGET:reject 目标定位
REJECT_TARGET: Literal["_current_target"]——当前reject目标存储 key;REJECT_CACHE_TARGET: Literal["_next_target"]——下一个reject目标存储 key。
这两个常量用于实现"reject后重新从头执行当前处理函数"的流程。set_target(target, cache=True)默认写入REJECT_CACHE_TARGET,cache=False时写入REJECT_TARGET(nonebot/internal/matcher/matcher.py)。当事件被拒绝后,resolve_reject()会把缓存目标提升为当前目标:
async def resolve_reject(self): handler = current_handler.get() self.remain_handlers.insert(0, handler) if REJECT_CACHE_TARGET in self.state: self.state[REJECT_TARGET] = self.state[REJECT_CACHE_TARGET](见 nonebot/internal/matcher/matcher.py)
这样get_target()就能准确判断"本次重试是针对哪个 receive/arg 目标",从而决定是直接继续还是重新等待用户输入。测试用例中也能看到matcher.set_target(RECEIVE_KEY.format(id="test"), cache=False)的用法(tests/test_param.py)。
2.4 PAUSE_PROMPT_RESULT_KEY 与 REJECT_PROMPT_RESULT_KEY:prompt 发送结果
PAUSE_PROMPT_RESULT_KEY: Literal["_pause_result"]——pauseprompt 发送结果存储 key;REJECT_PROMPT_RESULT_KEY: Literal["_reject_{key}_result"]——rejectprompt 发送结果存储 key。
当Matcher.pause(prompt)或Matcher.reject(prompt)携带提示消息时,send()的返回结果会被写入 state(nonebot/internal/matcher/matcher.py 与 nonebot/internal/matcher/matcher.py):
# pause 分支 if matcher is not None: matcher.state[PAUSE_PROMPT_RESULT_KEY] = result # reject 分支 key = REJECT_PROMPT_RESULT_KEY.format(key=key) if key is not None else None if prompt is not None: result = await cls.send(prompt, **kwargs) if key is not None and matcher: matcher.state[key] = result同样,reject_arg(key, prompt)与reject_receive(id, prompt)也会把发送结果写入REJECT_PROMPT_RESULT_KEY.format(key=arg_key)/format(key=receive_key)(nonebot/internal/matcher/matcher.py)。
插件侧可通过PausePromptResult()与ReceivePromptResult(id)依赖参数直接获取(nonebot/params.py),在后续处理中复用"提示消息是否成功送达"等发送结果(通常是适配器Bot.send的返回值,具体字段由各适配器决定)。
三、命令解析常量:PREFIX_KEY 及其五个子键
命令规则(on_command)在 nonebot/rule.py 的TrieRule.get_value中完成解析,解析结果统一存放在state[PREFIX_KEY]这个CMD_RESULT字典里。CMD_RESULT是一个TypedDict(nonebot/rule.py),其五个字段正好对应五个命令子键:
class CMD_RESULT(TypedDict): command: tuple[str, ...] | None # CMD_KEY raw_command: str | None # RAW_CMD_KEY command_arg: Message | None # CMD_ARG_KEY command_start: str | None # CMD_START_KEY command_whitespace: str | None # CMD_WHITESPACE_KEY各常量含义与写入位置:
| 常量 | 值 | 说明 | 写入位置 |
|---|---|---|---|
PREFIX_KEY | "_prefix" | 命令前缀存储 key(整个解析结果的容器) | nonebot/rule.py |
CMD_KEY | "command" | 命令元组存储 key,如("test",) | nonebot/rule.py |
RAW_CMD_KEY | "raw_command" | 命令文本存储 key,如"/test" | nonebot/rule.py |
CMD_ARG_KEY | "command_arg" | 命令参数存储 key(剩余消息段) | nonebot/rule.py |
CMD_START_KEY | "command_start" | 命令开头存储 key,如"/" | nonebot/rule.py |
CMD_WHITESPACE_KEY | "command_whitespace" | 命令与参数间空白符存储 key | nonebot/rule.py |
解析流程核心逻辑(nonebot/rule.py):
state[PREFIX_KEY] = prefix if event.get_type() != "message": return prefix message = event.get_message() message_seg: MessageSegment = message[0] if message_seg.is_text(): segment_text = str(message_seg).lstrip() if pf := cls.prefix.longest_prefix(segment_text): value: TRIE_VALUE = pf.value prefix[RAW_CMD_KEY] = pf.key prefix[CMD_START_KEY] = value.command_start prefix[CMD_KEY] = value.command ... prefix[CMD_ARG_KEY] = msg要点:
- 命令前缀通过
TrieRule.add_prefix注册进一个CharTrie(字符前缀树)(nonebot/rule.py),longest_prefix保证最长前缀优先匹配; - 非
message类型事件(如元事件、通知事件)不会触发命令解析,prefix保持全空字段; - 命令与参数之间的空白符会被单独截取存入
CMD_WHITESPACE_KEY(nonebot/rule.py),参数部分则重组为新的消息对象存入CMD_ARG_KEY。
插件侧对应的一组依赖参数位于 nonebot/params.py:Command()、RawCommand()、CommandArg()、CommandStart()、CommandWhitespace(),实现均为state[PREFIX_KEY][对应子键]的一行读取。
四、Shell 命令常量:SHELL_ARGS 与 SHELL_ARGV
Shell 命令规则(on_shell_command)在ShellCommandRule.__call__中完成解析(nonebot/rule.py),涉及两个常量:
SHELL_ARGS: Literal["_args"]——shell 命令 parse 后参数字典存储 key;SHELL_ARGV: Literal["_argv"]——shell 命令原始参数列表存储 key。
解析流程:
- 用
shlex.split把命令参数文本切分为原始参数列表,文本段切分、非文本段(如图片MessageSegment)原样保留,存入state[SHELL_ARGV]; - 若提供
ArgumentParser,则用parser.parse_args(state[SHELL_ARGV])解析,Namespace结果存入state[SHELL_ARGS];解析失败时(ArgumentError/ParserExit)存入的是ParserExit异常对象,且state[SHELL_ARGV]可能被置为None(nonebot/rule.py)。
这一点在测试中得到了完整覆盖(tests/test_rule.py):
- 无参数时
state[SHELL_ARGV] == []且SHELL_ARGS不存在; shlex切分失败时SHELL_ARGV is None;- 解析失败时
SHELL_ARGS为ParserExit,且status != 0(缺参/非法参数)或status == 0(如-h帮助请求); - 混合消息段(如
MessageSegment.image("test"))会保留在参数列表中。
插件侧通过ShellCommandArgv()(原始参数列表)与ShellCommandArgs()(解析后Namespace或ParserExit)消费(nonebot/params.py)。注意文档中的警告:如果参数解析失败,ShellCommandArgs获取到的将是ParserExit异常而非Namespace,需要在插件中自行判空与异常处理。
五、文本匹配规则常量:REGEX_MATCHED 与 前缀/后缀/全匹配/关键字
这五个常量由 nonebot/rule.py 中对应的文本规则写入,供插件通过nonebot.params依赖参数读取。
5.1 REGEX_MATCHED:正则匹配结果
REGEX_MATCHED: Literal["_matched"]——正则匹配结果存储 key。
RegexRule.__call__使用re.search(注意不是match,如需从头匹配须自行加^)对消息的str表示进行搜索,命中后将re.Match对象写入state[REGEX_MATCHED](nonebot/rule.py)。
插件侧对应依赖参数(nonebot/params.py):
RegexMatched()——返回Match[str]对象;RegexStr(*groups)——返回match.group(*groups)的文本;RegexGroup()——返回match.groups()元组;RegexDict()——返回match.groupdict()字典。
测试中通过构造fake_matched验证了这些依赖参数的读取行为(tests/test_param.py)。
5.2 STARTSWITH_KEY / ENDSWITH_KEY / FULLMATCH_KEY / KEYWORD_KEY
STARTSWITH_KEY: Literal["_startswith"]——响应触发前缀 key;ENDSWITH_KEY: Literal["_endswith"]——响应触发后缀 key;FULLMATCH_KEY: Literal["_fullmatch"]——响应触发完整消息 key;KEYWORD_KEY: Literal["_keyword"]——响应触发关键字 key。
各自的写入逻辑:
| 规则 | 匹配方式 | 写入内容 | 源码位置 |
|---|---|---|---|
StartswithRule | re.match匹配开头(可ignorecase) | 实际匹配到的前缀文本 | nonebot/rule.py |
EndswithRule | re.search匹配结尾(可ignorecase) | 实际匹配到的后缀文本 | nonebot/rule.py |
FullmatchRule | 纯文本与候选串全等(casefold忽略大小写) | 匹配到的完整文本 | nonebot/rule.py |
KeywordsRule | 纯文本包含任一关键字 | 命中的第一个关键字 | nonebot/rule.py |
对应依赖参数为Startswith()、Endswith()、Fullmatch()、Keyword()(nonebot/params.py)。一个典型用途:在@on_startswith("你好")的处理器中通过Startswith()拿到实际触发的前缀文本(例如用户发送了"你好呀",则拿到"你好"),实现更精细的响应。
六、实践:在插件中组合使用这些常量
6.1 直接读取 state 的"逃生通道"
虽然nonebot.params提供了大部分依赖参数,但框架本身并不禁止插件直接访问state。例如在@Matcher.got("city")之后:
from nonebot import on_command from nonebot.consts import ARG_KEY, CMD_KEY matcher = on_command("weather") @matcher.got("city", prompt="请输入城市名") async def handle(city: str = ArgStr("city")): # 等价于 state[ARG_KEY.format(key="city")] ...在自定义依赖函数中,ARG_KEY、RECEIVE_KEY等模板常量也能帮助你精确读写指定槽位,而不必硬编码"_receive_xxx"这类字符串。
6.2 组合命令参数与 arg 状态
一个综合示例:命令 + 多轮补参,同时读取命令元组与 got 参数:
from nonebot import on_command from nonebot.params import Command, ArgStr matcher = on_command("order") @matcher.got("item", prompt="请输入商品名") async def create_order( cmd: tuple[str, ...] = Command(), # 从 state[PREFIX_KEY][CMD_KEY] 读取 item: str = ArgStr("item"), # 从 state[ARG_KEY.format(key="item")] 读取 ): await matcher.send(f"命令 {cmd} 已收到商品 {item}")6.3 编写自定义规则时遵循常量协议
若需要编写自定义规则并在state中写入数据,应复用现有常量键(或遵循其命名风格),以保证与内置依赖参数、后续处理阶段兼容。例如自定义一个"天气关键字"规则时,可以参照KeywordsRule的写法把命中词写入state[KEYWORD_KEY](nonebot/rule.py),这样插件中直接用Keyword()就能拿到结果。
七、速查总表
| 常量 | 字面值 | 用途分组 | 主要读写位置 |
|---|---|---|---|
RECEIVE_KEY | "_receive_{id}" | Matcher | nonebot/internal/matcher/matcher.py |
LAST_RECEIVE_KEY | "_last_receive" | Matcher | nonebot/internal/matcher/matcher.py |
ARG_KEY | "{key}" | Matcher | nonebot/internal/matcher/matcher.py |
REJECT_TARGET | "_current_target" | Matcher | nonebot/internal/matcher/matcher.py |
REJECT_CACHE_TARGET | "_next_target" | Matcher | nonebot/internal/matcher/matcher.py |
PAUSE_PROMPT_RESULT_KEY | "_pause_result" | Matcher | nonebot/internal/matcher/matcher.py |
REJECT_PROMPT_RESULT_KEY | "_reject_{key}_result" | Matcher | nonebot/internal/matcher/matcher.py |
PREFIX_KEY | "_prefix" | Rule | nonebot/rule.py |
CMD_KEY | "command" | Rule | nonebot/rule.py |
RAW_CMD_KEY | "raw_command" | Rule | nonebot/rule.py |
CMD_ARG_KEY | "command_arg" | Rule | nonebot/rule.py |
CMD_START_KEY | "command_start" | Rule | nonebot/rule.py |
CMD_WHITESPACE_KEY | "command_whitespace" | Rule | nonebot/rule.py |
SHELL_ARGS | "_args" | Rule | nonebot/rule.py |
SHELL_ARGV | "_argv" | Rule | nonebot/rule.py |
REGEX_MATCHED | "_matched" | Rule | nonebot/rule.py |
STARTSWITH_KEY | "_startswith" | Rule | nonebot/rule.py |
ENDSWITH_KEY | "_endswith" | Rule | nonebot/rule.py |
FULLMATCH_KEY | "_fullmatch" | Rule | nonebot/rule.py |
KEYWORD_KEY | "_keyword" | Rule | nonebot/rule.py |
所有常量均以Literal[...]标注类型并定义于 nonebot/consts.py,是连接 nonebot/internal/matcher/matcher.py、nonebot/rule.py、nonebot/params.py 三个模块的"状态键协议"。理解这套常量,是深入 NoneBot2 事件处理管线、编写自定义规则与多轮会话插件的重要基础。
- 后端
- 即时通讯
【免费下载链接】nonebot2
跨平台 Python 异步聊天机器人框架 / Asynchronous multi-platform chatbot framework written in Python
相关推荐
用MOOTDX构建Python量化分析系统:通达信数据读取终极解决方案
用MOOTDX构建Python量化分析系统:通达信数据读取终极解决方案 在量化投资领域,数据获取一直是技术门槛最高的环节之一。MOOTDX作为通达信数据读取的P
金融科技数据分析163MusicLyrics:构建跨平台音乐元数据聚合框架的技术实现
163MusicLyrics:构建跨平台音乐元数据聚合框架的技术实现 价值定位与问题域定义 在数字音乐内容管理领域,元数据聚合与标准化处理构成了一个复杂的技术挑
桌面应用音视频终极指南:在Android设备上快速运行Windows应用的5个简单步骤
终极指南:在Android设备上快速运行Windows应用的5个简单步骤 在移动设备上运行Windows x86应用程序曾经听起来像是天方夜谭,但Mobox项目
虚拟化CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考