├── LICENSE ├── advanced └── functions │ ├── builtin-config.md │ ├── builtin-dependency-injection.md │ ├── builtin-exception.md │ ├── handler-advanced.md │ └── matcher-advanced.md └── tutorial ├── install └── install-without-cli.md ├── introduction ├── adapter.md ├── driver.md ├── overview.md └── plugin.md ├── overview.md ├── plugin-advance ├── call-api.md ├── config.md ├── custom-api.md ├── hook.md ├── nested-plugin.md ├── require.md ├── rule.md └── scheduler.md └── plugin-basic ├── create-and-load.md ├── get-data.md ├── handler.md ├── logger.md ├── matcher.md ├── message-construct.md ├── overview.md ├── permission.md └── resource └── 07_00.jpg /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 NoneBot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /advanced/functions/builtin-config.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | description: 内置配置项 4 | --- 5 | 6 | # 内置配置项 7 | 8 | ::: warning 9 | 本章节为[配置项](../../tutorial/plugin-advance/config)的拓展内容,请务必在阅读并理解之后再阅读本节内容。 10 | ::: 11 | 12 | ## Driver 13 | 14 | - **类型**: `str` 15 | - **默认值**: `"~fastapi"` 16 | 17 | NoneBot2 运行所使用的驱动器。主要分为 `ForwardDriver`、`ReverseDriver` 即客户端和服务端两类。 18 | 19 | 配置格式采用特殊语法:`[:][+[:]]*` 20 | 21 | 其中 `` 为驱动器模块名,可以使用 `~` 作为 `nonebot.drivers.` 的简写;`` 为驱动器类名,默认为 `Driver`;`` 为驱动器混入的类名,默认为 `Mixin`。 22 | 23 | NoneBot2 内置了几个常用驱动器,包括了各类常用功能,常见驱动器配置如下: 24 | 25 | ```env 26 | DRIVER=~fastapi 27 | DRIVER=~httpx+~websockets 28 | DRIVER=~fastapi+~httpx+~websockets 29 | DRIVER=~fastapi+~aiohttp 30 | ``` 31 | 32 | 各驱动器的功能与区别请参考[选择驱动器](./choose-driver.md)。 33 | 34 | ## Host 35 | 36 | - **类型**: `IPvAnyAddress` 37 | - **默认值**: `127.0.0.1` 38 | 39 | 使用 `ReversedDriver` 时,NoneBot2 监听的 IP/主机名。 40 | 41 | ```env 42 | HOST=127.0.0.1 43 | ``` 44 | 45 | ## Port 46 | 47 | - **类型**: `int` 48 | - **默认值**: `8080` 49 | 50 | 使用 `ReversedDriver` 时,NoneBot2 监听的端口。 51 | 52 | ```env 53 | PORT=8080 54 | ``` 55 | 56 | ## Log Level 57 | 58 | - **类型**: `int | str` 59 | - **默认值**: `INFO` 60 | 61 | NoneBot2 日志输出等级,可以为 `int` 类型等级或等级名称(日志等级名称应为大写,如 `INFO`)。 62 | 63 | 参考 [`loguru 日志等级`](https://loguru.readthedocs.io/en/stable/api/logger.html#levels)。 64 | 65 | ```env 66 | LOG_LEVEL=INFO 67 | ``` 68 | 69 | ## API Timeout 70 | 71 | - **类型**: `Optional[float]` 72 | - **默认值**: `30.0` 73 | 74 | API 请求超时时间,单位为秒。 75 | 76 | ```env 77 | API_TIMEOUT=30.0 78 | ``` 79 | 80 | ## SuperUsers 81 | 82 | - **类型**: `Set[str]` 83 | - **默认值**: `set()` 84 | 85 | 机器人超级用户,可以使用权限 [`SUPERUSER`](../api/permission.md#SUPERUSER)。 86 | 87 | ```env 88 | SUPERUSERS=["1234567890"] 89 | ``` 90 | 91 | ## Nickname 92 | 93 | - **类型**: `Set[str]` 94 | - **默认值**: `set()` 95 | 96 | 机器人昵称,通常协议适配器会根据用户是否 @user 或者是否以机器人昵称开头来判断是否是向机器人发送的消息。 97 | 98 | ```env 99 | NICKNAME=["bot"] 100 | ``` 101 | 102 | ## Command Start 103 | 104 | - **类型**: `Set[str]` 105 | - **默认值**: 106 | - Command Start: `{"/"}` 107 | 108 | 命令消息的起始符。用于 [`command`](../api/rule.md#command) 规则。 109 | 110 | ```env 111 | COMMAND_START={"/", "!"} 112 | ``` 113 | 114 | ## Command Separator 115 | 116 | - **类型**: `Set[str]` 117 | - **默认值**: 118 | - Command Separator: `{"."}` 119 | 120 | 命令消息的分割符。用于 [`command`](../api/rule.md#command) 规则。 121 | 122 | ```env 123 | COMMAND_START={"/", "!"} 124 | COMMAND_SEP={".", "/"} 125 | ``` 126 | 127 | ## Session Expire Timeout 128 | 129 | - **类型**: `timedelta` 130 | - **默认值**: `timedelta(minutes=2)` 131 | 132 | 用户会话超时时间,配置格式参考 [Datetime Types](https://pydantic-docs.helpmanual.io/usage/types/#datetime-types)。 133 | 134 | ```env 135 | SESSION_EXPIRE_TIMEOUT=120 136 | ``` 137 | -------------------------------------------------------------------------------- /advanced/functions/builtin-dependency-injection.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | description: 内置依赖注入 4 | --- 5 | 6 | # 内置依赖注入 7 | 8 | ::: warning 9 | 本章节为[读取与自定义配置](../../tutorial/plugin-basic/get-data)的拓展内容,请务必在阅读并理解之后再阅读本节内容。 10 | ::: 11 | 12 | ## Bot 13 | 14 | 获取当前事件的 Bot 对象。 15 | 16 | ```python {7-9} 17 | from nonebot.adapters import Bot 18 | 19 | async def _(bot: Bot): ... 20 | async def _(bot): ... # 兼容性处理 21 | ``` 22 | 23 | ## Event 24 | 25 | 获取当前事件。 26 | 27 | ```python {6-8} 28 | from nonebot.adapters import Event 29 | 30 | async def _(event: Event): ... 31 | async def _(event): ... # 兼容性处理 32 | ``` 33 | 34 | ## EventType 35 | 36 | 获取当前事件的类型。 37 | 38 | ```python {3} 39 | from nonebot.params import EventType 40 | 41 | async def _(foo: str = EventType()): ... 42 | ``` 43 | 44 | ## EventMessage 45 | 46 | 获取当前事件的消息。 47 | 48 | ```python {4} 49 | from nonebot.adapters import Message 50 | from nonebot.params import EventMessage 51 | 52 | async def _(foo: str = EventMessage()): ... 53 | ``` 54 | 55 | ## EventPlainText 56 | 57 | 获取当前事件的消息纯文本部分。 58 | 59 | ```python {3} 60 | from nonebot.params import EventPlainText 61 | 62 | async def _(foo: str = EventPlainText()): ... 63 | ``` 64 | 65 | ## EventToMe 66 | 67 | 获取当前事件是否与机器人相关。 68 | 69 | ```python {3} 70 | from nonebot.params import EventToMe 71 | 72 | async def _(foo: bool = EventToMe()): ... 73 | ``` 74 | 75 | ## State 76 | 77 | 获取当前事件处理上下文状态,State 为一个字典,用户可以向 State 中添加数据来保存状态等操作。(请注意不要随意覆盖 State 中 NoneBot 的数据) 78 | 79 | ```python {4} 80 | from nonebot.typing import T_State 81 | 82 | async def _(foo: T_State): ... 83 | ``` 84 | 85 | ## Command 86 | 87 | 获取当前命令型消息的元组形式命令名。 88 | 89 | ```python {7} 90 | from nonebot import on_command 91 | from nonebot.params import Command 92 | 93 | matcher = on_command("cmd") 94 | 95 | @matcher.handle() 96 | async def _(foo: Tuple[str, ...] = Command()): ... 97 | ``` 98 | 99 | ::: tips 100 | 命令详情只能在首次接收到命令型消息时获取,如果在事件处理后续流程中获取,则会获取到不同的值。 101 | ::: 102 | 103 | ## RawCommand 104 | 105 | 获取当前命令型消息的文本形式命令名。 106 | 107 | ```python {7} 108 | from nonebot import on_command 109 | from nonebot.params import RawCommand 110 | 111 | matcher = on_command("cmd") 112 | 113 | @matcher.handle() 114 | async def _(foo: str = RawCommand()): ... 115 | ``` 116 | 117 | ::: tips 118 | 命令详情只能在首次接收到命令型消息时获取,如果在事件处理后续流程中获取,则会获取到不同的值。 119 | ::: 120 | 121 | ## CommandArg 122 | 123 | 获取命令型消息命令后跟随的参数。 124 | 125 | ```python {8} 126 | from nonebot import on_command 127 | from nonebot.adapters import Message 128 | from nonebot.params import CommandArg 129 | 130 | matcher = on_command("cmd") 131 | 132 | @matcher.handle() 133 | async def _(foo: Message = CommandArg()): ... 134 | ``` 135 | 136 | ::: tips 137 | 命令详情只能在首次接收到命令型消息时获取,如果在事件处理后续流程中获取,则会获取到不同的值。 138 | ::: 139 | 140 | ## CommandStart 141 | 142 | 获取命令型消息命令前缀。 143 | 144 | ```python {8} 145 | from nonebot import on_command 146 | from nonebot.adapters import Message 147 | from nonebot.params import CommandStart 148 | 149 | matcher = on_command("cmd") 150 | 151 | @matcher.handle() 152 | async def _(foo: str = CommandStart()): ... 153 | ``` 154 | 155 | ::: tips 156 | 命令详情只能在首次接收到命令型消息时获取,如果在事件处理后续流程中获取,则会获取到不同的值。 157 | ::: 158 | 159 | ## ShellCommandArgs 160 | 161 | 获取 shell 命令解析后的参数。 162 | 163 | ::: tips 164 | 如果参数解析失败,则为 [`ParserExit`](builtin-exception#ParserExit) 异常,并携带错误码与错误信息。 165 | 166 | 由于 `ArgumentParser` 在解析到 `--help` 参数时也会抛出异常,这种情况下错误码为 `0` 且错误信息即为帮助信息。 167 | ::: 168 | 169 | ```python {8,12} 170 | from nonebot import on_shell_command 171 | from nonebot.params import ShellCommandArgs 172 | 173 | matcher = on_shell_command("cmd", parser) 174 | 175 | # 解析失败 176 | @matcher.handle() 177 | async def _(foo: ParserExit = ShellCommandArgs()): ... 178 | 179 | # 解析成功 180 | @matcher.handle() 181 | async def _(foo: Dict[str, Any] = ShellCommandArgs()): ... 182 | ``` 183 | 184 | ## ShellCommandArgv 185 | 186 | 获取 shell 命令解析前的参数列表。 187 | 188 | ```python {7} 189 | from nonebot import on_shell_command 190 | from nonebot.params import ShellCommandArgs 191 | 192 | matcher = on_shell_command("cmd") 193 | 194 | @matcher.handle() 195 | async def _(foo: List[str] = ShellCommandArgv()): ... 196 | ``` 197 | 198 | ## RegexMatched 199 | 200 | 获取正则匹配结果。 201 | 202 | ```python {7} 203 | from nonebot import on_regex 204 | from nonebot.params import RegexMatched 205 | 206 | matcher = on_regex("regex") 207 | 208 | @matcher.handle() 209 | async def _(foo: str = RegexMatched()): ... 210 | ``` 211 | 212 | ## RegexGroup 213 | 214 | 获取正则匹配结果的 group 元组。 215 | 216 | ```python {7} 217 | from nonebot import on_regex 218 | from nonebot.params import RegexGroup 219 | 220 | matcher = on_regex("regex") 221 | 222 | @matcher.handle() 223 | async def _(foo: Tuple[Any, ...] = RegexGroup()): ... 224 | ``` 225 | 226 | ## RegexDict 227 | 228 | 获取正则匹配结果的 group 字典。 229 | 230 | ```python {7} 231 | from nonebot import on_regex 232 | from nonebot.params import RegexDict 233 | 234 | matcher = on_regex("regex") 235 | 236 | @matcher.handle() 237 | async def _(foo: Dict[str, Any] = RegexDict()): ... 238 | ``` 239 | 240 | ## Matcher 241 | 242 | 获取当前事件响应器实例。 243 | 244 | ```python {7} 245 | from nonebot import on_message 246 | from nonebot.matcher import Matcher 247 | 248 | foo = on_message() 249 | 250 | @foo.handle() 251 | async def _(matcher: Matcher): ... 252 | ``` 253 | 254 | ## Received 255 | 256 | 获取某次 `receive` 接收的事件。 257 | 258 | ```python {8} 259 | from nonebot import on_message 260 | from nonebot.adapters import Event 261 | from nonebot.params import Received 262 | 263 | matcher = on_message() 264 | 265 | @matcher.receive("id") 266 | async def _(foo: Event = Received("id")): ... 267 | ``` 268 | 269 | ## LastReceived 270 | 271 | 获取最近一次 `receive` 接收的事件。 272 | 273 | ```python {8} 274 | from nonebot import on_message 275 | from nonebot.adapters import Event 276 | from nonebot.params import LastReceived 277 | 278 | matcher = on_message() 279 | 280 | @matcher.receive("any") 281 | async def _(foo: Event = LastReceived()): ... 282 | ``` 283 | 284 | ## Arg 285 | 286 | 获取某次 `got` 接收的参数。 287 | 288 | ```python {8-9} 289 | from nonebot.params import Arg 290 | from nonebot import on_message 291 | from nonebot.adapters import Message 292 | 293 | matcher = on_message() 294 | 295 | @matcher.got("key") 296 | async def _(key: Message = Arg()): ... 297 | async def _(foo: Message = Arg("key")): ... 298 | ``` 299 | 300 | ## ArgStr 301 | 302 | 获取某次 `got` 接收的参数,并转换为字符串。 303 | 304 | ```python {7-8} 305 | from nonebot import on_message 306 | from nonebot.params import ArgStr 307 | 308 | matcher = on_message() 309 | 310 | @matcher.got("key") 311 | async def _(key: str = ArgStr()): ... 312 | async def _(foo: str = ArgStr("key")): ... 313 | ``` 314 | 315 | ## ArgPlainText 316 | 317 | 获取某次 `got` 接收的参数的纯文本部分。 318 | 319 | ```python {7-8} 320 | from nonebot import on_message 321 | from nonebot.params import ArgPlainText 322 | 323 | matcher = on_message() 324 | 325 | @matcher.got("key") 326 | async def _(key: str = ArgPlainText()): ... 327 | async def _(foo: str = ArgPlainText("key")): ... 328 | ``` 329 | 330 | ## Exception 331 | 332 | 获取事件响应器运行中抛出的异常。 333 | 334 | ```python {4} 335 | from nonebot.message import run_postprocessor 336 | 337 | @run_postprocessor 338 | async def _(e: Exception): ... 339 | ``` 340 | 341 | ## Default 342 | 343 | 带有默认值的参数,便于复用依赖。 344 | 345 | ```python {1} 346 | async def _(foo="bar"): ... 347 | ``` 348 | -------------------------------------------------------------------------------- /advanced/functions/builtin-exception.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | description: 内置异常类型 4 | --- -------------------------------------------------------------------------------- /advanced/functions/handler-advanced.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | description: 事件处理进阶 4 | --- -------------------------------------------------------------------------------- /advanced/functions/matcher-advanced.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 事件响应器进阶 4 | --- 5 | 6 | # 事件响应器进阶 7 | 8 | ::: warning 9 | 本章节为[事件响应器](../../tutorial/plugin-basic/matcher.md)的拓展内容,请务必在阅读并理解之后再阅读本节内容。 10 | ::: 11 | 12 | ## 事件响应器的参数 13 | 14 | ### 通用参数 15 | 16 | 事件响应器的通用参数为任何事件响应器的辅助函数均支持的参数。 17 | 18 | #### 事件响应规则-rule 19 | 20 | 事件响应器的响应规则是一个 `Rule` 对象,它是一系列 `checker` 的集合,当所有的 `checker` 都返回 `True` 时,才会触发该响应器。 21 | 22 | ```py 23 | from nonebot import on_message 24 | 25 | on_message(rule=Rule(_checker)) 26 | ``` 27 | 28 | 详情请参考[教程 - 自定义规则](../../tutorial/plugin-advance/rule.md)。 29 | 30 | #### 事件处理函数列表-handler 31 | 32 | 事件处理函数列表是一个 `List[Union[T_Handler, Dependent]]` 对象,其中存放着该事件响应器的事件处理函数。通常我们会选择[事件响应器的依赖处理装饰器](../../tutorial/plugin-basic/handler.md#事件处理函数)来添加事件处理函数,而较少直接在事件响应器的注册阶段进行修改。 33 | 34 | #### 临时事件响应器-temp 35 | 36 | 临时事件响应器是一个 `bool` 对象,声明该事件响应器为临时事件响应器,该事件响应器在触发过一次后便会被销毁。在事件响应器被销毁后仍可重新注册响应器。 37 | 38 | ```py 39 | from nonebot import on_message 40 | 41 | on_message(temp=True) 42 | ``` 43 | 44 | #### 有效期-expire_time 45 | 46 | 有效期是一个 `Union[datetime, timedelta]` 对象,为事件响应器设置有效期(或过期时间),在超出有效期后无法再次触发该响应器并销毁。在事件响应器被销毁后仍可重新注册响应器。 47 | 48 | ```py 49 | from nonebot import on_message 50 | from datetime import timedelta 51 | 52 | on_message(expire_time=timedelta(seconds=300)) 53 | ``` 54 | 55 | 关于 datetime 模块的用法,请参考[官方文档](https://docs.python.org/3/library/datetime.html)。 56 | 57 | #### 优先级-priority 58 | 59 | 优先级是一个 `int` 对象,事件响应器的优先级代表事件响应器的执行顺序。默认的优先级为 `1`。 60 | 61 | 同一优先级的事件响应器会**同时执行**,优先级数字**越小**越先响应!优先级请从 `1` 开始排序! 62 | 63 | ```py 64 | from nonebot import on_message 65 | 66 | on_message(priority=10) 67 | ``` 68 | 69 | #### 阻断-block 70 | 71 | 阻断是一个 `bool` 对象,当有任意事件响应器发出了阻止事件传递信号时,该事件将不再会传递给下一优先级,直接结束处理。 72 | 73 | NoneBot 内置的事件响应器中,所有非 `command` 规则的 `message` 类型的事件响应器都会阻断事件传递,其他则不会。 74 | 75 | ```py 76 | from nonebot import on_message 77 | 78 | on_message(block=True) 79 | ``` 80 | 81 | #### 上下文-state 82 | 83 | 事件响应器的上下文是一个 `T_State` 对象,即 `Dict[Any, Any]` 对象,用于存放一个事件处理流程中的上下文信息。在事件响应器被触发后,会将此处传入的state并入该事件处理流程的上下文信息中。 84 | 85 | ```py 86 | from nonebot import on_message 87 | 88 | on_message(state={'key':'value'}) 89 | ``` 90 | 91 | 详情请参考[进阶 - 依赖注入](builtin-dependency-injection#State)。 92 | 93 | ### 特殊参数 94 | 95 | 事件响应器的特殊参数为部分响应器支持的参数。在使用此部分参数前请务必参考[事件响应器的辅助函数](#事件响应器的辅助函数)中列出的可用范围。 96 | 97 | #### 事件类型-type 98 | 99 | 事件类型即是该响应器所要响应的事件类型,只有在接收到的事件类型与该响应器的类型相同时,才会触发该响应器。如果类型留空,该响应器将会响应所有类型的事件。目前**仅有** [`on`](#on) 含有此参数。 100 | 101 | NoneBot 内置了四种主要类型:`meta_event`、`message`、`notice`、`request`。通常情况下,协议适配器会将事件合理地分类至这四种类型中。如果有其他类型的事件需要响应,可以自行定义新的类型。 102 | 103 | ```py 104 | from nonebot import on 105 | 106 | on('message') 107 | on('custom_type') 108 | ``` 109 | 110 | #### 事件响应权限-permission 111 | 112 | 事件响应器的响应权限是一个 `Permission` 对象,它也是一系列 `checker` 的集合,当其中一个 `checker` 返回 `True` 时,就会触发该响应器。 113 | 114 | ```py 115 | from nonebot import on_message 116 | 117 | on_message(permission=SUPERUSER) 118 | ``` 119 | 120 | 详情请参考[教程 - 权限控制](../../tutorial/plugin-basic/permission)。 121 | 122 | #### msg 123 | 124 | `msg` 是一个 `Union[str, Tuple[str, ...]]` 对象,用于匹配消息事件的内容。在不同的辅助函数中匹配模式也有所不同。如果事件响应器的辅助函数包含此参数,**此参数为必填参数**。 125 | 126 | ```py 127 | from nonebot import on_command, on_fullmatch 128 | 129 | on_fullmatch('msg_arg') 130 | ``` 131 | 132 | ### cmd 133 | 134 | `cmd` 是一个 `Union[str, Tuple[str, ...]]` 对象,用于匹配消息事件的内容。如果事件响应器的辅助函数包含此参数,**此参数为必填参数**。 135 | 136 | `msg` 和 `cmd` 的区别在于,包含 `cmd` 参数的辅助函数所生成的事件响应器为**命令类型**的事件响应器,匹配规则有所不同,详情请参考 [`on_command`](#on_command) 和 [`on_shell_command`](#on_shell_command)。 137 | 138 | ```py 139 | from nonebot import on_command 140 | 141 | ## env 中 COMMAND_SEP=["."] COMMAND_START=["/"] 142 | 143 | on_command('test') # 响应 /test 144 | on_command({'test','alpha'}) # 响应 /test.alpha 145 | ``` 146 | 147 | #### keywords 148 | 149 | `keywords` 是一个 `Set[str]` 对象,用于存放关键词列表。在被匹配的消息的纯文本部分中如果含哟 `keywords` 中的元素即可响应成功。目前**仅有** [`on_keyword`](#on_keyword) 含有此参数。 150 | 151 | ```py 152 | from nonebot import on_keyword 153 | 154 | on_keyword({'key', 'word', 'keywords'}) 155 | ``` 156 | 157 | #### aliases 158 | 159 | `aliases` 是一个 `Set[Union[str, Tuple[str, ...]]]` 对象,用于存放*命令别名*,通常和 [`cmd`](#cmd) 同时出现。其中每一个元素的作用均等价于 [`cmd`](#cmd) 的作用。 160 | 161 | 值得注意的是,我们并不能仅使用 `aliases` 而不填写 [`cmd`](#cmd),`aliases` 仅作为 [`cmd`](#cmd) 的补充而不是替代。 162 | 163 | ```py 164 | from nonebot import on_command 165 | 166 | on_command('cmd_arg', aliases={'cmd','arg','命令'}) 167 | ``` 168 | 169 | #### ignorecase 170 | 171 | `ignorecase` 是一个 `bool` 对象,用于声明事件响应器是否可以忽略大小写进行匹配。 172 | 173 | ```py 174 | from nonebot import on_startswith 175 | 176 | on_startswith('msg_arg', ignorecase=True) 177 | ``` 178 | 179 | #### parser 180 | 181 | `parser` 是一个 `nonebot.rule.ArgumentParser` 对象,继承自 `argparse.ArgumentParser` ,用于解析 `shell_like` 的参数。具体用法可以参考 [python官方文档](https://docs.python.org/3/library/argparse.html) 的介绍。目前**仅有** [`on_shell_command`](#on_shell_command) 含有此参数。 182 | 183 | ```py 184 | from nonebot import on_shell_command 185 | from nonebot.rule import ArgumentParser 186 | 187 | arg_parser = ArgumentParser() 188 | arg_parser.add_argument('--foo', help='foo help') 189 | on_shell_command('msg_arg', parser=arg_parser) 190 | ``` 191 | 192 | #### pattern 193 | 194 | `pattern` 是一个 `str` 对象,用于存放正则表达式。具体用法可以参考 [python官方文档](https://docs.python.org/3/library/re.html) 的介绍。目前**仅有** [`on_regex`](#on_regex) 含有此参数。 195 | 196 | ```py 197 | from nonebot import on_regex 198 | 199 | on_regex(r'^abc$') 200 | ``` 201 | 202 | #### flags 203 | 204 | `flags` 是一个 `Union[int, re.RegexFlag]` 对象,用于存放正则匹配标志。具体用法可以参考 [python官方文档](https://docs.python.org/3/library/re.html) 的介绍。目前**仅有** [`on_regex`](#on_regex) 含有此参数。 205 | 206 | ```py 207 | from nonebot import on_regex 208 | 209 | on_regex(r'^abc$', flags=1) 210 | ``` 211 | 212 | ## 事件响应器的辅助函数 213 | 214 | 事件响应器的辅助函数共有12种。其中 `on`、`on_metaevent`、`on_request`、`on_notice`、`on_message` 四种为直接调用 `Matcher.new()` 进行响应器的创建,`on_startswith`、`on_endswith`、`on_fullmatch`、`on_keyword`、`on_command`、`on_shell_command`、`on_regex` 函数都是在 `on_message` 的基础上添加了对应的匹配规则 `rule` 而成的。 215 | 216 | ### on 217 | 218 | 注册一个基础事件响应器,可自定义响应的消息类型。 219 | 220 | 可用参数: 221 | 222 | - [type](#事件类型-type)(特殊) 223 | - [rule](#事件响应规则-rule) 224 | - [permission](#事件响应权限-permission)(特殊) 225 | - [handlers](#事件处理函数列表-handler) 226 | - [temp](#临时事件响应器-temp) 227 | - [expire_time](#有效期-expire_time) 228 | - [priority](#优先级-priority) 229 | - [block](#阻断-block) 230 | - [state](#上下文-state) 231 | 232 | ### on_metaevent 233 | 234 | 注册一个元事件响应器。 235 | 236 | 可用参数: 237 | 238 | - [rule](#事件响应规则-rule) 239 | - [handlers](#事件处理函数列表-handler) 240 | - [temp](#临时事件响应器-temp) 241 | - [expire_time](#有效期-expire_time) 242 | - [priority](#优先级-priority) 243 | - [block](#阻断-block) 244 | - [state](#上下文-state) 245 | 246 | ### on_request 247 | 248 | 注册一个请求事件响应器。 249 | 250 | 可用参数: 251 | 252 | - [rule](#事件响应规则-rule) 253 | - [handlers](#事件处理函数列表-handler) 254 | - [temp](#临时事件响应器-temp) 255 | - [expire_time](#有效期-expire_time) 256 | - [priority](#优先级-priority) 257 | - [block](#阻断-block) 258 | - [state](#上下文-state) 259 | 260 | ### on_notice 261 | 262 | 注册一个通知事件响应器。 263 | 264 | 可用参数: 265 | 266 | - [rule](#事件响应规则-rule) 267 | - [handlers](#事件处理函数列表-handler) 268 | - [temp](#临时事件响应器-temp) 269 | - [expire_time](#有效期-expire_time) 270 | - [priority](#优先级-priority) 271 | - [block](#阻断-block) 272 | - [state](#上下文-state) 273 | 274 | ### on_message 275 | 276 | 注册一个消息事件响应器。 277 | 278 | 可用参数: 279 | 280 | - [rule](#事件响应规则-rule) 281 | - [permission](#事件响应权限-permission)(特殊) 282 | - [handlers](#事件处理函数列表-handler) 283 | - [temp](#临时事件响应器-temp) 284 | - [expire_time](#有效期-expire_time) 285 | - [priority](#优先级-priority) 286 | - [block](#阻断-block) 287 | - [state](#上下文-state) 288 | 289 | ### on_startswith 290 | 291 | 注册一个消息事件响应器,并且当消息的**文本部分**以指定内容开头时响应。 292 | 293 | 可用参数: 294 | 295 | - [msg](#msg)(特殊)(必填) 296 | - [rule](#事件响应规则-rule) 297 | - [ignorecase](#ignorecase)(特殊) 298 | - [permission](#事件响应权限-permission)(特殊) 299 | - [handlers](#事件处理函数列表-handler) 300 | - [temp](#临时事件响应器-temp) 301 | - [expire_time](#有效期-expire_time) 302 | - [priority](#优先级-priority) 303 | - [block](#阻断-block) 304 | - [state](#上下文-state) 305 | 306 | ### on_endswith 307 | 308 | 注册一个消息事件响应器,并且当消息的**文本部分**以指定内容结尾时响应。 309 | 310 | 可用参数: 311 | 312 | - [msg](#msg)(特殊)(必填) 313 | - [rule](#事件响应规则-rule) 314 | - [ignorecase](#ignorecase)(特殊) 315 | - [permission](#事件响应权限-permission)(特殊) 316 | - [handlers](#事件处理函数列表-handler) 317 | - [temp](#临时事件响应器-temp) 318 | - [expire_time](#有效期-expire_time) 319 | - [priority](#优先级-priority) 320 | - [block](#阻断-block) 321 | - [state](#上下文-state) 322 | 323 | ### on_fullmatch 324 | 325 | 注册一个消息事件响应器,并且当消息的**文本部分**与指定内容完全一致时响应。 326 | 327 | 可用参数: 328 | 329 | - [msg](#msg)(特殊)(必填) 330 | - [rule](#事件响应规则-rule) 331 | - [ignorecase](#ignorecase)(特殊) 332 | - [permission](#事件响应权限-permission)(特殊) 333 | - [handlers](#事件处理函数列表-handler) 334 | - [temp](#临时事件响应器-temp) 335 | - [expire_time](#有效期-expire_time) 336 | - [priority](#优先级-priority) 337 | - [block](#阻断-block) 338 | - [state](#上下文-state) 339 | 340 | ### on_keyword 341 | 342 | 注册一个消息事件响应器,并且当消息纯文本部分包含关键词时响应。 343 | 344 | 可用参数: 345 | 346 | - [keywords](#keywords)(特殊)(必填) 347 | - [rule](#事件响应规则-rule) 348 | - [permission](#事件响应权限-permission)(特殊) 349 | - [handlers](#事件处理函数列表-handler) 350 | - [temp](#临时事件响应器-temp) 351 | - [expire_time](#有效期-expire_time) 352 | - [priority](#优先级-priority) 353 | - [block](#阻断-block) 354 | - [state](#上下文-state) 355 | 356 | ### on_command 357 | 358 | 注册一个消息事件响应器,并且当消息以指定命令开头时响应。 359 | 360 | 根据配置里提供的 [`command_start`](builtin-config#command-start), [`command_sep`](builtin-config#command-separator) 判断消息是否为 `command`。 361 | 362 | 可用参数: 363 | 364 | - [cmd](#cmd)(特殊)(必填) 365 | - [rule](#事件响应规则-rule) 366 | - [aliases](#aliases)(特殊) 367 | - [permission](#事件响应权限-permission)(特殊) 368 | - [handlers](#事件处理函数列表-handler) 369 | - [temp](#临时事件响应器-temp) 370 | - [expire_time](#有效期-expire_time) 371 | - [priority](#优先级-priority) 372 | - [block](#阻断-block) 373 | - [state](#上下文-state) 374 | 375 | ### on_shell_command 376 | 377 | 注册一个支持 `shell_like` 解析参数的命令消息事件响应器。 378 | 379 | 与 `on_command` 相同,`on_shell_command` 采用**类似**的方法来判断消息是否为 `shell_command`,但与之不同的是,`shell_command` 会额外检查是否**包含命令以外**的内容,即参数内容。 380 | 381 | `on_shell_command` 通常被用于需要多个参数的场景。在添加 `parser` 参数时, 响应器会自动处理消息,并将用户输入的原始参数列表保存在 `state["argv"]`, `parser` 处理的参数保存在 `state["args"]` 中。 382 | 383 | 可用参数: 384 | 385 | - [cmd](#cmd)(特殊)(必填) 386 | - [rule](#事件响应规则-rule) 387 | - [aliases](#aliases)(特殊) 388 | - [parser](#parser)(特殊) 389 | - [permission](#事件响应权限-permission)(特殊) 390 | - [handlers](#事件处理函数列表-handler) 391 | - [temp](#临时事件响应器-temp) 392 | - [expire_time](#有效期-expire_time) 393 | - [priority](#优先级-priority) 394 | - [block](#阻断-block) 395 | - [state](#上下文-state) 396 | 397 | 使用方法: 398 | 399 | ```python 400 | from nonebot import on_shell_command 401 | from nonebot.rule import ArgumentParser 402 | 403 | ## env 中 COMMAND_SEP=["."] COMMAND_START=["/"] 404 | 405 | parser = ArgumentParser() 406 | parser.add_argument("--a", action="store") 407 | parser.add_argument("-y" , action="store_true") 408 | 409 | rule = on_shell_command("ls", parser=parser) 410 | # /ls --a test -y 411 | # state['a'] == 'test' 412 | # state['y'] == True 413 | ``` 414 | 415 | ### on_regex 416 | 417 | 注册一个消息事件响应器,并且当消息匹配正则表达式时响应。 418 | 419 | 可用参数: 420 | 421 | - [pattern](#pattern)(特殊)(必填) 422 | - [flags](#flags)(特殊) 423 | - [rule](#事件响应规则-rule) 424 | - [permission](#事件响应权限-permission)(特殊) 425 | - [handlers](#事件处理函数列表-handler) 426 | - [temp](#临时事件响应器-temp) 427 | - [expire_time](#有效期-expire_time) 428 | - [priority](#优先级-priority) 429 | - [block](#阻断-block) 430 | - [state](#上下文-state) 431 | 432 | ## 事件响应器组 433 | 434 | 事件响应器组是用于批量创建拥有相同参数的响应器所设计的。 435 | 436 | 在响应器组实例化时,传入的参数将会被作为**默认参数**,后续调用此响应器组所创建的事件响应器中将会被传入这些默认参数,从而避免**重复填写相同参数**。 437 | 438 | 如果响应器组中某个响应器需要传入**与默认参数不同**的参数时,可以直接在调用方法时传入新的参数,在新参数存在时将会**覆盖**掉默认参数。 439 | 440 | ### MatcherGroup 441 | 442 | 事件响应器组合,统一管理。为 `Matcher` 创建提供默认属性。 443 | 444 | 可用参数: 445 | 446 | - **kwargs: [`on`](#on) 的参数默认值,可被其所属命令覆盖 447 | 448 | `MatcherGroup` 目前支持全部辅助函数的调用,在用法上与其对应的的辅助函数相同。 449 | 450 | ```py 451 | from nonebot import MatcherGroup 452 | 453 | matcher_group = MatcherGroup(priority=10, block=False) 454 | matcher_cmd = matcher_group.on_command('cmd') 455 | matcher_start = matcher_group.on_startswith('start', ignorecase=True, temp=True) 456 | matcher_regex = matcher_group.on_regex(r'^regex$', priority=15) 457 | ``` 458 | 459 | 上述写法等价于: 460 | 461 | ```py 462 | from nonebot import on_command, on_startswith, on_regex 463 | 464 | matcher_cmd = on_command('cmd', priority=10) 465 | matcher_start = on_startswith('start', ignorecase=True, temp=True, priority=10, block=False) 466 | matcher_regex = on_regex(r'^regex$', priority=15, block=False) 467 | ``` 468 | 469 | ### CommandGroup 470 | 471 | 注册一个命令组,用于声明一组有相同名称前缀的命令。 472 | 473 | 可用参数: 474 | 475 | - cmd: 命令前缀 **(必填)(不可覆盖)** 476 | - **kwargs: [`on_command`](#on_command) 的参数默认值,可被其所属命令覆盖 477 | 478 | `CommandGroup` 目前支持的命令有 [`command`](#on_command) 和 [`shell_command`](#on_shell_command) 两种,在用法上与其对应的的辅助函数相同。 479 | 480 | ```py 481 | from nonebot import CommandGroup 482 | 483 | # env 中 COMMAND_SEP=["."] COMMAND_START=["/"] 484 | 485 | cmd_group = CommandGroup('prefix') 486 | matcher_0 = cmd_group.command('0') # 响应 /prefix.0 487 | matcher_1 = cmd_group.command('1') # 响应 /prefix.1 488 | matcher_2 = cmd_group.command('2') # 响应 /prefix.2 489 | ``` 490 | -------------------------------------------------------------------------------- /tutorial/install/install-without-cli.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | description: 不使用 nb-cli 安装创建机器人 4 | --- 5 | 6 | # 不使用 nb-cli 安装创建机器人 7 | 8 | ::: warning 9 | 我们十分不推荐直接创建机器人项目,请您优先考虑使用 nb-cli 进行项目创建。 10 | ::: 11 | 12 | 在本节中,我们将简要介绍如何在不使用 nb-cli 的方式创建一个机器人项目的**最小实例**,但我们十分不建议对于长期运行的或需要发布的项目使用此类方法进行部署,如果您需要在不使用 nb-cli 的情况下部署此类型的项目,请考虑使用 poetry 或其他项目管理工具。 13 | 14 | 一个机器人项目的**最小实例**中**至少**需要包含以下内容: 15 | 16 | - 入口文件:初始化并运行机器人 17 | - 配置项文件:存储机器人启动所需的参数 18 | - 插件:为机器人提供具体的功能 19 | 20 | ## 安装依赖 21 | 22 | 在创建项目前,我们首先需要将项目所需依赖安装至环境中。详情请参考[这里]()。 23 | 24 | 同时,如果你使用了第三方插件,你也需要在**运行之前**将插件安装到环境或项目中。详情见[加载插件](#加载插件) 25 | 26 | ## 创建配置项文件 27 | 28 | 配置项文件是用于存放 NoneBot2 运行所需要的必备及额外配置项目的文件,使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。配置项需符合特殊格式或 json 序列化格式。详情可见[配置项](../plugin-advance/config#dotenv) 29 | 30 | 在**项目的根目录**中创建一个 `.env` 文件,并写入以下内容: 31 | 32 | ```python title=bot.py 33 | HOST=0.0.0.0 # 配置 NoneBot2 监听的 IP/主机名 34 | PORT=8080 # 配置 NoneBot2 监听的端口 35 | SUPERUSERS=["123456789", "987654321"] # 配置 NoneBot 超级用户 36 | NICKNAME=["awesome", "bot"] # 配置机器人的昵称 37 | COMMAND_START=["/", ""] # 配置命令起始字符 38 | COMMAND_SEP=["."] # 配置命令分割字符 39 | ``` 40 | 41 | 关于其中配置项的详细信息,可参考[配置项](../plugin-advance/config#dotenv) 42 | 43 | ## 加载插件 44 | 45 | 插件(`Plugin`)是 NoneBot2 中实现具体功能的最小单位,也是用户对事件进行处理的基础单位。如果在不加载任何插件的情况下启动项目,那么机器人是无法对你的任何输入做出响应的。 46 | 47 | 可参考[这里](../plugin-basic/create-and-load#加载插件)了解如何加载插件。 48 | 49 | 可参考[这里]()了解如何安装并加载第三方插件。 50 | 51 | 在不使用 nb-cli 时,我们将会介绍如何在入口文件中加载插件。 52 | 53 | ## 创建入口文件 54 | 55 | 入口文件(`ENTRYPOINT`)顾名思义,是用来初始化并启动此项目的文件。 56 | 57 | ::: tips 58 | 在使用 nb-cli 时入口文件的功能将会被 `nb run` 命令所替代,所以并不需要额外准备入口文件 59 | ::: 60 | 61 | 在**项目的根目录**中创建一个入口文件,例如 `bot.py`,并写入以下内容: 62 | 63 | ```python title=bot.py 64 | import nonebot 65 | from nonebot.adapters.YOUR_ADAPTER_NAME import Adapter as YOUR_ADAPTER_NAME 66 | 67 | 68 | nonebot.init() 69 | app = nonebot.get_asgi() 70 | 71 | # load plugins here 72 | nonebot.load_plugin('your_plugin') 73 | 74 | driver = nonebot.get_driver() 75 | driver.register_adapter(YOUR_ADAPTER_NAME) 76 | 77 | if __name__ == "__main__": 78 | nonebot.run(app="__mp_main__:app") 79 | ``` 80 | 81 | ::: danger 82 | 请将示例中的 `YOUR_ADAPTER_NAME` 替换为你所需要的适配器名!可参考[这里]()进行适配器的选择。 83 | 请根据你的需要修改**加载插件部分**(`nonebot.load_plugin('your_plugin')`)的代码,不要直接运行!可参考[这里](#加载插件)加载插件。 84 | ::: 85 | 86 | 在创建完毕并修改好适配器及插件后,我们便完成了入口文件部分的创建。 87 | 88 | ## 运行bot 89 | 90 | 在**项目的根目录**中,使用配置好环境的 Python 解释器执行入口文件,例如 `python bot.py`,便可在不使用 nb-cli 的情况下运行机器人了。 91 | -------------------------------------------------------------------------------- /tutorial/introduction/adapter.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2-tutorial/af6c2ed71bcfacdc54aa2c750dc77bf1be4f96c1/tutorial/introduction/adapter.md -------------------------------------------------------------------------------- /tutorial/introduction/driver.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2-tutorial/af6c2ed71bcfacdc54aa2c750dc77bf1be4f96c1/tutorial/introduction/driver.md -------------------------------------------------------------------------------- /tutorial/introduction/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | description: NoneBot2 机器人的构成 4 | --- 5 | 6 | # NoneBot2 机器人的构成 7 | 8 | 由 NoneBot2 搭建的机器人由以下几部分构成: 9 | 10 | 1. NoneBot2: 框架本体 11 | 2. Driver: 服务端驱动器 12 | 3. Adapter: 协议适配器 13 | 4. Plugin: 插件系统 14 | 15 | 除 NoneBot2 框架本体外,其他三者原理上都是可以自由选择与互相搭配的,但由于不同的协议、服务端以及其他种种原因,导致会有兼容性问题。通常情况下,上述几项中的后者会对前者有种类或版本的要求。 16 | 17 | 接下来的几节中,我们将对后三者进行初步的讲解。 18 | -------------------------------------------------------------------------------- /tutorial/introduction/plugin.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2-tutorial/af6c2ed71bcfacdc54aa2c750dc77bf1be4f96c1/tutorial/introduction/plugin.md -------------------------------------------------------------------------------- /tutorial/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | description: 欢迎使用NoneBot2 4 | --- 5 | 6 | # 欢迎使用NoneBot2 7 | 8 | NoneBot2 是一个现代化的强大的聊天机器人框架,其基于 Python [asyncio](https://docs.python.org/3/library/asyncio.html) 编写,并在异步机制的基础上进行了一定程度的同步函数兼容。同时拥有大量的开发者为其开发插件,使不会编程的小白也可以在不进行任何代码编写,仅需要完成环境配置及插件安装后便可正常使用。 9 | 10 | ## 如何正确阅读本文档 11 | 12 | 由于上述特点,本文档将会将用户分为三类: 13 | 14 | 1. plugin-user: 不进行**插件编写**,仅进行插件使用的用户。 15 | 2. basic-user: 进行基础插件的编写,可以实现简单的功能。 16 | 3. advance-user: 可完成较为复杂的插件编写,对框架较为熟悉。 17 | 18 | 针对以上用户,文档将会以不同的知识储备为基础进行编写,依次递增: 19 | 20 | 1. plugin-user: 需要有基础的搜索及理解力,拥有一定的计算机使用经验。 21 | 2. basic-user: 熟悉 Python 基础语法,初步了解 [asyncio](https://docs.python.org/3/library/asyncio.html),熟悉如何解决问题及智慧的提问。 22 | 3. advance-user: 熟悉 Python 高级语法,初步理解框架运作原理,熟练使用 [asyncio](https://docs.python.org/3/library/asyncio.html) 及其他常见开发工具和第三方库。 23 | 24 | ## 内容分类 25 | 26 | 针对以上分类,文档将分为三类进行 27 | -------------------------------------------------------------------------------- /tutorial/plugin-advance/call-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | description: 调用平台API 4 | --- 5 | 6 | # 调用平台API 7 | 8 | 在 NoneBot2 及对应的协议适配器中,可能存在部分未被收录的API或无法调用依赖注入(如[定时任务](scheduler))的情况。此时可以通过调用平台API的方式来进行信息的获取和发送等操作。 9 | 10 | ## 获取Bot实例 11 | 12 | 调用平台API的方法是 Bot 对象的一种方法,因此我们首先需要获得 Bot 实例。 13 | 14 | ```python 15 | from nonebot import get_bot 16 | 17 | bot = get_bot() # 获取bot列表中的第一个 bot 18 | bot = get_bot("bot_id") # 获取id为 bot_id 的bot 19 | ``` 20 | 21 | ## 调用API 22 | 23 | NoneBot 提供了两种方式来调用机器人平台 API。 24 | 25 | ```python 26 | from nonebot import get_bot 27 | 28 | bot = get_bot("bot_id") 29 | # 通过 bot.api_name() 的方法调用API 30 | result = await bot.get_user_info(user_id=12345678) 31 | # 通过 bot.call_api(api_name) 的方法调用API 32 | result = await bot.call_api("get_user_info", user_id=12345678) 33 | ``` 34 | 35 | ## 可被调用的API 36 | 37 | 可参考对应的协议适配器文档或平台开发文档。 38 | -------------------------------------------------------------------------------- /tutorial/plugin-advance/config.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | description: 配置项 4 | --- 5 | 6 | # 配置项 7 | 8 | NoneBot2 使用 [`pydantic`](https://pydantic-docs.helpmanual.io/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。配置项需符合特殊格式或 json 序列化格式。详情见 [`pydantic Field Type`](https://pydantic-docs.helpmanual.io/usage/types/) 文档。 9 | 10 | ## 配置项的加载 11 | 12 | 在 NoneBot2 中,我们可以把配置项通过来源简单分为 `直接传入`、`系统环境变量`、`dotenv` 三个,其加载优先级依次从高到低。 13 | 14 | ### 直接传入 15 | 16 | 在 NoneBot2 初始化的过程中,可以通过 `nonebot.init()` 传入任意合法的 Python 变量,也可以通过 `config` 在初始化完成后传入。 17 | 18 | 通常,在初始化前的传参会在 bot 的入口文件(通常为 `bot.py`)中进行,而初始化后的传参通常会在插件中进行。 19 | 20 | ```python 21 | import nonebot 22 | 23 | # 初始化前 24 | nonebot.init(custom_config3="config on init") 25 | 26 | # 初始化后 27 | config = nonebot.get_driver().config 28 | config.custom_config3 = "changed after init" 29 | config.custom_config4 = "new config after init" 30 | ``` 31 | 32 | ### 系统环境变量 33 | 34 | 在 `env` 中定义的配置项,也会在环境变量中进行寻找,如果发现同名配置项,将会覆盖 `env` 中所填入的值。例如在环境变量中 `CUSTOM_CONFIG="config in system environment variables"`,`env` 中 `USTOM_CONFIG="config in env file"`,那么在 NoneBot2 的配置中最终会被读取的为环境变量中的内容,即 `"config in system environment variables"`。 35 | 36 | 值得注意是,NoneBot2 不会自发读取未被定义的环境变量,如果需要读取某一环境变量需要在 `dotenv` 进行声明。 37 | 38 | ### dotenv 39 | 40 | 作为专门用配置项加载的功能,[`dotenv`](https://saurabh-kumar.com/python-dotenv/) 可以高效、统一的管理大量配置项,是我们最推荐的配置存放及加载的方式。 41 | 42 | NoneBot2 在启动时将会从系统环境变量或者 `.env` 文件中寻找变量 `ENVIRONMENT`(大小写不敏感),默认值为 `prod`。这将引导 NoneBot2 从系统环境变量或者 `.env.{ENVIRONMENT}` 文件中进一步加载具体配置。 43 | 44 | #### 配置项解析 45 | 46 | `.env` 相关文件的加载使用 [`dotenv`](https://saurabh-kumar.com/python-dotenv/) 语法,并使用 [Pydantic](https://pydantic-docs.helpmanual.io/) 以 JSON 格式进行配置处理。例如: 47 | 48 | ```bash 49 | # Default Configs 50 | HOST=0.0.0.0 # 配置 NoneBot2 监听的 IP/主机名 51 | PORT=8080 # 配置 NoneBot2 监听的端口 52 | SUPERUSERS=["123456789", "987654321"] # 配置 NoneBot 超级用户 53 | NICKNAME=["awesome", "bot"] # 配置机器人的昵称 54 | COMMAND_START=["/", ""] # 配置命令起始字符 55 | COMMAND_SEP=["."] # 配置命令分割字符 56 | 57 | # Custom Configs 58 | CUSTOM_CONFIG_STR="config in env file" 59 | CUSTOM_CONFIG_EMPTY_STR= 60 | CUSTOM_CONFIG_NONE 61 | ``` 62 | 63 | 其中包含的默认配置项可在[进阶 - 内置配置项](../../advanced/functions/builtin-config.md)中查看。 64 | 65 | 如果配置项值无法被解析为 JSON 元素(例如 `HOST=0.0.0.0`)将作为**字符串**处理。如果配置项取值为空(例如 `CUSTOM_CONFIG_EMPTY_STR=`)会被解析为**空字符串**。如果配置项没有取值(例如 `CUSTOM_CONFIG_NONE`)则会被解析为**空值**。 66 | 67 | #### .env 文件 68 | 69 | `.env` 文件是基础环境配置文件,该文件中的配置项在不同环境下都会被加载,但会被 `.env.{ENVIRONMENT}` 文件中的配置所**覆盖**。 70 | 71 | 现在,我们在 `.env` 文件中写入当前环境信息: 72 | 73 | ```bash 74 | # .env 75 | ENVIRONMENT=dev 76 | CUSTOM_CONFIG=common config # 这个配置项在任何环境中都会被加载 77 | ``` 78 | 79 | 如你所想,之后 NoneBot2 就会从 `.env.dev` 文件中加载环境变量。 80 | 81 | #### .env.* 文件 82 | 83 | `.env.*` 类似于预设,可以让用户在多套不同的配置方案中灵活切换,默认含有两套预设配置:`.env.dev`、`.env.prod`。 84 | 85 | NoneBot 默认会从 `.env.{ENVIRONMENT}` 文件加载配置,其中 `ENVIRONMENT` 来自于 `.env` 文件中的配置项,若无此配置则会采用默认值 `prod`。同时,在 NoneBot2 初始化时可以指定加载某个环境配置文件:`nonebot.init(_env_file=".env.dev")`,这将忽略你在 `.env` 中设置的 `ENVIRONMENT`。 86 | 87 | ## 读取配置项 88 | 89 | 配置项可以通过三种类型的对象获取:`driver`、`adapter`、`bot`。 90 | 91 | ```python 92 | import nonebot 93 | 94 | # 已有配置 custom_config = 'custom_config_info' 95 | 96 | # driver 97 | nonebot.get_driver().config.custom_config 98 | # bot 99 | nonebot.get_bot().config.custom_config 100 | # adapter 101 | nonebot.get_driver()._adapters["adapter_name"].config.custom_config 102 | ``` 103 | 104 | 以上三种方式均可读取配置项,任选其一即可。 105 | 106 | 例如,我们在配置项中含有 `SUPERUSERS=["1234567890","0987654321"]` 项,那么我们可以通过以下方式获取此项的内容: 107 | 108 | ```python 109 | import nonebot 110 | 111 | su_list = nonebot.get_driver().config.superusers 112 | # su_list == set(["1234567890", "0987654321"]) 113 | ``` 114 | 115 | ## 使用配置模型 116 | 117 | 在一个涉及大量配置项的插件中,通过直接读取配置项的方式显然并不高效,同时由于额外的全局配置项没有预先定义的问题,导致开发时编辑器无法提示字段与类型,以及运行时没有对配置项直接进行检查。那么就需要一种方式来规范定义插件配置项。 118 | 119 | 在 NoneBot2 中,我们使用强大高效的 `Pydantic` 来定义配置模型,这个模型可以被用于配置的读取和类型检查等。例如在插件目录中新建 `config.py` 来定义一个模型: 120 | 121 | ```python title=config.py 122 | from typing import Optional 123 | 124 | from pydantic import BaseSettings 125 | 126 | 127 | class Config(BaseSettings): 128 | 129 | custom_config_int: Optional[int] = 123456 130 | custom_config_str: Optional[str] = None 131 | custom_config_bool: bool = True 132 | custom_config_dict: dict = {"key": "value"} 133 | ``` 134 | 135 | 定义完成配置模型后,我们可以在插件加载时获取全局配置,导入插件自身的配置模型: 136 | 137 | ```python title=__init__.py 138 | from nonebot import get_driver 139 | 140 | from .config import Config 141 | 142 | plugin_config = Config.parse_obj(get_driver().config) 143 | ``` 144 | 145 | 然后,我们便可以从 `plugin_config` 中读取配置了,例如 `plugin_config.custom_config_int`。 146 | 147 | 这种方式可以简洁、高效、统一的定义大量配置信息,并可以设置默认取值,防止由于配置项丢失所造成的插件崩溃。 148 | -------------------------------------------------------------------------------- /tutorial/plugin-advance/custom-api.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | description: 自定义API 4 | --- 5 | 6 | # 自定义API 7 | 8 | 由于 NoneBot2 可以使用 `ReverseDriver` (即服务端框架)来进行驱动,因此可以将 NoneBot2 来作为一个服务端程序来提供 API 接口等功能。 9 | 10 | 在扩展 API 之前,你首先需要确保 NoneBot2 使用的是 `ReverseDriver`,详情可以参考 [选择驱动器]()。下面我们以 FastAPI 驱动器为例,来演示如何添加自定义 API。 11 | 12 | ## 获取 APP 实例 13 | 14 | 在定义 API 接口之前,需要先获取到驱动器框架的 APP 实例。 15 | 16 | ```python {4} 17 | import nonebot 18 | from fastapi import FastAPI 19 | 20 | app: FastAPI = nonebot.get_app() 21 | 22 | 23 | @app.get("/api") 24 | async def custom_api(): 25 | return {"message": "Hello, world!"} 26 | ``` 27 | 28 | ## 添加接口 29 | 30 | 在获取到当前驱动器的 APP 实例后,即可以直接使用驱动器框架提供的方法来添加 API 接口。 31 | 32 | 在下面的代码中,我们添加了一个 `GET` 类型的 `/api` 接口,具体方法参考 [FastAPI 文档](https://fastapi.tiangolo.com/)。 33 | 34 | ```python {6-8} 35 | import nonebot 36 | from fastapi import FastAPI 37 | 38 | app: FastAPI = nonebot.get_app() 39 | 40 | 41 | @app.get("/api") 42 | async def custom_api(): 43 | return {"message": "Hello, world!"} 44 | ``` 45 | -------------------------------------------------------------------------------- /tutorial/plugin-advance/hook.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | description: 钩子函数 4 | --- 5 | 6 | # 钩子函数 7 | 8 | [钩子编程](https://zh.wikipedia.org/wiki/%E9%92%A9%E5%AD%90%E7%BC%96%E7%A8%8B) 9 | 10 | > 钩子编程(hooking),也称作“挂钩”,是计算机程序设计术语,指通过拦截软件模块间的函数调用、消息传递、事件传递来修改或扩展操作系统、应用程序或其他软件组件的行为的各种技术。处理被拦截的函数调用、事件、消息的代码,被称为钩子(hook)。 11 | 12 | 在 NoneBot2 中有一系列预定义的钩子函数,分为两类:**全局钩子函数**和**事件钩子函数**,这些钩子函数可以用装饰器的形式来使用。 13 | 14 | ## 全局钩子函数 15 | 16 | 全局钩子函数是指 NoneBot2 针对其本身运行过程的钩子函数。 17 | 18 | 这些钩子函数是由其后端驱动 `Driver` 来运行的,故需要先获得全局 `Driver` 对象: 19 | 20 | ```python 21 | from nonebot import get_driver 22 | 23 | 24 | driver=get_driver() 25 | ``` 26 | 27 | 共分为六种函数: 28 | 29 | ### 启动准备 30 | 31 | 这个钩子函数会在 NoneBot2 启动时运行。 32 | 33 | ```python 34 | @driver.on_startup 35 | async def do_something(): 36 | pass 37 | ``` 38 | 39 | ### 终止处理 40 | 41 | 这个钩子函数会在 NoneBot2 终止时运行。 42 | 43 | ```python 44 | @driver.on_shutdown 45 | async def do_something(): 46 | pass 47 | ``` 48 | 49 | ### Bot 连接处理 50 | 51 | 这个钩子函数会在 `Bot` 通过 websocket 连接到 NoneBot2 时运行。 52 | 53 | ```python 54 | @driver.on_bot_connect 55 | async def do_something(bot: Bot): 56 | pass 57 | ``` 58 | 59 | ### bot 断开处理 60 | 61 | 这个钩子函数会在 `Bot` 断开与 NoneBot2 的 websocket 连接时运行。 62 | 63 | ```python 64 | @driver.on_bot_disconnect 65 | async def do_something(bot: Bot): 66 | pass 67 | ``` 68 | 69 | ### bot api 调用钩子 70 | 71 | 这个钩子函数会在 `Bot` 调用 API 时运行。 72 | 73 | ```python 74 | from nonebot.adapters import Bot 75 | 76 | 77 | @Bot.on_calling_api 78 | async def handle_api_call(bot: Bot, api: str, data: Dict[str, Any]): 79 | pass 80 | ``` 81 | 82 | ### bot api 调用后钩子 83 | 84 | 这个钩子函数会在 `Bot` 调用 API 后运行。 85 | 86 | ```python 87 | from nonebot.adapters import Bot 88 | 89 | 90 | @Bot.on_called_api 91 | async def handle_api_result( 92 | bot: Bot, 93 | exception: Optional[Exception], 94 | api: str, 95 | data: Dict[str, Any], 96 | result: Any, 97 | ): 98 | pass 99 | ``` 100 | 101 | ## 事件钩子函数 102 | 103 | 这些钩子函数指的是影响 NoneBot2 进行**事件处理**的函数, 这些函数可以认为跟普通的事件处理函数一样,接受相应的参数。可分为 `事件处理`、`运行处理` 两大类,分别包含 `预处理`、`后处理` 两种不同的类型。因此,事件钩子函数共 `事件预处理`、`事件后处理`、`运行预处理`、`运行后处理` 四种类型。 104 | 105 | 其中,`预处理` 与 `后处理` 很好理解,分别会在其对应的主体的开始和结束时被调用。`事件处理` 与 `运行处理` 二者的区别就在于主体的定义分别为 `Event` 与 `matcher`,分别对应 `事件触发与结束` 和 `事件响应器触发与结束`。 106 | 107 | 在这个简化的流程图中,我们可以看到 `事件处理` 在事件开始和结束时被触发,而 `运行处理` 在事件响应器开始和结束时被触发。可以推断出以下两点: 108 | 109 | 1. `事件处理` 在一个事件中仅可以被调用一次,而 `运行处理` 可以根据“可以响应该会话的事件响应器数量”被调用大于等于0次。 110 | 2. `事件处理` 类型的钩子函数并不会对应到具体的事件响应器中,因此与 `matcher` 运行状态相关的函数将不可用,如 `matcher.finish()`,而 `运行处理` 可以使用 `matcher` 运行状态相关的函数。 111 | 112 | 如果需要在事件处理钩子函数中打断整个对话的执行,可以使用 `IgnoredException` 进行打断,例如: 113 | 114 | ```python 115 | from nonebot.exception import IgnoredException 116 | 117 | 118 | @event_preprocessor 119 | async def do_something(): 120 | raise IgnoredException("reason") 121 | ``` 122 | 123 | 关于 `IgnoredException` 可参考[进阶 - 内置异常类型](../../advanced/functions/builtin-exception)了解更多信息。 124 | 125 | ### 事件预处理 126 | 127 | 这个钩子函数会在 `Event` 上报到 NoneBot2 时运行 128 | 129 | ```python 130 | from nonebot.message import event_preprocessor 131 | 132 | @event_preprocessor 133 | async def do_something(): 134 | pass 135 | ``` 136 | 137 | ### 事件后处理 138 | 139 | 这个钩子函数会在 NoneBot2 处理 `Event` 后运行 140 | 141 | ```python 142 | from nonebot.message import event_postprocessor 143 | 144 | @event_postprocessor 145 | async def do_something(): 146 | pass 147 | ``` 148 | 149 | ### 运行预处理 150 | 151 | 这个钩子函数会在 NoneBot2 运行 `matcher` 前运行。 152 | 153 | ```python 154 | from nonebot.message import run_preprocessor 155 | 156 | @run_preprocessor 157 | async def do_something(): 158 | pass 159 | ``` 160 | 161 | ### 运行后处理 162 | 163 | 这个钩子函数会在 NoneBot2 运行 `matcher` 后运行。 164 | 165 | ```python 166 | from nonebot.message import run_postprocessor 167 | 168 | @run_postprocessor 169 | async def do_something(): 170 | pass 171 | ``` 172 | -------------------------------------------------------------------------------- /tutorial/plugin-advance/nested-plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | description: 嵌套插件 4 | --- -------------------------------------------------------------------------------- /tutorial/plugin-advance/require.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 跨插件访问 4 | --- 5 | 6 | # 跨插件访问 7 | 8 | `require` 是 NoneBot2 中用于获取其他插件中对象的一种函数。同时,此函数也可保证加载正确的顺序,被广泛用于需要依赖于其他插件中对象的场景中。 9 | 10 | ::: tips 11 | 在 NoneBot2-alpha 版中,该函数需要搭配 `export` 进行使用,这一特性在 NoneBot2-beta 及之后的版本中被移除。关于在旧版本中 `export` 的使用可以参考 [NoneBot2-a16 文档](https://61d3d9dbcadf413fd3238e89--nonebot2.netlify.app/advanced/export-and-require.html)。 12 | ::: 13 | 14 | ## 基础使用 15 | 16 | 假设,在 `plugin_a` 中存在一个常量 `PLUGIN_NAME`、函数 `add_a_and_b` 和类 `foo`。 17 | 18 | ```python title=plugin_a.py 19 | PLUGIN_NAME = "plugin a" 20 | 21 | 22 | def add_a_and_b(a: int, b: int) -> int: 23 | return a + b 24 | 25 | 26 | class Foo: 27 | def sub_a_and_b(a: int, b: int) -> int: 28 | return a - b 29 | ``` 30 | 31 | 我们需要在 `plugin_b` 插件中使用 `plugin_a` 中的对象时,便可用 `require` 进行获取。 32 | 33 | ```python title=plugin_b.py 34 | from nonebot import require 35 | 36 | require("plugin_a") 37 | 38 | from plugin_a import PLUGIN_NAME, add_a_and_b, Foo 39 | 40 | 41 | print(PLUGIN_NAME) 42 | print(add_a_and_b(114, 514)) 43 | print(Foo.sub_a_and_b(1919, 810)) 44 | ``` 45 | 46 | 在 `plugin_b` 载入时,NoneBot2 会尝试在已加载的插件中搜索 `plugin_a`。若 `plugin_a` 未被加载则对其进行载入。确保其正确载入后便返回对应插件的 `plugin.module` 以供 `plugin_b` 调用。 47 | 48 | 由于 `require` 的返回值是目标插件的 `plugin.module`,因此函数、变量和类等 `一级对象` 是可以直接调用的,但对于闭包中的方法、类的方法等 `二级对象` 是无法直接引用的,需要引入其对于的 `一级对象` 并进行调用。 49 | 50 | ```python title=plugin_b.py 51 | from nonebot import require 52 | 53 | # 正确 54 | add_a_and_b = require("plugin_a").add_a_and_b 55 | 56 | # 错误 57 | sub_a_and_b = require("plugin_a").foo.sub_a_and_b 58 | ``` 59 | 60 | ## 确保加载顺序 61 | 62 | 如上文所提,在插件 `require` 其他插件时,NoneBot2 会尝试在已加载的插件中搜索,如果存在则直接返回对应插件的 `plugin.module`,若不存在则尝试加载对应的插件,成功则返回对应插件的 `plugin.module`,失败则抛出 `RuntimeError`。 63 | 64 | 相对于使用 `load_plugin` 等方法加载插件,`require` 加载可以避免插件重复加载,这使得对于拥有复杂依赖关系的插件不会因加载顺序的问题或插件重复加载导致的错误。 65 | -------------------------------------------------------------------------------- /tutorial/plugin-advance/rule.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | description: 自定义响应规则 4 | --- 5 | 6 | # 自定义响应规则 7 | 8 | 机器人在实际应用中,往往会接收到多种多样的事件类型,NoneBot2 提供了可自定义的响应规则 ── `Rule`。 9 | 10 | 在 [进阶 - 事件响应器及辅助函数](../../advanced/functions/matcher-advanced.md#事件响应规则-rule) 中,我们提到了响应规则是一个 `Rule` 对象。类似于 `Message` 与 `MessageSegement` 的关系,`Rule` 也有自己的子项 `RuleChecker`。 11 | 12 | ## `RuleChecker` 13 | 14 | `RuleChecker` 是一个返回值为 `Bool` 类型的依赖函数,即 `RuleChecker` 支持依赖注入。例如: 15 | 16 | ```python {4-5} 17 | from nonebot import on_message 18 | from nonebot.adapters import Event 19 | 20 | 21 | async def user_checker(event: Event) -> bool: 22 | return event.get_user_id() == "123123" 23 | 24 | 25 | matcher = on_message(rule=user_checker) 26 | ``` 27 | 28 | 在上面的代码中,我们定义了一个函数 `user_checker`,它检查事件的用户 ID 是否等于 `"123123"`。这个函数 `user_checker` 即为一个 `RuleChecker`。 29 | 30 | ## `Rule` 31 | 32 | `Rule` 是若干个 `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查通过时匹配成功。 33 | 34 | ```python {4-5,7-8,10} 35 | from nonebot import on_message 36 | from nonebot.adapters import Event 37 | 38 | 39 | async def user_checker(event: Event) -> bool: 40 | return event.get_user_id() == "123123" 41 | 42 | 43 | async def message_checker(event: Event) -> bool: 44 | return event.get_plaintext() == "hello" 45 | 46 | 47 | rule = Rule(user_checker, message_checker) 48 | matcher = on_message(rule=rule) 49 | ``` 50 | 51 | 在上面的代码中,我们定义了两个函数 `user_checker` 和 `message_checker`,它们检查事件的用户 ID 是否等于 `"123123"`,以及消息的内容是否等于 `"hello"`。随后,我们定义了一个 `Rule` 对象,它包含了这两个函数。 52 | 53 | ## 合并响应规则 54 | 55 | 在定义响应规则时,我们可以将规则进行细分,来更好地复用规则。而在使用时,我们需要合并多个规则。除了使用 `Rule` 对象来组合多个 `RuleChecker` 外,我们还可以对 `Rule` 对象进行合并。 56 | 57 | ```python {4-6} 58 | rule1 = Rule(foo_checker) 59 | rule2 = Rule(bar_checker) 60 | 61 | rule = rule1 & rule2 62 | rule = rule1 & bar_checker 63 | rule = foo_checker & rule2 64 | ``` 65 | 66 | 同时,你也无需担心合并了一个 `None` 值,`Rule` 会忽略 `None` 值。 67 | 68 | ```python 69 | assert (rule & None) is rule 70 | ``` 71 | -------------------------------------------------------------------------------- /tutorial/plugin-advance/scheduler.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | description: 定时任务 4 | --- 5 | 6 | # 定时任务 7 | 8 | [APScheduler](https://apscheduler.readthedocs.io/en/3.x/) 是一个Python第三方库,其强大的定时任务功能被广泛应用于各个场景。在 NoneBot2 中,定时任务作为一个额外功能,依赖于基于 APScheduler 开发的 [`nonebot_plugin_apscheduler`](https://github.com/nonebot/plugin-apscheduler) 插件进行支持。 9 | 10 | ## 安装 nonebot_plugin_apscheduler 11 | 12 | 由于 `nonebot_plugin_apscheduler` 并不作为 NoneBot2 的标准功能,并不会随之一并安装。使用前请先自行安装 `nonebot_plugin_apscheduler` 插件。可参考 ***这里*** 来了解并选择安装插件的方式。 13 | 14 | 15 | 16 | ```bash 17 | # 使用 nb-cli (推荐) 18 | nb plugin install nonebot-plugin-apscheduler 19 | # 使用 poetry 20 | poetry add nonebot-plugin-apscheduler 21 | # 使用 pip 22 | pip install nonebot-plugin-apscheduler 23 | ``` 24 | 25 | ## 使用 nonebot_plugin_apscheduler 26 | 27 | `nonebot_plugin_apscheduler` 本质上是对 [APScheduler](https://apscheduler.readthedocs.io/en/3.x/) 进行了封装以适用于 NoneBot2 开发,因此其使用方式与 APScheduler 本身并无显著区别。在此我们会简要介绍其调用方法,更多的使用方面的功能请参考[APScheduler 官方文档](https://apscheduler.readthedocs.io/en/3.x/userguide.html)。 28 | 29 | ### 导入 scheduler 对象 30 | 31 | 由于 `nonebot_plugin_apscheduler` 作为插件,引起需要在使用前对其进行**加载**并**调用**其中的 `scheduler` 对象来创建定时任务。使用 `require` 方法可轻松完成这一过程。可参考 [跨插件访问](require) 一节进行了解。 32 | 33 | 首先我们先来演示一种较老的写法: 34 | 35 | ```python 36 | from nonebot import require 37 | 38 | scheduler = require("nonebot_plugin_apscheduler").scheduler 39 | ``` 40 | 41 | 在上述示例中获取的 `scheduler` 即为 [AsyncIOScheduler](https://apscheduler.readthedocs.io/en/3.x/modules/schedulers/asyncio.html#apscheduler.schedulers.asyncio.AsyncIOScheduler) 对象。但遗憾的是,此方法引入的 `scheduler` 对象无法正确的被编辑器识别,因此无法直接获取类型提示等信息。 42 | 43 | 同时,我们也可以使用另一种在 NoneBot2-beta 版**新引入**的方法方法来获取 `scheduler` 对象。 44 | 45 | ```python 46 | from nonebot import require 47 | 48 | require("nonebot_plugin_apscheduler") 49 | 50 | from nonebot_plugin_apscheduler import scheduler 51 | ``` 52 | 53 | 相对第一种方法,在使用第二种方法时可以在编辑器(例如 VSCode)中获得类型提示等信息,可以帮助我们更轻松的编写代码,是较为推荐的引入方式。 54 | 55 | ### 添加定时任务 56 | 57 | 在 [APScheduler 官方文档](https://apscheduler.readthedocs.io/en/3.x/userguide.html#adding-jobs) 中提供了以下两种直接添加任务的方式: 58 | 59 | ```python 60 | from nonebot import require 61 | 62 | require("nonebot_plugin_apscheduler") 63 | 64 | from nonebot_plugin_apscheduler import scheduler 65 | 66 | # 基于装饰器的方式(推荐) 67 | @scheduler.scheduled_job("cron", hour="*/2", id="job_0", args=[1], kwargs={arg2: 2}) 68 | async def run_every_2_hour(arg1, arg2): 69 | pass 70 | 71 | 72 | # 基于add_job方法的方式 73 | def run_every_day(arg1, arg2): 74 | pass 75 | 76 | 77 | scheduler.add_job( 78 | run_every_day, "interval", days=1, id="job_1", args=[1], kwargs={arg2: 2} 79 | ) 80 | ``` 81 | 82 | ::: warning 83 | 由 APScheduler 的定时任务并不是 **由事件响应器所触发的事件**,因此其处理函数无法同[事件处理依赖](../plugin-basic/handler#事件处理函数)一样通过[依赖注入](../plugin-basic/get-data#认识依赖注入)获取上下文信息,也无法通过事件响应器对象的方法进行任何操作,因此我们需要使用[调用平台API](call-api)的方式来获取信息或收发消息。 84 | 相对于事件处理依赖而言,编写定时任务更像是编写普通的函数,需要我们自行获取信息以及发送信息,请务必**不要**将事件处理依赖的特殊语法用于定时任务! 85 | ::: 86 | 87 | 关于 APScheduler 的更多使用方法,可以参考 [APScheduler 官方文档](https://apscheduler.readthedocs.io/en/3.x/index.html) 进行了解。 88 | 89 | ### 配置项 90 | 91 | #### apscheduler_autostart 92 | 93 | - **类型**: `bool` 94 | - **默认值**: `True` 95 | 96 | 是否自动启动 `scheduler` ,若不启动需要自行调用 `scheduler.start()`。 97 | 98 | #### apscheduler_log_level 99 | 100 | - **类型**: `int` 101 | - **默认值**: `30` 102 | 103 | apscheduler 输出的日志等级 104 | 105 | - `WARNING` = `30` (默认) 106 | - `INFO` = `20` 107 | - `DEBUG` = `10` (只有在开启 nonebot 的 debug 模式才会显示 debug 日志) 108 | 109 | #### apscheduler_config 110 | 111 | - **类型**: `dict` 112 | - **默认值**: `{ "apscheduler.timezone": "Asia/Shanghai" }` 113 | 114 | `apscheduler` 的相关配置。参考 [配置 scheduler](https://apscheduler.readthedocs.io/en/latest/userguide.html#scheduler-config), [配置参数](https://apscheduler.readthedocs.io/en/latest/modules/schedulers/base.html#apscheduler.schedulers.base.BaseScheduler) 115 | 116 | 配置需要包含 `apscheduler.` 作为前缀,例如 `apscheduler.timezone`。 117 | -------------------------------------------------------------------------------- /tutorial/plugin-basic/create-and-load.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | description: 01_创建并加载插件 4 | --- 5 | 6 | # 创建并加载插件 7 | 8 | ## 插件结构 9 | 10 | 在正式编写插件之前,首先我们需要了解一下插件的概念。 11 | 12 | 在 NoneBot2 中,插件可以是 Python 的一个[模块](https://docs.python.org/zh-cn/3/tutorial/modules.html) `module`,也可以是一个[包](https://docs.python.org/zh-cn/3/tutorial/modules.html#packages) `package` 。NoneBot2 会在导入时对这些模块或包做一些特殊的处理使得他们成为一个插件。插件间应尽量减少耦合,可以进行有限制的插件间调用,NoneBot2 能够正确解析插件间的依赖关系。 13 | 14 | ### 模块插件(单文件形式) 15 | 16 | 一个 `.py` 文件即可被称为模块插件了,例如: 17 | 18 | ```tree title=Project 19 | 📂 plugins 20 | └── 📜 foo.py 21 | ``` 22 | 23 | 这个时候 `foo.py` 已经可以被称为一个插件了,尽管它还什么都没做。 24 | 25 | ### 包插件(文件夹形式) 26 | 27 | 一个包含了 `__init__.py` 文件的文件夹即可被称为包插件了,例如: 28 | 29 | ```tree title=Project 30 | 📂 plugins 31 | └── 📂 foo 32 | └── 📜 __init__.py 33 | ``` 34 | 35 | 这个时候 `foo` 就是一个合法的 Python 包了,同时也是合法的 NoneBot2 插件,插件内容可以在 `__init__.py` 中编写。 36 | 37 | ## 插件示例 38 | 39 | 这里我们将展示 NoneBot2 内置插件 `echo` 的内容以及其实际效果: 40 | 41 | ```python title=echo.py 42 | from nonebot.rule import to_me 43 | from nonebot.adapters import Message 44 | from nonebot.params import CommandArg 45 | from nonebot.plugin import on_command 46 | 47 | echo = on_command("echo", to_me()) 48 | 49 | 50 | @echo.handle() 51 | async def echo_escape(message: Message = CommandArg()): 52 | await echo.send(message=message) 53 | ``` 54 | 55 | 在[配置项](../plugin-advance/config)中存在 `COMMAND_START=["/", "!!"]` 的情况下,我们私聊机器人可见: 56 | 57 | 58 | 59 | ## 加载插件 60 | 61 | ::: danger 62 | 请勿在插件被加载前 `import` 插件模块,这会导致 NoneBot2 无法将其转换为插件而损失部分功能。 63 | ::: 64 | 65 | 加载插件通常在机器人的入口文件进行,例如在[创建项目]()中创建的项目中的 `bot.py` 文件。在 NoneBot2 初始化完成后即可加载插件。 66 | 67 | ```python title=bot.py {5} 68 | import nonebot 69 | 70 | nonebot.init() 71 | 72 | # load your plugin here 73 | 74 | nonebot.run() 75 | ``` 76 | 77 | 加载插件的方式有多种,但在底层的加载逻辑是一致的。以下是为加载插件提供的几种方式: 78 | 79 | ### `load_plugin` 80 | 81 | 通过点分割模块名称或使用 [`pathlib`](https://docs.python.org/zh-cn/3.8/library/pathlib.html) 的 `Path` 对象来加载插件。通常用于加载单个插件或者是第三方插件。例如: 82 | 83 | ```python 84 | from pathlib import Path 85 | 86 | nonebot.load_plugin("path.to.your.plugin") 87 | nonebot.load_plugin(Path("./path/to/your/plugin.py")) 88 | ``` 89 | 90 | ### `load_plugins` 91 | 92 | 加载传入插件目录中的所有插件,通常用于加载一系列本地编写的插件。例如: 93 | 94 | ```python 95 | nonebot.load_plugins("src/plugins", "path/to/your/plugins") 96 | ``` 97 | 98 | ::: warning 99 | 请注意,插件所在目录应该为相对机器人 **入口文件(通常为 bot.py)** 可导入的,例如与入口文件在 **同一目录** 下。 100 | ::: 101 | 102 | ### `load_all_plugins` 103 | 104 | 这种加载方式是以上两种方式的混合,加载所有传入的插件模块名称,以及所有给定目录下的插件。例如: 105 | 106 | ```python 107 | nonebot.load_all_plugins(["path.to.your.plugin"], ["path/to/your/plugins"]) 108 | ``` 109 | 110 | ### `load_from_json` 111 | 112 | 通过 JSON 文件加载插件,是 [`load_all_plugins`](#load_all_plugins) 的 JSON 变种。通过读取 JSON 文件中的 `plugins` 字段和 `plugin_dirs` 字段进行加载。例如: 113 | 114 | ```json title=plugin_config.json 115 | { 116 | "plugins": ["path.to.your.plugin"], 117 | "plugin_dirs": ["path/to/your/plugins"] 118 | } 119 | ``` 120 | 121 | ```python 122 | nonebot.load_from_json("plugin_config.json", encoding="utf-8") 123 | ``` 124 | 125 | ::: tips 126 | 如果 JSON 配置文件中的字段无法满足你的需求,可以使用 [`load_all_plugins`](#load_all_plugins) 方法自行读取配置来加载插件。 127 | ::: 128 | 129 | ### `load_from_toml` 130 | 131 | 通过 TOML 文件加载插件,是 [`load_all_plugins`](#load_all_plugins) 的 TOML 变种。通过读取 TOML 文件中的 `[tool.nonebot]` Table 中的 `plugins` 和 `plugin_dirs` Array 进行加载。例如: 132 | 133 | ```toml title=plugin_config.toml 134 | [tool.nonebot] 135 | plugins = ["path.to.your.plugin"] 136 | plugin_dirs = ["path/to/your/plugins"] 137 | ``` 138 | 139 | ```python 140 | nonebot.load_from_toml("plugin_config.toml", encoding="utf-8") 141 | ``` 142 | 143 | ::: tips 144 | 如果 TOML 配置文件中的字段无法满足你的需求,可以使用 [`load_all_plugins`](#load_all_plugins) 方法自行读取配置来加载插件。 145 | ::: 146 | 147 | ### `load_builtin_plugin` 148 | 149 | 加载一个内置插件,传入的插件名必须为 NoneBot2 内置插件。该方法是 [`load_plugin`](#load_plugin) 的封装。例如: 150 | 151 | ```python 152 | nonebot.load_builtin_plugin("echo") 153 | ``` 154 | 155 | ### `load_builtin_plugins` 156 | 157 | 加载传入插件目录中的所有内置插件 158 | 159 | ### 其他加载方式 160 | 161 | 有关其他插件加载的方式,可参考 [跨插件访问](../plugin-advance/require.md) 和 [嵌套插件](../plugin-advance/nested-plugin.md))。 162 | -------------------------------------------------------------------------------- /tutorial/plugin-basic/get-data.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | description: 04_获取信息 4 | --- 5 | 6 | # 获取信息 7 | 8 | 在 NoneBot2 中,获取事件相关信息的途径有很多,而目前最主要的途径有两种:`调用平台API` 和 `依赖注入`。本章节中我们将介绍通过 `依赖注入` 获取信息的方法,`调用平台API`的使用方法将在[调用平台API](../plugin-advance/call-api)中进行介绍。 9 | 10 | 值得注意的是,`调用平台API` 并不是专职用于获取信息所使用的,其更多是用于提供一些平台特殊功能,在获取信息方面也是作为 `依赖注入` 的补充进行少量使用的。大部分情况下 `依赖注入` 是获取信息的最佳途径。 11 | 12 | ## 认识依赖注入 13 | 14 | 在事件处理流程中,事件响应器具有自己独立的上下文,例如:当前的事件、机器人自身的信息或由其他处理依赖或事件处理流程所新增的参数等。在 NoneBot2 中,这些数据可以根据用户的需求,通过依赖注入的方式,被事件响应器的上下文注入到事件处理函数中。 15 | 16 | 相对于传统的信息获取方法,通过依赖注入获取信息的最大特色在于**按需获取**。如果该事件处理函数不需要任何额外信息即可运行,那么可以不进行依赖注入。如果事件处理函数需要额外的数据,可以通过依赖注入的方式灵活的标注出需要的依赖,在函数运行时便会被按需注入。 17 | 18 | ## 使用依赖注入获取上下文信息 19 | 20 | 使用依赖注入获取上下文信息的方法十分简单,我们仅需要在函数的参数中声明所需的依赖,并正确的将函数添加为处理依赖即可。例如: 21 | 22 | ```python 23 | from nonebot import on_message 24 | from nonebot.params import EventToMe 25 | 26 | matcher = on_message() 27 | 28 | 29 | @matcher.handle() 30 | async def _(foo: bool = EventToMe()): 31 | return foo 32 | ``` 33 | 34 | 在事件响应器被触发后,这个函数便可以通过依赖注入的方式获取到对应的信息。 35 | 36 | ```python title=weather.py 37 | from nonebot.adapters import Message 38 | from nonebot.params import CommandArg 39 | from nonebot.plugin import on_command 40 | 41 | matcher = on_command("天气", priority=10) # 注册事件响应器 42 | 43 | 44 | @matcher.handle() # 为事件响应器添加一个处理函数 45 | async def handle_func(args: Message = CommandArg()): 46 | city = args.extract_plain_text() # 获取用户发送的命令信息 47 | if city in ["广州", "上海"]: 48 | await matcher.finish(f"今天 {city} 的天气是...") 49 | else: 50 | await matcher.finish(f"您输入的城市 {city} 暂不支持查询,请重试...") 51 | ``` 52 | 53 | 如上方示例所示,我们使用了 `args` 作为变量名,获取了注入的 `CommandArg()`,也就是 `命令型消息命令后跟随的参数` 项。在这个示例中,我们获得的参数会被检查是否有效,对无效参数则会结束事件。 54 | 55 | 56 | 57 | 目前 NoneBot2 共提供了多种可供注入的依赖,具体内容可参考 [进阶 - 内置依赖注入](../../advanced/functions/builtin-dependency-injection)。 58 | -------------------------------------------------------------------------------- /tutorial/plugin-basic/handler.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | description: 03_事件处理流程 4 | --- 5 | 6 | # 事件处理流程 7 | 8 | 在我们收到事件,并被某个事件响应器正确响应后,便正式开启了对于这个事件的 **事件处理流程**。 9 | 10 | ## 认识事件处理流程 11 | 12 | 就像我们在解决问题时需要遵循流程一样,处理一个事件也需要一套流程。在事件响应器对一个事件进行响应之后,会依次执行 `事件处理函数`, 13 | 14 | 简单来说,事件处理流程并不是一个函数、一个对象或一个方法,而是一整套由开发者设计的流程。 15 | 16 | 在这个流程中,我们**目前**只需要了解两个概念——“事件处理函数” 和 “事件响应器操作”。 17 | 18 | ### 事件处理函数 19 | 20 | 在事件响应器中,事件处理流程由一个或多个 `事件处理函数` 组成,这些事件处理函数将会按照顺序依次对事件进行处理,直到全部执行完成或被中断。通常我们会采用事件响应器的 `事件处理函数装饰器` 来添加这些 `事件处理函数`。 21 | 22 | 顾名思义,事件响应器的 `事件处理函数装饰器` 是 [decorator](https://docs.python.org/zh-cn/3/glossary.html#term-decorator),那么它的使用方法也同[函数定义](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#function-definitions)中所展示的包装用法相同。例如: 23 | 24 | ```python title=foo.py {6,8} 25 | from nonebot.plugin import on_command 26 | 27 | matcher = on_command("ping") 28 | 29 | 30 | @matcher.handle() 31 | async def handle_func(): 32 | pass # do something here 33 | ``` 34 | 35 | 如上方示例所示,我们使用 `matcher` 响应器的 `handle` 装饰器装饰了一个函数 `handle_func`。`handle_func` 函数会被添加到 `matcher` 的事件处理流程中。 36 | 37 | ::: tips 38 | 如果这个概念目前仍难以理解,你目前仅需要知道这种方法可以在事件响应器被调起之后自动调用 `handle_func` 来对事件进行处理即可。 39 | ::: 40 | 41 | 目前 NoneBot2 共提供了 `handle`、`receive` 和 `got` 三种事件处理函数装饰器,同时也可通过其他手段为事件响应器添加事件处理函数,具体内容可参考 [进阶 - 事件处理进阶](../../advanced/functions/handler-advanced)。 42 | 43 | ### 事件响应器操作 44 | 45 | 在事件处理流程中,我们可以使用事件响应器操作来进行一些交互或改变事件处理流程,例如向用户发送消息或提前结束事件处理流程等。 46 | 47 | 事件响应器操作实际上同事件处理函数装饰器一样,也是作为 `Matcher` 类的[类方法](https://docs.python.org/zh-cn/3/library/functions.html?highlight=classmethod#classmethod)存在,因此事件响应器操作的调用方法也是 `Matcher.func()` 的形式。不过不同的是,事件响应器操作并不是装饰器,因此并不需要@进行标注。 48 | 49 | ```python title=weather.py {8,9} 50 | from nonebot.plugin import on_command 51 | 52 | matcher = on_command("天气", priority=10) # 注册事件响应器 53 | 54 | 55 | @matcher.handle() # 为事件响应器添加一个处理函数 56 | async def handle_func(): 57 | # await matcher.send("天气是...") 58 | await matcher.finish("天气是...") 59 | ``` 60 | 61 | 如上方示例所示,我们使用 `matcher` 响应器的 `finish` 事件响应器操作方法向用户回复了 `天气是...` 并结束了事件处理流程。效果如下图所示: 62 | 63 | 64 | 65 | 值得注意的是,在使用了 `finish` 方法之后,NoneBot2 会在向用户回复其中的消息内容后抛出 `FinishedException` 来结束事件事件响应流程的后续操作。也就是说,如果在 `finish` 被执行后,后续的程序是不会被执行的(如果你需要回复用户消息但不想事件处理流程结束,可以使用被注释的部分中展示的 `send` 方法)。 66 | 67 | ::: danger 68 | 由于 `finish` 是通过抛出 `FinishedException` 来结束事件的,因此抛出异常后可能会被未加限制的 `try-except` 捕获,影响事件处理流程正确处理,导致无法正常结束此事件。请务必在异常捕获中指定错误类型,或将 `finish` 移出捕获范围进行使用。 69 | ::: 70 | 71 | 目前 NoneBot2 共提供了多种事件响应器操作,其中包括用于用户交互与流程控制两大类,具体内容可参考 [进阶 - 事件处理进阶](../../advanced/functions/handler-advanced)。 72 | -------------------------------------------------------------------------------- /tutorial/plugin-basic/logger.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 7 3 | description: 07_优化日志 4 | --- 5 | 6 | # 优化日志 7 | 8 | 在我们开发的过程中,难免会遇到需要在后台获取某些信息来辅助开发或排查故障的情况,例如用户信息、返回值或其他相关信息。虽然我们可以使用 `print` 来将这些信息输出到控制台,但这些信息相对于NoneBot2 框架本身和其他优秀的插件所提供的日志来说并不美观,也没有其他辅助信息(例如事件、内容高亮、插件名等)。而实现这种日志效果的方法非常简单——[Loguru](https://loguru.readthedocs.io/)。 9 | 10 | NoneBot2 使用 `Loguru` 进行日志记录。我们可以通过一行代码即可输出不同 `log level` 的日志,例如: 11 | 12 | ```python 13 | from nonebot.log import logger 14 | 15 | # Log with corresponding severity. 16 | logger.trace('trace level log') 17 | logger.debug('debug level log') 18 | logger.info('info level log') 19 | logger.success('success level log') 20 | logger.warning('warning level log') 21 | logger.error('error level log') 22 | logger.critical('critical level log') 23 | ``` 24 | 25 | 输出的日志为依次为: 26 | 27 | ```log 28 | 07-18 02:32:36 [TRACE] src | trace level log 29 | 07-18 02:32:36 [DEBUG] src | debug level log 30 | 07-18 02:32:36 [INFO] src | info level log 31 | 07-18 02:32:36 [SUCCESS] src | success level log 32 | 07-18 02:32:36 [WARNING] src | warning level log 33 | 07-18 02:32:36 [ERROR] src | error level log 34 | 07-18 02:32:36 [CRITICAL] src | critical level log 35 | ``` 36 | 37 | 在 [Windows Terminal](https://github.com/microsoft/terminal) 中的效果为: 38 | ![00](./resource/07_00.jpg) 39 | 40 | 在机器人的配置项中,我们也可以通过 `LOG_LEVEL=` 来配置[日志等级](https://loguru.readthedocs.io/en/stable/api/logger.html#loguru._logger.Logger),在控制台中仅会输出大于等于日志等级的日志,忽略掉低于日志等级的日志。默认的 `LOG_LEVEL` 为 `INFO`。 41 | 42 | 43 | 44 | 在 NoneBot2 中,用户可以自定义日志的格式。此部分将在将会在 ***(进阶部分的对应内容)*** 中进行介绍。 45 | 46 | 47 | -------------------------------------------------------------------------------- /tutorial/plugin-basic/matcher.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | description: 02_事件响应器 4 | --- 5 | 6 | # 事件响应器 7 | 8 | 事件响应器(`Matcher`)是对接收到的事件进行响应的基本单元,所有的事件响应器都继承自 `Matcher` 基类。 9 | 10 | 就像人或动物可以针对不同的外界输入做出不同的反应,例如看到了美食会流口水,被火烧到了会缩手。在 Nonebot2 中,事件响应器也可以通过一系列特定的规则精准的 **筛选** 出 **某一类型的事件**,并按照 **特定的流程** 交由 **指定的事件处理依赖** 进行处理。 11 | 12 | ## 事件响应器的辅助函数 13 | 14 | 所有的事件响应器都继承自 `Matcher` 基类,而直接使用继承自 `Matcher` 基类的方法创建事件响应器过于繁琐。于是 NoneBot2 中提供了 “事件响应器的辅助函数”(下称辅助函数)来辅助用户用 **最简的方式** 创建 **带有不同规则预设** 的事件响应器,极大的提高了代码的可读性和书写效率。通常情况下,我们可以仅使用辅助函数便完成几乎全部事件响应器的创建,而不需要自行定义 `Matcher` 类。 15 | 16 | 在 NoneBot2 中,辅助函数以 `on()` 或 `on_matchertype()` 为形式出现(例如 `on_message()` ),被调用后根据自身的类型及传入的参数返回一个 `Type[Matcher]` 的新的事件响应器。 17 | 18 | ### 多种多样的辅助函数 19 | 20 | 目前,NoneBot2 共提供了多种功能各异的辅助函数、具有共同命令名称前缀的命令组和具有共同参数的响应器组,具体内容可参考[进阶 - 事件响应器进阶](../../advanced/functions/matcher-advanced)。 21 | 22 | ## 创建事件响应器 23 | 24 | 我们可以使用 `nonebot.plugin` 模块中定义的辅助函数来创建事件响应器,此处我们采用 `on_command` 作为演示。 25 | 26 | ::: tips 27 | 用于定义事件响应器的辅助函数已经在 `nonebot` 主模块中被 `re-export`,所以直接从 `nonebot` 导入也是可以的。 28 | ::: 29 | 30 | ```py title=weather.py 31 | from nonebot import on_command 32 | # from nonebot.plugin import on_command 33 | 34 | matcher = on_command("天气") # 注册事件响应器 35 | ``` 36 | 37 | 这样,我们就获得一个名为 `matcher` 的事件响应器了,这个事件响应器会对 `[命令头]天气` 开头的命令进行响应。 38 | 39 | ::: tips 40 | 如果一条消息中包含 “对机器人的@” 或 “机器人的昵称”,例如`@awesome-bot /天气` 时,协议适配器会将 `event.is_tome()` 判断为 `True` ,同时 **部分协议适配器** 也会自动去除 `@awesome-bot`,即事件响应器收到的信息内容为 `/天气`。 41 | 也就是说,在 **部分事件响应器** 中,事件响应器所匹配的原始数据是不受 “对机器人的@” 或 “机器人的昵称” 影响的,这一影响会在其他匹配规则中体现出来。具体请参考对应的协议适配器文档。 42 | ::: 43 | 44 | ### 为事件响应器添加参数 45 | 46 | 在事件响应器的辅助函数中,我们可以添加一些参数来对事件响应器进行更加精细的调整,例如事件响应器的优先级、匹配规则、权限限制等。例如: 47 | 48 | ```py 49 | from nonebot import on_command 50 | 51 | matcher = on_command("天气",aliases={"weather","tq"},priority=10,block=True) 52 | ``` 53 | 54 | 这样我们就获得了一个 `可以响应天气、weather、tq三个命令` 、 `优先级为10` 、 `阻断事件传播` 的事件响应器。 55 | 56 | ::: tips 57 | 需要注意的是,不同的事件响应器有不同的可选参数,在使用之前可以参考[进阶 - 事件响应器进阶](../../advanced/functions/matcher-advanced#事件响应器的辅助函数)或编辑器的提示。 58 | ::: 59 | -------------------------------------------------------------------------------- /tutorial/plugin-basic/message-construct.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | description: 05_消息构造(Message) 4 | --- 5 | 6 | # 消息构造 7 | 8 | 在不同平台中,一条消息可能会有承载有各种不同的表现形式,它可能是一段纯文本、一张图片、一段语音、一篇富文本文章,也有可能是多种类型的组合等等。 9 | 10 | 在 NoneBot2 中,为确保消息的正常处理与跨平台兼容性,采用了扁平化的消息序列形式,即 `Message` 对象。 11 | 12 | ## 基础使用 13 | 14 | ### Message 15 | 16 | 在 NoneBot2 中,`Message` 的主要作用是用于表达“一串消息”。由于 `Message` 继承自 `List[MessageSegment]`,所以 `Message` 的本质是由若干 `MessageSegment` 所组成的消息序列。因此,`Message` 的使用方法与 `List` 有很多相似之处,例如切片、索引、拼接等。 17 | 18 | ```python title=weather.py 19 | from nonebot.adapters import Message, MessageSegment 20 | from nonebot.params import CommandArg 21 | from nonebot.plugin import on_command 22 | 23 | # 通过此处的函数来获取天气信息 24 | async def get_weather(city: str): 25 | weather = f"do something to get weather of {city}" 26 | return weather 27 | 28 | 29 | matcher = on_command("天气", priority=10) # 注册事件响应器 30 | 31 | 32 | @matcher.handle() # 为事件响应器添加一个处理函数 33 | async def handle_func(args: Message = CommandArg()): 34 | city = args.extract_plain_text() # 获取用户发送的命令信息 35 | if city in ["广州", "上海"]: 36 | weather_info = Message(f"今天 {city} 的天气是:") + MessageSegment.text( 37 | await get_weather(city=city) 38 | ) # 拼接回复消息 39 | await matcher.finish(weather_info) 40 | else: 41 | await matcher.finish(f"您输入的城市 {city} 暂不支持查询,请重试...") 42 | ``` 43 | 44 | 如上方示例所示,`Message` 可以将 `str` 类型的信息转化为 `Message` 类型的信息,并进行拼接操作。 45 | 46 | `Message` 不仅可以拼接 `Message` 类型的消息,也可以对 `str` 和下文即将提到的 `MessageSegment` 进行拼接,也可以对由其他适配器提供的特殊消息段进行拼接。 47 | 48 | ### MessageSegment 49 | 50 | 不难发现,`Message` 的功能更多的在于消息序列层面,例如将多段消息进行拼接、排序、切片和模板化构造等功能。这些功能更加倾向于对多段消息的管理,而对于单段复杂消息的构建实际上起不到太大作用。而 `MessageSegment`(消息段)则是针对于 `Message` 中这一问题的解决方案。 51 | 52 | 顾名思义,`MessageSegment` 是一段消息。由于 `Message` 的本质是由若干 `MessageSegment` 所组成的消息序列。简单来说就是 `Message` 类似于一个自然段,而 `MessageSegment` 则是组成自然段的一句话。也就是说 `MessageSegment` 可以被认为是构成 `Message` 的最小单位。同时,作为特殊消息载体的存在,`MessageSegment` 在 NoneBot2 的抽象基类中仅实现了 `text` 一种消息类型,而绝大多数的平台都有着 **独特的消息类型**,这些独特的内容均需要由对应的[协议适配器]()所提供,以适应不同平台中的消息模式。**这也意味着,你需要导入对应的协议适配器中的 `MessageSegment` 后才能使用其特殊的工厂方法。** 53 | 54 | 55 | 56 | 简单来说,`MessageSegment` 是用于快速构造不同消息种类的工具,而 `MessageSegment` 在 NoneBot2 自身中仅包含了 `text` 一类消息类型,更多的消息类型可由对应的[协议适配器]()所提供。 57 | 58 | ::: warning 59 | 在使用 `MessageSegment` 的 **平台特殊方法** 前,请务必先导入 **对应的协议适配器** 中的 `MessageSegment` 实现,并查询其文档获得其支持的特殊的工厂方法及使用方法。 60 | ::: 61 | 62 | `MessageSegment` 的用法也十分简单,即为 `MessageSegment.func(arg)` ,例如: 63 | 64 | ```python 65 | from nonebot.adapters import MessageSegment 66 | 67 | MessageSegment.text("hello world") 68 | ``` 69 | 70 | 对于 `MessageSegment` 的**平台特殊方法**,请参考对应的协议适配器的文档。 71 | 72 | ## 进阶使用 73 | 74 | ::: tips 75 | 下列使用方法并不属于 `Message` 或 `MessageSegment` 的最基础的应用,如果您无法理解其内容,可以直接跳过下文。 76 | ::: 77 | 78 | 与上文相同,`Message` 与 `MessageSegment` 在抽象基类中的实现十分有限,对于不同的平台中的**平台特殊方法**,请参考对应的协议适配器的文档。 79 | 80 | ### 使用消息序列 81 | 82 | 通常情况下,适配器在接收到消息时,会将消息转换为消息序列,可以通过 [`EventMessage`](../../advanced/functions/builtin-dependency-injection#eventmessage) 作为依赖注入, 或者使用 `event.get_message()` 获取。 83 | 84 | 由于它是`List[MessageSegment]`的子类, 所以你总是可以用和操作 List 类似的方式来处理消息序列 85 | 86 | ```python 87 | >>> message = Message([ 88 | MessageSegment(type='text', data={'text':'hello'}), 89 | MessageSegment(type='image', data={'url':'http://example.com/image.png'}), 90 | MessageSegment(type='text', data={'text':'world'}), 91 | ]) 92 | >>> for segment in message: 93 | ... print(segment.type, segment.data) 94 | ... 95 | text {'text': 'hello'} 96 | image {'url': 'http://example.com/image.png'} 97 | text {'text': 'world'} 98 | >>> len(message) 99 | 3 100 | ``` 101 | 102 | ### 构造消息序列 103 | 104 | 在使用事件响应器操作发送消息时,既可以使用 `str` 作为消息,也可以使用 `Message`、`MessageSegment` 或者 `MessageTemplate`。那么,我们就需要先构造一个消息序列。 105 | 106 | #### 直接构造 107 | 108 | `Message` 类可以直接实例化,支持 `str`、`MessageSegment`、`Iterable[MessageSegment]` 或适配器自定义类型的参数。 109 | 110 | ```python 111 | # str 112 | Message("Hello, world!") 113 | # MessageSegment 114 | Message(MessageSegment.text("Hello, world!")) 115 | # List[MessageSegment] 116 | Message([MessageSegment.text("Hello, world!")]) 117 | ``` 118 | 119 | #### 运算构造 120 | 121 | `Message` 对象可以通过 `str`、`MessageSegment` 相加构造,详情请参考[拼接消息](#拼接消息)。 122 | 123 | #### 从字典数组构造 124 | 125 | `Message` 对象支持 Pydantic 自定义类型构造,可以使用 Pydantic 的 `parse_obj_as` (`parse_raw_as`) 方法进行构造。 126 | 127 | ```python 128 | from pydantic import parse_obj_as 129 | 130 | # 由字典构造消息段 131 | parse_obj_as( 132 | MessageSegment, {"type": "text", "data": {"text": "text"}} 133 | ) == MessageSegment.text("text") 134 | # 由字典数组构造消息序列 135 | parse_obj_as( 136 | Message, 137 | [MessageSegment.text("text"), {"type": "text", "data": {"text": "text"}}], 138 | ) == Message([MessageSegment.text("text"), MessageSegment.text("text")]) 139 | ``` 140 | 141 | ::: warning 142 | 以上示例中的字典数据仅做参考,具体的数据格式由适配器自行定义。 143 | ::: 144 | 145 | ### 获取消息纯文本 146 | 147 | 由于消息中存在各种类型的消息段,因此 `str(message)` 通常并不能得到消息的纯文本,而是一个消息序列的字符串表示。 148 | 149 | NoneBot2 为消息段定义了一个方法 `is_text()` ,可以用于判断消息段是否为纯文本;也可以使用 `message.extract_plain_text()` 方法获取消息纯文本。 150 | 151 | ```python 152 | # 判断消息段是否为纯文本 153 | MessageSegment.text("text").is_text() == True 154 | # 提取消息纯文本字符串 155 | Message( 156 | [MessageSegment.text("text"), MessageSegment.at(123)] 157 | ).extract_plain_text() == "text" 158 | ``` 159 | 160 | ### 遍历 161 | 162 | `Message` 继承自 `List[MessageSegment]` ,因此可以使用 `for` 循环遍历消息段。 163 | 164 | ```python 165 | for segment in message: 166 | ... 167 | ``` 168 | 169 | ### 索引与切片 170 | 171 | `Message` 对列表的索引与切片进行了增强,在原有列表 int 索引与切片的基础上,支持 `type` 过滤索引与切片。 172 | 173 | ```python 174 | message = Message( 175 | [ 176 | MessageSegment.text("test"), 177 | MessageSegment.image("test2"), 178 | MessageSegment.image("test3"), 179 | MessageSegment.text("test4"), 180 | ] 181 | ) 182 | 183 | # 索引 184 | message[0] == MessageSegment.text("test") 185 | # 切片 186 | message[0:2] == Message( 187 | [MessageSegment.text("test"), MessageSegment.image("test2")] 188 | ) 189 | 190 | # 类型过滤 191 | message["image"] == Message( 192 | [MessageSegment.image("test2"), MessageSegment.image("test3")] 193 | ) 194 | # 类型索引 195 | message["image", 0] == MessageSegment.image("test2") 196 | # 类型切片 197 | message["image", 0:2] == Message( 198 | [MessageSegment.image("test2"), MessageSegment.image("test3")] 199 | ) 200 | ``` 201 | 202 | 同样的,`Message` 对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段。 203 | 204 | ```python 205 | # 指定类型首个消息段索引 206 | message.index("image") == 1 207 | # 指定类型消息段数量 208 | message.count("image") == 2 209 | ``` 210 | 211 | 此外,`Message` 添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。 212 | 213 | ```python 214 | # 获取指定类型指定个数的消息段 215 | message.get("image", 1) == Message([MessageSegment.image("test2")]) 216 | ``` 217 | 218 | ### 拼接消息 219 | 220 | `str`、`Message`、`MessageSegment` 对象之间可以直接相加,相加均会返回一个新的 `Message` 对象。 221 | 222 | ```python 223 | # 消息序列与消息段相加 224 | Message([MessageSegment.text("text")]) + MessageSegment.text("text") 225 | # 消息序列与字符串相加 226 | Message([MessageSegment.text("text")]) + "text" 227 | # 消息序列与消息序列相加 228 | Message([MessageSegment.text("text")]) + Message([MessageSegment.text("text")]) 229 | # 字符串与消息序列相加 230 | "text" + Message([MessageSegment.text("text")]) 231 | 232 | # 消息段与消息段相加 233 | MessageSegment.text("text") + MessageSegment.text("text") 234 | # 消息段与字符串相加 235 | MessageSegment.text("text") + "text" 236 | # 消息段与消息序列相加 237 | MessageSegment.text("text") + Message([MessageSegment.text("text")]) 238 | # 字符串与消息段相加 239 | "text" + MessageSegment.text("text") 240 | ``` 241 | 242 | 如果需要在当前消息序列后直接拼接新的消息段,可以使用 `Message.append`、`Message.extend` 方法,或者使用自加。 243 | 244 | ```python 245 | msg = Message([MessageSegment.text("text")]) 246 | # 自加 247 | msg += "text" 248 | msg += MessageSegment.text("text") 249 | msg += Message([MessageSegment.text("text")]) 250 | # 附加 251 | msg.append("text") 252 | msg.append(MessageSegment.text("text")) 253 | # 扩展 254 | msg.extend([MessageSegment.text("text")]) 255 | ``` 256 | 257 | ### 使用消息模板 258 | 259 | 为了提供安全可靠的跨平台模板字符, 我们提供了一个消息模板功能来构建消息序列 260 | 261 | 它在以下常见场景中尤其有用: 262 | 263 | - 多行富文本编排(包含图片,文字以及表情等) 264 | 265 | - 客制化(由 Bot 最终用户提供消息模板时) 266 | 267 | 在事实上, 它的用法和`str.format`极为相近, 所以你在使用的时候, 总是可以参考[Python 文档](https://docs.python.org/zh-cn/3/library/stdtypes.html#str.format)来达到你想要的效果 268 | 269 | 这里给出几个简单的例子: 270 | 271 | ::: warning 272 | 这里面所有的 `Message` 均是用对应的[协议适配器]()实现导入的, 而不是抽象基类 273 | ::: 274 | 275 | ```python title="基础格式化用法" 276 | >>> Message.template("{} {}").format("hello", "world") 277 | Message( 278 | MessageSegment.text("hello"), 279 | MessageSegment.text(" "), 280 | MessageSegment.text("world") 281 | ) 282 | ``` 283 | 284 | ```python title="对消息段进行安全的拼接" 285 | >>> Message.template("{}{}").format(MessageSegment.image("file:///..."), "world") 286 | Message( 287 | MessageSegment(type='image', data={'file': 'file:///...'}), 288 | MessageSegment(type='text', data={'text': 'world'}) 289 | ) 290 | ``` 291 | 292 | ```python title="以消息对象作为模板" 293 | >>> Message.template( 294 | ... MessageSegment.text('{user_id}') + MessageSegment.face(233) + 295 | ... MessageSegment.text('{message}') 296 | ... ).format_map({'user_id':123456, 'message':'hello world'} 297 | ... 298 | Message( 299 | MessageSegment(type='text', data={'text': '123456'}), 300 | MessageSegment(type='face', data={'face': 233}), 301 | MessageSegment(type='text', data={'text': 'hello world'}) 302 | ) 303 | ``` 304 | 305 | ```python title="使用消息段的拓展控制符" 306 | >>> Message.template("{link:image}").format(link='https://...') 307 | Message(MessageSegment(type='image', data={'file': 'https://...'})) 308 | ``` 309 | -------------------------------------------------------------------------------- /tutorial/plugin-basic/overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 0 3 | description: 00_概览 4 | --- 5 | 6 | # 插件入门 7 | 8 | 插件(`Plugin`)是 NoneBot2 中实现具体功能的最小单位,也是用户对事件进行处理的基础单位。 9 | 10 | ## 什么是插件 11 | 12 | 插件是 NoneBot2 中最为重要的组成部分,其负责了机器人全部的事件处理工作,可以说 NoneBot2 中其他全部组件都是为了支撑插件的功能而存在的。 13 | 14 | 如果把机器人比作一个人,那么无数的插件便组成了这个人的大脑。若未加载任何插件,这个机器人也与就像是变成了植物人,无法对任何消息做出任何回应。 15 | 16 | ## 插件的兼容性 17 | 18 | 在编写插件之前,我们首先要明确一点,在不同的使用场景中,插件所需要使用的依赖并不相同,进而导致插件很有可能无法直接在其他平台上直接使用。这点尤其体现在不同平台适配器上,例如 [mirai2](https://github.com/ieew/nonebot_adapter_mirai2) 与 [OneBot V11](https://github.com/nonebot/adapter-onebot) 均可以在 QQ 平台上使用,但其大部分插件并无法直接通用。 19 | 20 | 造成这一点的原因是不同驱动器、适配器均有自己的特殊方法,而这些在其他不同的平台上均不尽相同,那么依赖于这些特殊方法的插件在其他平台上自然无法直接使用。因此对于插件开发者而言,首先要选择合适的驱动器、适配器及 NoneBot2 版本,然后再进行开发,并在发布插件时进行兼容性标注。 21 | 22 | ## 参考他人的插件 23 | 24 | NoneBot2 拥有强大的生态,很多开发者都将自己写好的插件发布至[插件商店](https://v2.nonebot.dev/store),我们不仅可以直接使用,也可参考其源代码进行进一步的学习。 25 | -------------------------------------------------------------------------------- /tutorial/plugin-basic/permission.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | description: 06_权限控制 4 | --- 5 | 6 | # 权限控制 7 | 8 | **权限控制**是机器人在实际应用中需要解决的重点问题之一,NoneBot2 提供了灵活的权限控制机制——`Permission`。 9 | 10 | ## 基础使用 11 | 12 | `Permission` 是由非负整数个 `PermissionChecker` 所共同组成的 **用于筛选事件** 的对象。而相对于 [`Rule`](../plugin-advance/rule) 而言,`Permission` 则更侧重于对于 **发起事件的用户** 的筛选,例如由 NoneBot2 自身提供的 `SUPERUSER`,便是筛选出会话发起者是否为超级用户。它们可以对输入的用户进行鉴别,如果符合要求则会被认为通过并返回 `True`,反之则返回 `False`。 13 | 14 | 简单来说,`Permission` 是一个用于筛选出符合要求的用户的机制,可以通过 `Permission` 精确的控制响应对象的覆盖范围,从而拒绝掉我们所不希望的事件。 15 | 16 | ```python 17 | from nonebot import on_command 18 | from nonebot.permission import SUPERUSER 19 | 20 | 21 | matcher = on_command("测试超管", permission=SUPERUSER) 22 | 23 | @matcher.handle() 24 | async def _(): 25 | await matcher.send("超管命令测试成功") 26 | ``` 27 | 28 | 如上方示例所示,在注册事件响应器时,我们设置了 `permission` 参数,那么这个事件处理器在触发事件前的检查阶段会对用户身份进行验证,如果不符合我们设置的条件(此处即为 [`超级用户`](../../advanced/functions/builtin-config#superusers))则会响应失败。 29 | 30 | 目前,NoneBot2 内置了 `SUPERUSER` 一个针对用户的 `Permission`,和 `METAEVENT` `REQUEST` `NOTICE` `MESSAGE` 四个针对事件类型的 `Permission`,同时也可由协议适配器或用户自行定义更多的权限,并对权限进行 `或运算`。 31 | 32 | ```python title=weather.py 33 | from nonebot.adapters import Message, MessageSegment 34 | from nonebot.params import CommandArg 35 | from nonebot.plugin import on_command 36 | from nonebot.permission import SUPERUSER 37 | 38 | # 通过此处的函数来获取天气信息 39 | async def get_weather(city: str): 40 | weather = f"do something to get weather of {city}" 41 | return weather 42 | 43 | 44 | matcher = on_command("天气", priority=10, permission=SUPERUSER) # 注册事件响应器 45 | 46 | 47 | @matcher.handle() # 为事件响应器添加一个处理函数 48 | async def handle_func(args: Message = CommandArg()): 49 | city = args.extract_plain_text() # 获取用户发送的命令信息 50 | if city in ["广州", "上海"]: 51 | weather_info = Message(f"今天 {city} 的天气是:") + MessageSegment.text( 52 | await get_weather(city=city) 53 | ) # 拼接回复消息 54 | await matcher.finish(weather_info) 55 | else: 56 | await matcher.finish(f"您输入的城市 {city} 暂不支持查询,请重试...") 57 | ``` 58 | 59 | 如上方示例所示,我们在weather插件中加入了权限控制,目前此插件仅会对bot的 `超级用户` 进行响应,并忽略掉其他用户的请求。 60 | 61 | ## 进阶使用 62 | 63 | ::: warning 64 | 下列使用方法并不属于 `Permission` 的最基础的应用,如果您无法理解其内容,可以直接跳过下文。 65 | ::: 66 | 67 | ### Permission的连续性以及更新 68 | 69 | 与 `Rule` 不同的是,`Permission` 不会在会话状态更新时丢失,因此 `Permission` 通常用于会话的响应控制。 70 | 71 | 并且,当会话状态更新时,会执行 `permission_updater` 以更新 `Permission`。默认情况下,`permission_updater` 会在原有的 `Permission` 基础上添加一个 `USER` 条件,以检查事件的 `session_id` 是否与当前会话一致。 72 | 73 | 你可以自行定义 `permission_updater` 来控制会话的响应权限更新。`permission_updater` 是一个返回 `Permission` 的函数,可选依赖注入参数参考类型 `T_PermissionUpdater`。 74 | 75 | ```python {3,5} 76 | matcher = on_message() 77 | 78 | @matcher.permission_updater 79 | async def update_type(matcher: Matcher): 80 | return matcher.permission # return same without session_id check 81 | ``` 82 | 83 | ### 在事件处理流程中调用 84 | 85 | `Permission` 除了可以在注册事件响应器时加以应用,还可以在编写事件处理函数 `handler` 时主动调用,我们可以利用这个特性在一个 `handler` 里对不同权限的事件主体进行区别响应,下面我们以 OneBot 适配器中的 `GROUP_ADMIN`(普通管理员非群主)和 `GROUP_OWNER` 为例,说明下怎么进行主动调用。 86 | 87 | ```python 88 | from nonebot import on_command 89 | from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent 90 | from nonebot.adapters.onebot.v11 import GROUP_ADMIN, GROUP_OWNER 91 | 92 | matcher = on_command("测试权限") 93 | 94 | @matcher.handle() 95 | async def _(bot: Bot, event: GroupMessageEvent): 96 | if await GROUP_ADMIN(bot, event): 97 | await matcher.send("管理员测试成功") 98 | elif await GROUP_OWNER(bot, event): 99 | await matcher.send("群主测试成功") 100 | else: 101 | await matcher.send("群员测试成功") 102 | ``` 103 | 104 | 在这段代码里,我们并没有对命令的权限指定,这个命令会响应所有在群聊中的 `测试权限` 命令,但是在 `handler` 里,我们对两个 `Permission` 进行主动调用,从而可以对不同的角色进行不同的响应。 105 | 106 | ### 自定义权限 107 | 108 | 如同 `Rule` 一样,`Permission` 也是由非负数个 `PermissionChecker` 组成的,但只需其中一个返回 `True` 时就会匹配成功。下面是自定义 `PermissionChecker` 和 `Permission` 的示例: 109 | 110 | ```python 111 | from nonebot.adapters import Bot, Event 112 | from nonebot.permission import Permission 113 | 114 | async def async_checker(bot: Bot, event: Event) -> bool: 115 | return True 116 | 117 | def sync_checker(bot: Bot, event: Event) -> bool: 118 | return True 119 | 120 | def check(arg1, arg2): 121 | 122 | async def _checker(bot: Bot, event: Event) -> bool: 123 | return bool(arg1 + arg2) 124 | 125 | return Permission(_checker) 126 | ``` 127 | 128 | `Permission` 和 `PermissionChecker` 之间可以使用 `|`(或符号)互相组合: 129 | 130 | ```python 131 | from nonebot.permission import Permission 132 | 133 | Permission(async_checker1) | sync_checker | async_checker2 134 | ``` 135 | 136 | 同样地,如果想用 `Permission(*checkers)` 包裹构造 `Permission`,函数可以是异步的,也可以是同步的。 137 | -------------------------------------------------------------------------------- /tutorial/plugin-basic/resource/07_00.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nonebot/nonebot2-tutorial/af6c2ed71bcfacdc54aa2c750dc77bf1be4f96c1/tutorial/plugin-basic/resource/07_00.jpg --------------------------------------------------------------------------------