diff --git a/README.md b/README.md index 20e04892..78caed43 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ **NoneBot2 尚在开发中** -NoneBot2 是一个可扩展的的 Python 异步机器人框架,它会对机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。 +NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。 除了起到解析消息的作用,NoneBot 还为插件提供了大量实用的预设操作和权限控制机制,尤其对于命令处理器,它更是提供了完善且易用的会话机制和内部调用机制,以分别适应命令的连续交互和插件内部功能复用等需求。 @@ -30,10 +30,10 @@ NoneBot2 是一个可扩展的的 Python 异步机器人框架,它会对机器 ## 文档 -文档目前尚未完成,「API」部分由 sphinx 自动生成,你可以在 [这里](https://docs.nonebot.dev/) 查看。 +文档目前尚未完成,「API」部分由 sphinx 自动生成,你可以在 [这里](https://v2.nonebot.dev/) 查看。 ## 贡献 -如果你在使用过程中发现任何问题,可以 [提交 issue](https://github.com/nonebot/nonebot/issues/new) 或自行 fork 修改后提交 pull request。 +如果你在使用过程中发现任何问题,可以 [提交 issue](https://github.com/nonebot/nonebot2/issues/new) 或自行 fork 修改后提交 pull request。 如果你要提交 pull request,请确保你的代码风格和项目已有的代码保持一致,遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/),变量命名清晰,有适当的注释。 diff --git a/archive/2.0.0a1/README.md b/archive/2.0.0a1/README.md new file mode 100644 index 00000000..e568de9f --- /dev/null +++ b/archive/2.0.0a1/README.md @@ -0,0 +1,15 @@ +--- +home: true +heroImage: /logo.png +tagline: An asynchronous QQ bot framework. +actionText: 开始使用 +actionLink: /guide/ +features: + - title: 简洁 + details: 提供极其简洁易懂的 API,使你可以毫无压力地开始验证你的绝佳创意,只需编写最少量的代码,即可实现丰富的功能。 + - title: 易于扩展 + details: 精心设计的消息处理流程使得你可以很方便地将原型扩充为具有大量实用功能的完整聊天机器人,并持续保证扩展性。 + - title: 高性能 + details: 采用异步 I/O,利用 WebSocket 进行通信,以获得极高的性能;同时,支持使用多账号同时接入,减少业务宕机的可能。 +footer: MIT Licensed | Copyright © 2020 NoneBot Team +--- diff --git a/archive/2.0.0a1/api/README.md b/archive/2.0.0a1/api/README.md new file mode 100644 index 00000000..2fa1066a --- /dev/null +++ b/archive/2.0.0a1/api/README.md @@ -0,0 +1,31 @@ +# NoneBot Api Reference + + +* **模块索引** + + + * [nonebot](nonebot.html) + + + * [nonebot.typing](typing.html) + + + * [nonebot.config](config.html) + + + * [nonebot.sched](sched.html) + + + * [nonebot.log](log.html) + + + * [nonebot.rule](rule.html) + + + * [nonebot.permission](permission.html) + + + * [nonebot.utils](utils.html) + + + * [nonebot.exception](exception.html) diff --git a/archive/2.0.0a1/api/config.md b/archive/2.0.0a1/api/config.md new file mode 100644 index 00000000..fd90f50e --- /dev/null +++ b/archive/2.0.0a1/api/config.md @@ -0,0 +1,265 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.config 模块 + +## 配置 + +NoneBot 使用 [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/) 文档。 + + +## _class_ `Env` + +基类:`pydantic.env_settings.BaseSettings` + +运行环境配置。大小写不敏感。 + +将会从 `nonebot.init 参数` > `环境变量` > `.env 环境配置文件` 的优先级读取配置。 + + +### `environment` + + +* 类型: `str` + + +* 默认值: `"prod"` + + +* 说明: +当前环境名。 NoneBot 将从 `.env.{environment}` 文件中加载配置。 + + +## _class_ `Config` + +基类:`nonebot.config.BaseConfig` + +NoneBot 主要配置。大小写不敏感。 + +除了 NoneBot 的配置项外,还可以自行添加配置项到 `.env.{environment}` 文件中。 +这些配置将会在 json 反序列化后一起带入 `Config` 类中。 + + +### `driver` + + +* 类型: `str` + + +* 默认值: `"nonebot.drivers.fastapi"` + + +* 说明: +NoneBot 运行所使用的 `Driver` 。继承自 `nonebot.driver.BaseDriver` 。 + + +### `host` + + +* 类型: `IPvAnyAddress` + + +* 默认值: `127.0.0.1` + + +* 说明: +NoneBot 的 HTTP 和 WebSocket 服务端监听的 IP/主机名。 + + +### `port` + + +* 类型: `int` + + +* 默认值: `8080` + + +* 说明: +NoneBot 的 HTTP 和 WebSocket 服务端监听的端口。 + + +### `debug` + + +* 类型: `bool` + + +* 默认值: `False` + + +* 说明: +是否以调试模式运行 NoneBot。 + + +### `api_root` + + +* 类型: `Dict[str, str]` + + +* 默认值: `{}` + + +* 说明: +以机器人 ID 为键,上报地址为值的字典,环境变量或文件中应使用 json 序列化。 + + +* 示例: + +```default +API_ROOT={"123456": "http://127.0.0.1:5700"} +``` + + +### `api_timeout` + + +* 类型: `Optional[float]` + + +* 默认值: `30.` + + +* 说明: +API 请求超时时间,单位: 秒。 + + +### `access_token` + + +* 类型: `Optional[str]` + + +* 默认值: `None` + + +* 说明: +API 请求以及上报所需密钥,在请求头中携带。 + + +* 示例: + +```http +POST /cqhttp/ HTTP/1.1 +Authorization: Bearer kSLuTF2GC2Q4q4ugm3 +``` + + +### `secret` + + +* 类型: `Optional[str]` + + +* 默认值: `None` + + +* 说明: +HTTP POST 形式上报所需签名,在请求头中携带。 + + +* 示例: + +```http +POST /cqhttp/ HTTP/1.1 +X-Signature: sha1=f9ddd4863ace61e64f462d41ca311e3d2c1176e2 +``` + + +### `superusers` + + +* 类型: `Set[int]` + + +* 默认值: `set()` + + +* 说明: +机器人超级用户。 + + +* 示例: + +```default +SUPER_USERS=[12345789] +``` + + +### `nickname` + + +* 类型: `Union[str, Set[str]]` + + +* 默认值: `""` + + +* 说明: +机器人昵称。 + + +### `command_start` + + +* 类型: `Set[str]` + + +* 默认值: `{"/"}` + + +* 说明: +命令的起始标记,用于判断一条消息是不是命令。 + + +### `command_sep` + + +* 类型: `Set[str]` + + +* 默认值: `{"."}` + + +* 说明: +命令的分隔标记,用于将文本形式的命令切分为元组(实际的命令名)。 + + +### `session_expire_timeout` + + +* 类型: `timedelta` + + +* 默认值: `timedelta(minutes=2)` + + +* 说明: +等待用户回复的超时时间。 + + +* 示例: + +```default +SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒 +SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff] +SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601 +``` + + +### `apscheduler_config` + + +* 类型: `dict` + + +* 默认值: `{"apscheduler.timezone": "Asia/Shanghai"}` + + +* 说明: +APScheduler 的配置对象,见 [Configuring the Scheduler](https://apscheduler.readthedocs.io/en/latest/userguide.html#configuring-the-scheduler) diff --git a/archive/2.0.0a1/api/exception.md b/archive/2.0.0a1/api/exception.md new file mode 100644 index 00000000..2c7abad1 --- /dev/null +++ b/archive/2.0.0a1/api/exception.md @@ -0,0 +1,156 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.exception 模块 + +## 异常 + +下列文档中的异常是所有 NoneBot 运行时可能会抛出的。 +这些异常并非所有需要用户处理,在 NoneBot 内部运行时被捕获,并进行对应操作。 + + +## _exception_ `IgnoredException` + +基类:`Exception` + + +* **说明** + + 指示 NoneBot 应该忽略该事件。可由 PreProcessor 抛出。 + + + +* **参数** + + + * `reason`: 忽略事件的原因 + + + +## _exception_ `PausedException` + +基类:`Exception` + + +* **说明** + + 指示 NoneBot 结束当前 `Handler` 并等待下一条消息后继续下一个 `Handler`。 + 可用于用户输入新信息。 + + + +* **用法** + + 可以在 `Handler` 中通过 `Matcher.pause()` 抛出。 + + + +## _exception_ `RejectedException` + +基类:`Exception` + + +* **说明** + + 指示 NoneBot 结束当前 `Handler` 并等待下一条消息后重新运行当前 `Handler`。 + 可用于用户重新输入。 + + + +* **用法** + + 可以在 `Handler` 中通过 `Matcher.reject()` 抛出。 + + + +## _exception_ `FinishedException` + +基类:`Exception` + + +* **说明** + + 指示 NoneBot 结束当前 `Handler` 且后续 `Handler` 不再被运行。 + 可用于结束用户会话。 + + + +* **用法** + + 可以在 `Handler` 中通过 `Matcher.finish()` 抛出。 + + + +## _exception_ `ExpiredException` + +基类:`Exception` + + +* **说明** + + 指示 NoneBot 当前 `Matcher` 已失效。 + + + +* **用法** + + 当 `Matcher` 运行前检查时抛出。 + + + +## _exception_ `StopPropagation` + +基类:`Exception` + + +* **说明** + + 指示 NoneBot 终止事件向下层传播。 + + + +* **用法** + + 在 `Matcher.block == True` 时抛出。 + + + +## _exception_ `ApiNotAvailable` + +基类:`Exception` + + +* **说明** + + 在 API 连接不可用时抛出。 + + + +## _exception_ `NetworkError` + +基类:`Exception` + + +* **说明** + + 在网络出现问题时抛出,如: API 请求地址不正确, API 请求无返回或返回状态非正常等。 + + + +## _exception_ `ActionFailed` + +基类:`Exception` + + +* **说明** + + API 请求成功返回数据,但 API 操作失败。 + + + +* **参数** + + + * `retcode`: 错误代码 diff --git a/archive/2.0.0a1/api/log.md b/archive/2.0.0a1/api/log.md new file mode 100644 index 00000000..77ce3609 --- /dev/null +++ b/archive/2.0.0a1/api/log.md @@ -0,0 +1,55 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.log 模块 + +## 日志 + +NoneBot 使用 [loguru](https://github.com/Delgan/loguru) 来记录日志信息。 + +自定义 logger 请参考 [loguru](https://github.com/Delgan/loguru) 文档。 + + +## `logger` + + +* **说明** + + NoneBot 日志记录器对象。 + + + +* **默认信息** + + + * 格式: `[%(asctime)s %(name)s] %(levelname)s: %(message)s` + + + * 等级: `DEBUG` / `INFO` ,根据 config 配置改变 + + + * 输出: 输出至 stdout + + + +* **用法** + + +```python +from nonebot.log import logger +``` + + +## _class_ `LoguruHandler` + +基类:`logging.Handler` + + +### `emit(record)` + +Do whatever it takes to actually log the specified logging record. + +This version is intended to be implemented by subclasses and so +raises a NotImplementedError. diff --git a/archive/2.0.0a1/api/nonebot.md b/archive/2.0.0a1/api/nonebot.md new file mode 100644 index 00000000..3c278a5c --- /dev/null +++ b/archive/2.0.0a1/api/nonebot.md @@ -0,0 +1,208 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot 模块 + + +## `get_driver()` + + +* **说明** + + 获取全局 Driver 对象。可用于在计划任务的回调中获取当前 Driver 对象。 + + + +* **返回** + + + * `Driver`: 全局 Driver 对象 + + + +* **异常** + + + * `ValueError`: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用) + + + +* **用法** + + +```python +driver = nonebot.get_driver() +``` + + +## `get_app()` + + +* **说明** + + 获取全局 Driver 对应 Server App 对象。 + + + +* **返回** + + + * `Any`: Server App 对象 + + + +* **异常** + + + * `ValueError`: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用) + + + +* **用法** + + +```python +app = nonebot.get_app() +``` + + +## `get_asgi()` + + +* **说明** + + 获取全局 Driver 对应 Asgi 对象。 + + + +* **返回** + + + * `Any`: Asgi 对象 + + + +* **异常** + + + * `ValueError`: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用) + + + +* **用法** + + +```python +asgi = nonebot.get_asgi() +``` + + +## `get_bots()` + + +* **说明** + + 获取所有通过 ws 连接 NoneBot 的 Bot 对象。 + + + +* **返回** + + + * `Dict[str, Bot]`: 一个以字符串 ID 为键,Bot 对象为值的字典 + + + +* **异常** + + + * `ValueError`: 全局 Driver 对象尚未初始化 (nonebot.init 尚未调用) + + + +* **用法** + + +```python +bots = nonebot.get_bots() +``` + + +## `init(*, _env_file=None, **kwargs)` + + +* **说明** + + 初始化 NoneBot 以及 全局 Driver 对象。 + + NoneBot 将会从 .env 文件中读取环境信息,并使用相应的 env 文件配置。 + + 你也可以传入自定义的 _env_file 来指定 NoneBot 从该文件读取配置。 + + + +* **参数** + + + * `_env_file: Optional[str]`: 配置文件名,默认从 .env.{env_name} 中读取配置 + + + * `**kwargs`: 任意变量,将会存储到 Config 对象里 + + + +* **返回** + + + * `None` + + + +* **用法** + + +```python +nonebot.init(database=Database(...)) +``` + + +## `run(host=None, port=None, *args, **kwargs)` + + +* **说明** + + 启动 NoneBot,即运行全局 Driver 对象。 + + + +* **参数** + + + * `host: Optional[str]`: 主机名/IP,若不传入则使用配置文件中指定的值 + + + * `port: Optional[int]`: 端口,若不传入则使用配置文件中指定的值 + + + * `*args`: 传入 Driver.run 的位置参数 + + + * `**kwargs`: 传入 Driver.run 的命名参数 + + + +* **返回** + + + * `None` + + + +* **用法** + + +```python +nonebot.run(host="127.0.0.1", port=8080) +``` diff --git a/archive/2.0.0a1/api/permission.md b/archive/2.0.0a1/api/permission.md new file mode 100644 index 00000000..26d3cd34 --- /dev/null +++ b/archive/2.0.0a1/api/permission.md @@ -0,0 +1,121 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.permission 模块 + +## 权限 + +每个 `Matcher` 拥有一个 `Permission` ,其中是 **异步** `PermissionChecker` 的集合,只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。 + +:::tip 提示 +`PermissionChecker` 既可以是 async function 也可以是 sync function +::: + + +## `MESSAGE` + + +* **说明**: 匹配任意 `message` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 message type 的 Matcher。 + + +## `NOTICE` + + +* **说明**: 匹配任意 `notice` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 notice type 的 Matcher。 + + +## `REQUEST` + + +* **说明**: 匹配任意 `request` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 request type 的 Matcher。 + + +## `METAEVENT` + + +* **说明**: 匹配任意 `meta_event` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 meta_event type 的 Matcher。 + + +## `USER(*user, perm=)` + + +* **说明** + + 在白名单内且满足 perm + + + +* **参数** + + + * `*user: int`: 白名单 + + + * `perm: Permission`: 需要同时满足的权限 + + + +## `PRIVATE` + + +* **说明**: 匹配任意私聊消息类型事件 + + +## `PRIVATE_FRIEND` + + +* **说明**: 匹配任意好友私聊消息类型事件 + + +## `PRIVATE_GROUP` + + +* **说明**: 匹配任意群临时私聊消息类型事件 + + +## `PRIVATE_OTHER` + + +* **说明**: 匹配任意其他私聊消息类型事件 + + +## `GROUP` + + +* **说明**: 匹配任意群聊消息类型事件 + + +## `GROUP_MEMBER` + + +* **说明**: 匹配任意群员群聊消息类型事件 + +:::warning 警告 +该权限通过 event.sender 进行判断且不包含管理员以及群主! +::: + + +## `GROUP_ADMIN` + + +* **说明**: 匹配任意群管理员群聊消息类型事件 + + +## `GROUP_OWNER` + + +* **说明**: 匹配任意群主群聊消息类型事件 + + +## `SUPERUSER` + + +* **说明**: 匹配任意超级用户消息类型事件 + + +## `EVERYBODY` + + +* **说明**: 匹配任意消息类型事件 diff --git a/archive/2.0.0a1/api/rule.md b/archive/2.0.0a1/api/rule.md new file mode 100644 index 00000000..269abc57 --- /dev/null +++ b/archive/2.0.0a1/api/rule.md @@ -0,0 +1,122 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.rule 模块 + +## 规则 + +每个 `Matcher` 拥有一个 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。 + +:::tip 提示 +`RuleChecker` 既可以是 async function 也可以是 sync function +::: + + +## _class_ `Rule` + +基类:`object` + + +* **说明** + + `Matcher` 规则类,当事件传递时,在 `Matcher` 运行前进行检查。 + + + +* **示例** + + +```python +Rule(async_function) & sync_function +# 等价于 +from nonebot.utils import run_sync +Rule(async_function, run_sync(sync_function)) +``` + + +### `__init__(*checkers)` + + +* **参数** + + + * `*checkers: Callable[[Bot, Event, dict], Awaitable[bool]]`: **异步** RuleChecker + + + +### `checkers` + + +* **说明** + + 存储 `RuleChecker` + + + +* **类型** + + + * `Set[Callable[[Bot, Event, dict], Awaitable[bool]]]` + + + +### _async_ `__call__(bot, event, state)` + + +* **说明** + + 检查是否符合所有规则 + + + +* **参数** + + + * `bot: Bot`: Bot 对象 + + + * `event: Event`: Event 对象 + + + * `state: dict`: 当前 State + + + +* **返回** + + + * `bool` + + + +## `startswith(msg)` + + +* **说明** + + 匹配消息开头 + + + +* **参数** + + + * `msg: str`: 消息开头字符串 + + + +## `endswith(msg)` + + +* **说明** + + 匹配消息结尾 + + + +* **参数** + + + * `msg: str`: 消息结尾字符串 diff --git a/archive/2.0.0a1/api/sched.md b/archive/2.0.0a1/api/sched.md new file mode 100644 index 00000000..450fd7d0 --- /dev/null +++ b/archive/2.0.0a1/api/sched.md @@ -0,0 +1,41 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.sched 模块 + +## 计划任务 + +计划任务使用第三方库 [APScheduler](https://github.com/agronholm/apscheduler) ,使用文档请参考 [APScheduler使用文档](https://apscheduler.readthedocs.io/en/latest/) 。 + + +## `scheduler` + + +* **类型** + + `Optional[apscheduler.schedulers.asyncio.AsyncIOScheduler]` + + + +* **说明** + + 当可选依赖 `APScheduler` 未安装时,`scheduler` 为 None + + 使用 `pip install nonebot[scheduler]` 安装可选依赖 + + + +* **常用示例** + + +```python +from nonebot import scheduler + +@scheduler.scheduled_job("cron", hour="*/2", id="xxx", args=[1], kwargs={arg2: 2}) +async def run_every_2_hour(arg1, arg2): + pass + +scheduler.add_job(run_every_day_from_program_start, "interval", days=1, id="xxx") +``` diff --git a/archive/2.0.0a1/api/typing.md b/archive/2.0.0a1/api/typing.md new file mode 100644 index 00000000..f68bdfcb --- /dev/null +++ b/archive/2.0.0a1/api/typing.md @@ -0,0 +1,236 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.typing 模块 + +## 类型 + +下面的文档中,「类型」部分使用 Python 的 Type Hint 语法,见 [PEP 484](https://www.python.org/dev/peps/pep-0484/)、[PEP 526](https://www.python.org/dev/peps/pep-0526/) 和 [typing](https://docs.python.org/3/library/typing.html)。 + +除了 Python 内置的类型,下面还出现了如下 NoneBot 自定类型,实际上它们是 Python 内置类型的别名。 + +以下类型均可从 nonebot.typing 模块导入。 + + +## `Driver` + + +* **类型** + + `BaseDriver` + + + +* **说明** + + 所有 Driver 的基类。 + + + + +## `WebSocket` + + +* **类型** + + `BaseWebSocket` + + + +* **说明** + + 所有 WebSocket 的基类。 + + + + +## `Bot` + + +* **类型** + + `BaseBot` + + + +* **说明** + + 所有 Bot 的基类。 + + + + +## `Event` + + +* **类型** + + `BaseEvent` + + + +* **说明** + + 所有 Event 的基类。 + + + + +## `Message` + + +* **类型** + + `BaseMessage` + + + +* **说明** + + 所有 Message 的基类。 + + + + +## `MessageSegment` + + +* **类型** + + `BaseMessageSegment` + + + +* **说明** + + 所有 MessageSegment 的基类。 + + + + +## `PreProcessor` + + +* **类型** + + `Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]` + + + +* **说明** + + 消息预处理函数 PreProcessor 类型 + + + + +## `Matcher` + + +* **类型** + + `Matcher` + + + +* **说明** + + Matcher 即响应事件的处理类。通过 Rule 判断是否响应事件,运行 Handler。 + + + + +## `Rule` + + +* **类型** + + `Rule` + + + +* **说明** + + Rule 即判断是否响应事件的处理类。内部存储 RuleChecker ,返回全为 True 则响应事件。 + + + + +## `RuleChecker` + + +* **类型** + + `Callable[[Bot, Event, dict], Union[bool, Awaitable[bool]]]` + + + +* **说明** + + RuleChecker 即判断是否响应事件的处理函数。 + + + + +## `Permission` + + +* **类型** + + `Permission` + + + +* **说明** + + Permission 即判断是否响应消息的处理类。内部存储 PermissionChecker ,返回只要有一个 True 则响应消息。 + + + + +## `PermissionChecker` + + +* **类型** + + `Callable[[Bot, Event], Union[bool, Awaitable[bool]]]` + + + +* **说明** + + RuleChecker 即判断是否响应消息的处理函数。 + + + + +## `Handler` + + +* **类型** + + `Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]` + + + +* **说明** + + Handler 即事件的处理函数。 + + + + +## `ArgsParser` + + +* **类型** + + `Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]` + + + +* **说明** + + ArgsParser 即消息参数解析函数,在 Matcher.got 获取参数时被运行。 diff --git a/archive/2.0.0a1/api/utils.md b/archive/2.0.0a1/api/utils.md new file mode 100644 index 00000000..7de3ba3f --- /dev/null +++ b/archive/2.0.0a1/api/utils.md @@ -0,0 +1,39 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.utils 模块 + + +## `run_sync(func)` + + +* **说明** + + 一个用于包装 sync function 为 async function 的装饰器 + + + +* **参数** + + + * `func: Callable[..., Any]`: 被装饰的同步函数 + + + +* **返回** + + + * `Callable[..., Awaitable[Any]]` + + + +## _class_ `DataclassEncoder` + +基类:`json.encoder.JSONEncoder` + + +* **说明** + + 在JSON序列化 `Message` (List[Dataclass]) 时使用的 `JSONEncoder` diff --git a/archive/2.0.0a1/guide/README.md b/archive/2.0.0a1/guide/README.md new file mode 100644 index 00000000..6cf31e90 --- /dev/null +++ b/archive/2.0.0a1/guide/README.md @@ -0,0 +1,38 @@ +# 概览 + +:::tip 提示 +如果在阅读本文档时遇到难以理解的词汇,请随时查阅 [术语表](../glossary.md) 或使用 [Google 搜索](https://www.google.com/)。 +::: + +:::tip 提示 +初次使用时可能会觉得这里的概览过于枯燥,可以先简单略读之后直接前往 [安装](./installation.md) 查看安装方法,并进行后续的基础使用教程。 +::: + +NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。 + +除了起到解析消息的作用,NoneBot 还为插件提供了大量实用的预设操作和权限控制机制,尤其对于命令处理器,它更是提供了完善且易用的会话机制和内部调用机制,以分别适应命令的连续交互和插件内部功能复用等需求。 + +目前 NoneBot2 在 [FastAPI](https://fastapi.tiangolo.com/) 的基础上封装了与 [CQHTTP(OneBot) 协议](http://cqhttp.cc/)插件的网络交互。 + +得益于 Python 的 [asyncio](https://docs.python.org/3/library/asyncio.html) 机制,NoneBot 处理消息的吞吐量有了很大的保障,再配合 WebSocket 通信方式(也是最建议的通信方式),NoneBot 的性能可以达到 HTTP 通信方式的两倍以上,相较于传统同步 I/O 的 HTTP 通信,更是有质的飞跃。 + +需要注意的是,NoneBot 仅支持 Python 3.7+ 及 CQHTTP(OneBot) 插件 v11+。 + +## 它如何工作? + +NoneBot 的运行离不开 酷 Q 和 CQHTTP 插件。酷 Q 扮演着「无头 QQ 客户端」的角色,它进行实际的消息、通知、请求的接收和发送,当 酷 Q 收到消息时,它将这个消息包装为一个事件(通知和请求同理),并通过它自己的插件机制将事件传送给 CQHTTP 插件,后者再根据其配置中的 `post_url` 或 `ws_reverse_url` 等项来将事件发送至 NoneBot。 + +在 NoneBot 收到事件前,它底层的 aiocqhttp 实际已经先看到了事件,aiocqhttp 根据事件的类型信息,通知到 NoneBot 的相应函数。特别地,对于消息类型的事件,还将消息内容转换成了 `aiocqhttp.message.Message` 类型,以便处理。 + +NoneBot 的事件处理函数收到通知后,对于不同类型的事件,再做相应的预处理和解析,然后调用对应的插件,并向其提供适合此类事件的会话(Session)对象。NoneBot 插件的编写者要做的,就是利用 Session 对象中提供的数据,在插件的处理函数中实现所需的功能。 + +## 特色 + +- 提供直观的测试前端 +- 提供使用简易的脚手架 +- 基于异步 I/O +- 同时支持 HTTP 和反向 WebSocket 通信方式 +- 支持多个机器人账号负载均衡 +- 提供直观的交互式会话接口 +- 提供可自定义的权限控制机制 +- 多种方式渲染要发送的消息内容,使对话足够自然 diff --git a/archive/2.0.0a1/guide/basic-configuration.md b/archive/2.0.0a1/guide/basic-configuration.md new file mode 100644 index 00000000..e4c4449f --- /dev/null +++ b/archive/2.0.0a1/guide/basic-configuration.md @@ -0,0 +1,68 @@ +# 基本配置 + +到目前为止我们还在使用 NoneBot 的默认行为,在开始编写自己的插件之前,我们先尝试在配置文件上动动手脚,让 NoneBot 表现出不同的行为。 + +在上一章节中,我们创建了默认的项目结构,其中 `.env`, `.env.*` 均为项目的配置文件,下面将介绍几种 NoneBot 配置方式。 + +:::danger 警告 +请勿将敏感信息写入配置文件并提交至开源仓库! +::: + +## .env 文件 + +NoneBot 在启动时将会从系统环境变量或者 `.env` 文件中寻找变量 `ENVIRONMENT` (大小写不敏感),默认值为 `prod`。 +这将引导 NoneBot 从系统环境变量或者 `.env.{ENVIRONMENT}` 文件中进一步加载具体配置。 + +现在,我们在 `.env` 文件中写入当前环境信息 + +```bash +# .env +ENVIRONMENT=dev +``` + +## .env.\* 文件 + +详细配置文件,使用 [pydantic](https://pydantic-docs.helpmanual.io/) 加载配置。在 NoneBot 初始化时可以指定忽略 `.env` 中的环境信息转而加载某个配置文件: `nonebot.init(_env_file=".env.dev")`。 + +:::warning 提示 +由于 `pydantic` 使用 JSON 加载配置项,请确保配置项值为 JSON 能够解析的数据。如果 JSON 解析失败将作为字符串处理。 +::: + +示例及说明: + +```bash +HOST=0.0.0.0 # 配置 NoneBot 监听的 IP/主机名 +PORT=8080 # 配置 NoneBot 监听的端口 +DEBUG=true # 开启 debug 模式 **请勿在生产环境开启** +SUPERUSERS=["123456789", "987654321"] # 配置 NoneBot 超级用户 +NICKNAME=["awesome", "bot"] # 配置机器人的昵称 +COMMAND_START=["/", ""] # 配置命令起始字符 +COMMAND_SEP=["."] # 配置命令分割字符 + +# Custom Configs +CUSTOM_CONFIG1="config in env file" +CUSTOM_CONFIG2= # 留空则从系统环境变量读取,如不存在则为空字符串 +``` + +详细的配置项参考 [Config Reference](../api/config.md) 。 + +## bot.py 文件 + +配置项也可以在 NoneBot 初始化时传入。此处可以传入任意合法 Python 变量。当然也可以在初始化完成后修改或新增。 + +示例: + +```python +# bot.py +import nonebot + +nonebot.init(custom_config3="config on init") + +config = nonebot.get_driver().config +config.custom_config3 = "changed after init" +config.custom_config4 = "new config after init" +``` + +## 优先级 + +`bot.py init` > `env file` > `system env` diff --git a/archive/2.0.0a1/guide/creating-a-project.md b/archive/2.0.0a1/guide/creating-a-project.md new file mode 100644 index 00000000..74cdb24a --- /dev/null +++ b/archive/2.0.0a1/guide/creating-a-project.md @@ -0,0 +1,55 @@ +# 创建一个完整的项目 + +上一章中我们已经运行了一个最小的 NoneBot 实例,在这一章,我们将从零开始一个完整的项目。 + +## 目录结构 + +首先,我们可以使用 `nb-cli` 或者自行创建项目目录: + +```bash +pip install nonebot2[cli] +# pip install nb-cli +nb create +``` + +这将创建默认的目录结构 + + +:::vue +AweSome-Bot +├── `awesome_bot` _(**或是 src**)_ +│ └── `plugins` +├── `.env` _(**可选的**)_ +├── `.env.dev` _(**可选的**)_ +├── `.env.prod` _(**可选的**)_ +├── .gitignore +├── `bot.py` +├── docker-compose.yml +├── Dockerfile +├── `pyproject.toml` +└── README.md +::: + + +- `awesome_bot/plugins` 或 `src/plugins`: 用于存放编写的 bot 插件 +- `.env`, `.env.dev`, `.env.prod`: 各环境配置文件 +- `bot.py`: bot 入口文件 +- `pyproject.toml`: 项目依赖管理文件,默认使用 [poetry](https://python-poetry.org/) + +## 启动 Bot + +如果你使用 `nb-cli` + +```bash +nb run [--file=bot.py] [--app=app] +``` + +或者使用 + +```bash +python bot.py +``` + +:::tip 提示 +如果在 bot 入口文件内定义了 asgi server, `nb-cli` 将会为你启动**冷重载模式** +::: diff --git a/archive/2.0.0a1/guide/getting-started.md b/archive/2.0.0a1/guide/getting-started.md new file mode 100644 index 00000000..d52a9396 --- /dev/null +++ b/archive/2.0.0a1/guide/getting-started.md @@ -0,0 +1,146 @@ +# 开始使用 + +一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 NoneBot 实例的准备。 + +## 最小实例 + +使用你最熟悉的编辑器或 IDE,创建一个名为 `bot.py` 的文件,内容如下: + +```python{3,4,7} +import nonebot + +nonebot.init() +nonebot.load_builtin_plugins() + +if __name__ == "__main__": + nonebot.run() +``` + +这几行高亮代码将依次: + +1. 使用默认配置初始化 NoneBot 包 +2. 加载 NoneBot 内置的插件 +3. 在地址 `127.0.0.1:8080` 运行 NoneBot + +在命令行使用如下命令即可运行这个 NoneBot 实例: + +```bash +python bot.py +``` + +运行后会产生如下日志: + +```default +09-14 21:02:00 [INFO] nonebot | Succeeded to import "nonebot.plugins.base" +09-14 21:02:00 [INFO] nonebot | Running NoneBot... +09-14 21:02:00 [INFO] uvicorn | Started server process [1234] +09-14 21:02:00 [INFO] uvicorn | Waiting for application startup. +09-14 21:02:00 [INFO] nonebot | Scheduler Started +09-14 21:02:00 [INFO] uvicorn | Application startup complete. +09-14 21:02:00 [INFO] uvicorn | Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit) +``` + +## 配置 QQ 协议端 + +单纯运行 NoneBot 实例并不会产生任何效果,因为此刻 QQ 这边还不知道 NoneBot 的存在,也就无法把消息发送给它,因此现在需要使用一个无头 QQ 来把消息等事件上报给 NoneBot。 + +目前支持的协议有: + +- [OneBot(CQHTTP)](https://github.com/howmanybots/onebot) + +QQ 协议端举例: + +- [Mirai](https://github.com/mamoe/mirai) + [cqhttp-mirai](https://github.com/yyuueexxiinngg/cqhttp-mirai) +- [cqhttp-mirai-embedded](https://github.com/yyuueexxiinngg/cqhttp-mirai/tree/embedded) +- [Mirai](https://github.com/mamoe/mirai) + [Mirai Native](https://github.com/iTXTech/mirai-native) + [CQHTTP](https://github.com/richardchien/coolq-http-api) +- [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) (基于 [MiraiGo](https://github.com/Mrs4s/MiraiGo)) +- [OICQ-http-api](https://github.com/takayama-lily/onebot) (基于 [OICQ](https://github.com/takayama-lily/oicq)) + +这里以 [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 为例 + +1. 下载 go-cqhttp 对应平台的 release 文件 +2. 双击 exe 文件或者使用 `./go-cqhttp` 启动 +3. 生成默认配置文件并修改默认配置 + +```json{2,3,30-31} +{ + "uin": 你的QQ号, + "password": "你的密码", + "encrypt_password": false, + "password_encrypted": "", + "enable_db": true, + "access_token": "", + "relogin": { + "enabled": true, + "relogin_delay": 3, + "max_relogin_times": 0 + }, + "ignore_invalid_cqcode": false, + "force_fragmented": true, + "heartbeat_interval": 0, + "http_config": { + "enabled": false, + "host": "0.0.0.0", + "port": 5700, + "timeout": 0, + "post_urls": {} + }, + "ws_config": { + "enabled": false, + "host": "0.0.0.0", + "port": 6700 + }, + "ws_reverse_servers": [ + { + "enabled": true, + "reverse_url": "ws://127.0.0.1:8080/cqhttp/ws", + "reverse_api_url": "", + "reverse_event_url": "", + "reverse_reconnect_interval": 3000 + } + ], + "post_message_format": "string", + "debug": false, + "log_level": "" +} +``` + +其中 `ws://127.0.0.1:8080/cqhttp/ws` 中的 `127.0.0.1` 和 `8080` 应分别对应 nonebot 配置的 HOST 和 PORT + +## 历史性的第一次对话 + +一旦新的配置文件正确生效之后,NoneBot 所在的控制台(如果正在运行的话)应该会输出类似下面的内容(两条访问日志): + +```default +09-14 21:31:16 [INFO] uvicorn | ('127.0.0.1', 12345) - "WebSocket /cqhttp/ws" [accepted] +09-14 21:31:16 [INFO] nonebot | WebSocket Connection from CQHTTP Bot 你的QQ号 Accepted! +``` + +这表示 QQ 协议端已经成功地使用 CQHTTP 协议连接上了 NoneBot。 + +:::warning 注意 +如果到这一步你没有看到上面这样的成功日志,CQHTTP 的日志中在不断地重连或无反应,请注意检查配置中的 IP 和端口是否确实可以访问。比较常见的出错点包括: + +- NoneBot 监听 `0.0.0.0`,然后在 CQHTTP 配置中填了 `ws://0.0.0.0:8080/cqhttp/ws` +- 在 Docker 容器内运行 CQHTTP,并通过 `127.0.0.1` 访问宿主机上的 NoneBot +- 想从公网访问,但没有修改云服务商的安全组策略或系统防火墙 +- NoneBot 所监听的端口存在冲突,已被其它程序占用 +- 弄混了 NoneBot 的 `host`、`port` 参数与 CQHTTP 配置中的 `host`、`port` 参数 +- 使用了 `ws_reverse_api_url` 和 `ws_reverse_event_url` 而非 universal client +- `ws://` 错填为 `http://` +- CQHTTP 或 NoneBot 启动时遭到外星武器干扰 + +请尝试重启 CQHTTP、重启 NoneBot、更换端口、修改防火墙、重启系统、仔细阅读前面的文档及提示、更新 CQHTTP 和 NoneBot 到最新版本等方式来解决。 +::: + +现在,尝试向你的 QQ 机器人账号发送如下内容: + +```default +/say 你好,世界 +``` + +到这里如果一切 OK,你应该会收到机器人给你回复了 `你好,世界`。这一历史性的对话标志着你已经成功地运行了一个 NoneBot 的最小实例,开始了编写更强大的 QQ 机器人的创意之旅! + + + + diff --git a/archive/2.0.0a1/guide/installation.md b/archive/2.0.0a1/guide/installation.md new file mode 100644 index 00000000..06a88598 --- /dev/null +++ b/archive/2.0.0a1/guide/installation.md @@ -0,0 +1,73 @@ +# 安装 + +## NoneBot + +:::warning 注意 +请确保你的 Python 版本 >= 3.7。 +::: + +```bash +pip install nonebot2 +``` + +如果你需要使用最新的(可能尚未发布的)特性,可以克隆 Git 仓库后手动安装: + +```bash +git clone https://github.com/nonebot/nonebot2.git +cd nonebot2 +poetry install --no-dev # 推荐 +pip install . # 不推荐 +``` + +## 额外依赖 + +### APScheduler + +A task scheduling library for Python. + +可用于计划任务,后台执行任务等 + +```bash +pip install nonebot2[scheduler] +poetry add nonebot2[scheduler] +``` + +[View On GitHub](https://github.com/agronholm/apscheduler) + +### NoneBot-Test + +A test frontend for nonebot2. + +通过前端展示 nonebot 已加载的插件以及运行状态,同时可以用于模拟发送事件测试机器人 + +```bash +pip install nonebot2[test] +poetry add nonebot2[test] +``` + +[View On GitHub](https://github.com/nonebot/nonebot-test) + +### CLI + +CLI for nonebot2. + +一个多功能脚手架 + +```bash +pip install nonebot2[cli] +poetry add nonebot2[cli] +``` + +[View On GitHub](https://github.com/yanyongyu/nb-cli) + +### 我全都要 + +```bash +pip install nonebot2[full] +poetry add nonebot2[full] +``` + +```bash +pip install nonebot2[cli,scheduler] +poetry add nonebot2[cli,scheduler] +``` diff --git a/archive/2.0.0a1/guide/writing-a-plugin.md b/archive/2.0.0a1/guide/writing-a-plugin.md new file mode 100644 index 00000000..bd176750 --- /dev/null +++ b/archive/2.0.0a1/guide/writing-a-plugin.md @@ -0,0 +1,290 @@ +# 编写插件 + +本章将以一个天气查询插件为例,教学如何编写自己的命令。 + +## 加载插件 + +在 [创建一个完整的项目](creating-a-project) 一章节中,我们已经创建了插件目录 `awesome_bot/plugins`,现在我们在机器人入口文件中加载它。当然,你也可以单独加载一个插件。 + +:::tip 提示 +加载插件目录时,目录下以 `_` 下划线开头的插件将不会被加载! +::: + +在 `bot.py` 文件中添加以下行: + +```python{5,7} +import nonebot + +nonebot.init() +# 加载单独的一个插件,参数为合法的python包名 +nonebot.load_plugin("nonebot.plugins.base") +# 加载插件目录,该目录下为各插件,以下划线开头的插件将不会被加载 +nonebot.load_plugins("awesome_bot/plugins") + +app = nonebot.get_asgi() + +if __name__ == "__main__": + nonebot.run() +``` + +尝试运行 `nb run` 或者 `python bot.py`,可以看到日志输出了类似如下内容: + +```plain +09-19 21:51:59 [INFO] nonebot | Succeeded to import "nonebot.plugins.base" +09-19 21:51:59 [INFO] nonebot | Succeeded to import "plugin_in_folder" +``` + +## 创建插件 + +现在我们已经有了一个空的插件目录,我们可以开始创建插件了!插件有两种形式 + +### 单文件形式 + +在插件目录下创建名为 `weather.py` 的 Python 文件,暂时留空,此时目录结构如下: + + +:::vue +AweSome-Bot +├── awesome_bot +│ └── plugins +│ └── `weather.py` +├── .env +├── .env.dev +├── .env.prod +├── .gitignore +├── bot.py +├── docker-compose.yml +├── Dockerfile +├── pyproject.toml +└── README.md +::: + + +这个时候它已经可以被称为一个插件了,尽管它还什么都没做。 + +### 包形式 + +在插件目录下创建文件夹 `weather`,并在该文件夹下创建文件 `__init__.py`,此时目录结构如下: + + +:::vue +AweSome-Bot +├── awesome_bot +│ └── plugins +│ └── `weather` +│ └── `__init__.py` +├── .env +├── .env.dev +├── .env.prod +├── .gitignore +├── bot.py +├── docker-compose.yml +├── Dockerfile +├── pyproject.toml +└── README.md +::: + + +这个时候 `weather` 就是一个合法的 Python 包了,同时也是合法的 NoneBot 插件,插件内容可以在 `__init__.py` 中编写。 + +## 编写真正的内容 + +好了,现在插件已经可以正确加载,我们可以开始编写命令的实际代码了。在 `weather.py` 中添加如下代码: + +```python +from nonebot import on_command +from nonebot.rule import to_me +from nonebot.adapters.cqhttp import Bot, Event + +weather = on_command("天气", rule=to_me(), priority=5) + + +@weather.handle() +async def handle_first_receive(bot: Bot, event: Event, state: dict): + args = str(event.message).strip() # 首次发送命令时跟随的参数,例:/天气 上海,则args为上海 + if args: + state["city"] = args # 如果用户发送了参数则直接赋值 + + +@weather.got("city", prompt="你想查询哪个城市的天气呢?") +async def handle_city(bot: Bot, event: Event, state: dict): + city = state["city"] + if city not in ["上海", "北京"]: + await weather.reject("你想查询的城市暂不支持,请重新输入!") + city_weather = await get_weather(city) + await weather.finish(city_weather) + + +async def get_weather(city: str): + return f"{city}的天气是..." +``` + +为了简单起见,我们在这里的例子中没有接入真实的天气数据,但要接入也非常简单,你可以使用中国天气网、和风天气等网站提供的 API。 + +下面我们来说明这段代码是如何工作的。 + +:::tip 提示 +从这里开始,你需要对 Python 的 asyncio 编程有所了解,因为 NoneBot 是完全基于 asyncio 的,具体可以参考 [廖雪峰的 Python 教程](https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152) +::: + +### 注册一个 [事件响应器](../api/matcher.md) + +```python{4} +from nonebot import on_command +from nonebot.rule import to_me +from nonebot.permission import Permission + +weather = on_command("天气", rule=to_me(), permission=Permission(), priority=5) +``` + +在上方代码中,我们注册了一个事件响应器 `Matcher`,它由几个部分组成: + +1. `on_command` 注册一个消息类型的命令处理器 +2. `"天气"` 指定 command 参数 - 命令名 +3. `rule` 补充事件响应器的匹配规则 +4. `priority` 事件响应器优先级 +5. `block` 是否阻止事件传递 + +其他详细配置可以参考 API 文档,下面我们详细说明各个部分: + +#### 事件响应器类型 type + +事件响应器类型其实就是对应 `Event.type` ,NoneBot 提供了一个基础类型事件响应器 `on()` 以及一些内置的事件响应器。 + +- `on("事件类型")`: 基础事件响应器,第一个参数为事件类型,空字符串表示不限 +- `on_metaevent()` ~ `on("meta_event")`: 元事件响应器 +- `on_message()` ~ `on("message")`: 消息事件响应器 +- `on_request()` ~ `on("request")`: 请求事件响应器 +- `on_notice()` ~ `on("notice")`: 通知事件响应器 +- `on_startswith(str)` ~ `on("message", startswith(str))`: 消息开头匹配处理器 +- `on_endswith(str)` ~ `on("message", endswith(str))`: 消息结尾匹配处理器 +- `on_command(str|tuple)` ~ `on("message", command(str|tuple))`: 命令处理器 +- `on_regax(pattern_str)` ~ `on("message", regax(pattern_str))`: 正则匹配处理器 + +#### 匹配规则 rule + +事件响应器的匹配规则即 `Rule`,由非负个 `RuleChecker` 组成,当所有 `RuleChecker` 返回 `True` 时匹配成功。这些 `RuleChecker` 的形式如下: + +```python +async def check(bot: Bot, event: Event, state: dict) -> bool: + return True + +def check(bot: Bot, event: Event, state: dict) -> bool: + return True +``` + +`Rule` 和 `RuleChecker` 之间可以使用 `与 &` 互相组合: + +```python +from nonebot.rule import Rule + +Rule(async_checker1) & sync_checker & async_checker2 +``` + +:::danger 警告 +`Rule(*checkers)` 只接受 async function,或使用 `nonebot.utils.run_sync` 自行包裹 sync function。在使用 `与 &` 时,NoneBot 会自动包裹 sync function +::: + +#### 优先级 priority + +事件响应器的优先级代表事件响应器的执行顺序,同一优先级的事件响应器会 **同时执行!** + +:::tip 提示 +使用 `nonebot-test` 可以看到当前所有事件响应器的执行流程,有助理解事件响应流程! + +```bash +pip install nonebot2[test] +``` + +::: + +#### 阻断 block + +当有任意事件响应器发出了阻止事件传递信号时,该事件将不再会传递给下一优先级,直接结束处理。 + +NoneBot 内置的事件响应器中,所有 `message` 类的事件响应器默认会阻断事件传递,其他则不会。 + +### 编写事件处理函数 [Handler](../api/typing.md#handler) + +```python{1,2,8,9} +@weather.handle() +async def handle_first_receive(bot: Bot, event: Event, state: dict): + args = str(event.message).strip() # 首次发送命令时跟随的参数,例:/天气 上海,则args为上海 + if args: + state["city"] = args # 如果用户发送了参数则直接赋值 + + +@weather.got("city", prompt="你想查询哪个城市的天气呢?") +async def handle_city(bot: Bot, event: Event, state: dict): + city = state["city"] + if city not in ["上海", "北京"]: + await weather.reject("你想查询的城市暂不支持,请重新输入!") + city_weather = await get_weather(city) + await weather.finish(city_weather) +``` + +在上面的代码中,我们给 `weather` 事件响应器添加了两个事件处理函数:`handle_first_receive`, `handle_city` + +其中有几个要点,我们一一解释: + +#### 添加一个事件处理函数 + +在事件响应器响应事件时,事件处理函数会依次顺序执行,也就是说,与添加顺序一致。 + +我们可以使用 `@matcher.handle()` 装饰器来简单地为该事件响应器添加一个处理函数。 + +同时,NoneBot 内置了几种添加事件处理函数方式以方便处理: + +- `@matcher.receive()`: 指示 NoneBot 接收一条新的用户消息以继续执行后续处理函数。 +- `@matcher.got(key, [prompt="请输入key"], [args_parser=function])`: 指示 NoneBot 当 `state` 中不存在 `key` 时向用户发送 `prompt` 等待用户回复并赋值给 `state[key]` + +这些装饰器可以套娃使用!例如: + +```python +@matcher.got("key1") +@matcher.got("key2") +async def handle(bot: Bot, event: Event, state: dict): + pass +``` + +#### 事件处理函数参数 + +事件处理函数类型为 `Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]` 。 + +参数分别为: + +1. [nonebot.typing.Bot](../api/typing.md#bot): 即事件上报连接对应的 Bot 对象,为 BaseBot 的子类。特别注意,此处的类型注释可以替换为指定的 Bot 类型,例如:`nonebot.adapters.cqhttp.Bot`,只有在上报事件的 Bot 类型与类型注释相符时才会执行该处理函数!可用于多平台进行不同的处理。 +2. [nonebot.typing.Event](../api/typing.md#event): 即上报事件对象,可以获取到上报的所有信息。 +3. `state`: 状态字典,可以存储任意的信息 + +#### 处理事件 + +在事件处理函数中,我们只需要对 `event` 做出相应的处理,存入状态字典 `state` 中,或者向用户发送消息、调用某个机器人 API 等等。 + +在 NoneBot 中,提供了几种特殊的处理函数: + +##### `@matcher.args_parser` + +这是一个装饰器,装饰一个函数来使它成为参数的默认解析函数,当使用 `matcher.got(xxx, [args_parser])` 获取到一条消息时,会运行 `matcher.got` 的 `args_parser` ,如果不存在则运行 `@matcher.args_parser`。 + +##### `matcher.pause` + +这个函数用于结束当前事件处理函数,强制接收一条新的消息再运行**下一个消息处理函数**。 + +##### `matcher.reject` + +这个函数用于结束当前事件处理函数,强制接收一条新的消息再**再次运行当前消息处理函数**。 + +##### `matcher.finish` + +这个函数用于直接结束当前事件处理。 + +以上三个函数都拥有一个参数 `prompt`,用于向用户发送一条消息。 + +## 结语 + +至此,相信你已经能够写出一个基础的插件了,更多的用法将会在 进阶 部分进行介绍,这里给出几个小提示: + +- 请千万注意事件处理器的优先级设定 +- 在匹配规则中请勿使用耗时极长的函数 +- 同一个用户可以跨群(私聊)继续他的事件处理(除非做出权限限制,将在后续介绍) diff --git a/archive/2.0.0a1/sidebar.config.json b/archive/2.0.0a1/sidebar.config.json new file mode 100644 index 00000000..9dd289f1 --- /dev/null +++ b/archive/2.0.0a1/sidebar.config.json @@ -0,0 +1,88 @@ +{ + "locales": { + "/": { + "label": "简体中文", + "selectText": "Languages", + "editLinkText": "在 GitHub 上编辑此页", + "lastUpdated": "上次更新", + "nav": [ + { + "text": "主页", + "link": "/" + }, + { + "text": "指南", + "link": "/guide/" + }, + { + "text": "API", + "link": "/api/" + } + ], + "sidebarDepth": 2, + "sidebar": { + "/guide/": [ + { + "title": "指南", + "path": "", + "collapsable": false, + "sidebar": "auto", + "children": [ + "", + "installation", + "getting-started", + "creating-a-project", + "basic-configuration", + "writing-a-plugin" + ] + } + ], + "/api/": [ + { + "title": "NoneBot Api Reference", + "path": "", + "collapsable": false, + "children": [ + { + "title": "nonebot 模块", + "path": "nonebot" + }, + { + "title": "nonebot.typing 模块", + "path": "typing" + }, + { + "title": "nonebot.config 模块", + "path": "config" + }, + { + "title": "nonebot.sched 模块", + "path": "sched" + }, + { + "title": "nonebot.log 模块", + "path": "log" + }, + { + "title": "nonebot.rule 模块", + "path": "rule" + }, + { + "title": "nonebot.permission 模块", + "path": "permission" + }, + { + "title": "nonebot.utils 模块", + "path": "utils" + }, + { + "title": "nonebot.exception 模块", + "path": "exception" + } + ] + } + ] + } + } + } +} \ No newline at end of file diff --git a/docs/.vuepress/components/Messenger.vue b/docs/.vuepress/components/Messenger.vue new file mode 100644 index 00000000..7c12d979 --- /dev/null +++ b/docs/.vuepress/components/Messenger.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 8f76a704..aa2e8ea1 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -20,6 +20,14 @@ module.exports = context => ({ [ "meta", { name: "apple-mobile-web-app-status-bar-style", content: "black" } + ], + [ + "link", + { + rel: "stylesheet", + href: + "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5/css/all.min.css" + } ] ], locales: { @@ -47,8 +55,29 @@ module.exports = context => ({ selectText: "Languages", editLinkText: "在 GitHub 上编辑此页", lastUpdated: "上次更新", - nav: [{ text: "API", link: "/api/" }], + nav: [ + { text: "主页", link: "/" }, + { text: "指南", link: "/guide/" }, + { text: "API", link: "/api/" } + ], + sidebarDepth: 2, sidebar: { + "/guide/": [ + { + title: "指南", + path: "", + collapsable: false, + sidebar: "auto", + children: [ + "", + "installation", + "getting-started", + "creating-a-project", + "basic-configuration", + "writing-a-plugin" + ] + } + ], "/api/": [ { title: "NoneBot Api Reference", @@ -63,17 +92,33 @@ module.exports = context => ({ title: "nonebot.typing 模块", path: "typing" }, + { + title: "nonebot.config 模块", + path: "config" + }, + { + title: "nonebot.sched 模块", + path: "sched" + }, { title: "nonebot.log 模块", path: "log" }, { - title: "nonebot.exception 模块", - path: "exception" + title: "nonebot.rule 模块", + path: "rule" }, { - title: "nonebot.config 模块", - path: "config" + title: "nonebot.permission 模块", + path: "permission" + }, + { + title: "nonebot.utils 模块", + path: "utils" + }, + { + title: "nonebot.exception 模块", + path: "exception" } ] } @@ -95,6 +140,14 @@ module.exports = context => ({ console.log(`Created version ${version} in ${versionDestPath}`); } } + ], + [ + "container", + { + type: "vue", + before: '
',
+        after: "
" + } ] ] }); diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js index 800c2e5f..1c8cfd7a 100644 --- a/docs/.vuepress/enhanceApp.js +++ b/docs/.vuepress/enhanceApp.js @@ -4,12 +4,25 @@ * https://v1.vuepress.vuejs.org/guide/basic-config.html#app-level-enhancements */ +import Vuetify from "vuetify"; +import "vuetify/dist/vuetify.min.css"; + export default ({ Vue, // the version of Vue being used in the VuePress app options, // the options for the root Vue instance router, // the router instance for the app siteData // site metadata }) => { + Vue.use(Vuetify); + options.vuetify = new Vuetify({ + icons: { + iconfont: "fa", + values: { + // + } + } + }); + if (typeof process === "undefined" || process.env.VUE_ENV !== "server") { router.onReady(() => { const { app } = router; @@ -18,7 +31,7 @@ export default ({ setTimeout(() => { const { hash } = document.location; if (hash.length > 1) { - const id = hash.substring(1); + const id = decodeURI(hash.substring(1)); const element = document.getElementById(id); if (element) element.scrollIntoView(); } diff --git a/docs/.vuepress/versions.json b/docs/.vuepress/versions.json new file mode 100644 index 00000000..76e10341 --- /dev/null +++ b/docs/.vuepress/versions.json @@ -0,0 +1,3 @@ +[ + "2.0.0a1" +] \ No newline at end of file diff --git a/docs/api/README.md b/docs/api/README.md index 1aaf25f7..2fa1066a 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -10,7 +10,22 @@ * [nonebot.typing](typing.html) + * [nonebot.config](config.html) + + + * [nonebot.sched](sched.html) + + * [nonebot.log](log.html) - * [nonebot.config](config.html) + * [nonebot.rule](rule.html) + + + * [nonebot.permission](permission.html) + + + * [nonebot.utils](utils.html) + + + * [nonebot.exception](exception.html) diff --git a/docs/api/config.md b/docs/api/config.md index 9fe3464e..fd90f50e 100644 --- a/docs/api/config.md +++ b/docs/api/config.md @@ -67,7 +67,7 @@ NoneBot 运行所使用的 `Driver` 。继承自 `nonebot.driver.BaseDriver` 。 * 说明: -NoneBot 的 HTTP 和 WebSocket 服务端监听的 IP/主机名。 +NoneBot 的 HTTP 和 WebSocket 服务端监听的 IP/主机名。 ### `port` @@ -83,27 +83,6 @@ NoneBot 的 HTTP 和 WebSocket 服务端监听的 IP/主机名。 NoneBot 的 HTTP 和 WebSocket 服务端监听的端口。 -### `secret` - - -* 类型: `Optional[str]` - - -* 默认值: `None` - - -* 说明: -上报连接 NoneBot 所需的密钥。 - - -* 示例: - -```http -POST /cqhttp/ HTTP/1.1 -Authorization: Bearer kSLuTF2GC2Q4q4ugm3 -``` - - ### `debug` @@ -132,7 +111,7 @@ Authorization: Bearer kSLuTF2GC2Q4q4ugm3 * 示例: -```plain +```default API_ROOT={"123456": "http://127.0.0.1:5700"} ``` @@ -143,7 +122,7 @@ API_ROOT={"123456": "http://127.0.0.1:5700"} * 类型: `Optional[float]` -* 默认值: `60.` +* 默认值: `30.` * 说明: @@ -160,7 +139,36 @@ API 请求超时时间,单位: 秒。 * 说明: -API 请求所需密钥,会在调用 API 时在请求头中携带。 +API 请求以及上报所需密钥,在请求头中携带。 + + +* 示例: + +```http +POST /cqhttp/ HTTP/1.1 +Authorization: Bearer kSLuTF2GC2Q4q4ugm3 +``` + + +### `secret` + + +* 类型: `Optional[str]` + + +* 默认值: `None` + + +* 说明: +HTTP POST 形式上报所需签名,在请求头中携带。 + + +* 示例: + +```http +POST /cqhttp/ HTTP/1.1 +X-Signature: sha1=f9ddd4863ace61e64f462d41ca311e3d2c1176e2 +``` ### `superusers` @@ -178,7 +186,7 @@ API 请求所需密钥,会在调用 API 时在请求头中携带。 * 示例: -```plain +```default SUPER_USERS=[12345789] ``` @@ -237,8 +245,21 @@ SUPER_USERS=[12345789] * 示例: -```plain +```default SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒 SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff] SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601 ``` + + +### `apscheduler_config` + + +* 类型: `dict` + + +* 默认值: `{"apscheduler.timezone": "Asia/Shanghai"}` + + +* 说明: +APScheduler 的配置对象,见 [Configuring the Scheduler](https://apscheduler.readthedocs.io/en/latest/userguide.html#configuring-the-scheduler) diff --git a/docs/api/log.md b/docs/api/log.md index fdc24b50..77ce3609 100644 --- a/docs/api/log.md +++ b/docs/api/log.md @@ -39,8 +39,17 @@ NoneBot 使用 [loguru](https://github.com/Delgan/loguru) 来记录日志信息 ```python from nonebot.log import logger - -# 也可以这样 -import logging -logger = logging.getLogger("nonebot") ``` + + +## _class_ `LoguruHandler` + +基类:`logging.Handler` + + +### `emit(record)` + +Do whatever it takes to actually log the specified logging record. + +This version is intended to be implemented by subclasses and so +raises a NotImplementedError. diff --git a/docs/api/nonebot.md b/docs/api/nonebot.md index 8dcd0c20..3c278a5c 100644 --- a/docs/api/nonebot.md +++ b/docs/api/nonebot.md @@ -156,7 +156,7 @@ bots = nonebot.get_bots() * **返回** - * None + * `None` @@ -196,7 +196,7 @@ nonebot.init(database=Database(...)) * **返回** - * None + * `None` diff --git a/docs/api/permission.md b/docs/api/permission.md new file mode 100644 index 00000000..26d3cd34 --- /dev/null +++ b/docs/api/permission.md @@ -0,0 +1,121 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.permission 模块 + +## 权限 + +每个 `Matcher` 拥有一个 `Permission` ,其中是 **异步** `PermissionChecker` 的集合,只要有一个 `PermissionChecker` 检查结果为 `True` 时就会继续运行。 + +:::tip 提示 +`PermissionChecker` 既可以是 async function 也可以是 sync function +::: + + +## `MESSAGE` + + +* **说明**: 匹配任意 `message` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 message type 的 Matcher。 + + +## `NOTICE` + + +* **说明**: 匹配任意 `notice` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 notice type 的 Matcher。 + + +## `REQUEST` + + +* **说明**: 匹配任意 `request` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 request type 的 Matcher。 + + +## `METAEVENT` + + +* **说明**: 匹配任意 `meta_event` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 meta_event type 的 Matcher。 + + +## `USER(*user, perm=)` + + +* **说明** + + 在白名单内且满足 perm + + + +* **参数** + + + * `*user: int`: 白名单 + + + * `perm: Permission`: 需要同时满足的权限 + + + +## `PRIVATE` + + +* **说明**: 匹配任意私聊消息类型事件 + + +## `PRIVATE_FRIEND` + + +* **说明**: 匹配任意好友私聊消息类型事件 + + +## `PRIVATE_GROUP` + + +* **说明**: 匹配任意群临时私聊消息类型事件 + + +## `PRIVATE_OTHER` + + +* **说明**: 匹配任意其他私聊消息类型事件 + + +## `GROUP` + + +* **说明**: 匹配任意群聊消息类型事件 + + +## `GROUP_MEMBER` + + +* **说明**: 匹配任意群员群聊消息类型事件 + +:::warning 警告 +该权限通过 event.sender 进行判断且不包含管理员以及群主! +::: + + +## `GROUP_ADMIN` + + +* **说明**: 匹配任意群管理员群聊消息类型事件 + + +## `GROUP_OWNER` + + +* **说明**: 匹配任意群主群聊消息类型事件 + + +## `SUPERUSER` + + +* **说明**: 匹配任意超级用户消息类型事件 + + +## `EVERYBODY` + + +* **说明**: 匹配任意消息类型事件 diff --git a/docs/api/rule.md b/docs/api/rule.md new file mode 100644 index 00000000..269abc57 --- /dev/null +++ b/docs/api/rule.md @@ -0,0 +1,122 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.rule 模块 + +## 规则 + +每个 `Matcher` 拥有一个 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。 + +:::tip 提示 +`RuleChecker` 既可以是 async function 也可以是 sync function +::: + + +## _class_ `Rule` + +基类:`object` + + +* **说明** + + `Matcher` 规则类,当事件传递时,在 `Matcher` 运行前进行检查。 + + + +* **示例** + + +```python +Rule(async_function) & sync_function +# 等价于 +from nonebot.utils import run_sync +Rule(async_function, run_sync(sync_function)) +``` + + +### `__init__(*checkers)` + + +* **参数** + + + * `*checkers: Callable[[Bot, Event, dict], Awaitable[bool]]`: **异步** RuleChecker + + + +### `checkers` + + +* **说明** + + 存储 `RuleChecker` + + + +* **类型** + + + * `Set[Callable[[Bot, Event, dict], Awaitable[bool]]]` + + + +### _async_ `__call__(bot, event, state)` + + +* **说明** + + 检查是否符合所有规则 + + + +* **参数** + + + * `bot: Bot`: Bot 对象 + + + * `event: Event`: Event 对象 + + + * `state: dict`: 当前 State + + + +* **返回** + + + * `bool` + + + +## `startswith(msg)` + + +* **说明** + + 匹配消息开头 + + + +* **参数** + + + * `msg: str`: 消息开头字符串 + + + +## `endswith(msg)` + + +* **说明** + + 匹配消息结尾 + + + +* **参数** + + + * `msg: str`: 消息结尾字符串 diff --git a/docs/api/sched.md b/docs/api/sched.md new file mode 100644 index 00000000..450fd7d0 --- /dev/null +++ b/docs/api/sched.md @@ -0,0 +1,41 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.sched 模块 + +## 计划任务 + +计划任务使用第三方库 [APScheduler](https://github.com/agronholm/apscheduler) ,使用文档请参考 [APScheduler使用文档](https://apscheduler.readthedocs.io/en/latest/) 。 + + +## `scheduler` + + +* **类型** + + `Optional[apscheduler.schedulers.asyncio.AsyncIOScheduler]` + + + +* **说明** + + 当可选依赖 `APScheduler` 未安装时,`scheduler` 为 None + + 使用 `pip install nonebot[scheduler]` 安装可选依赖 + + + +* **常用示例** + + +```python +from nonebot import scheduler + +@scheduler.scheduled_job("cron", hour="*/2", id="xxx", args=[1], kwargs={arg2: 2}) +async def run_every_2_hour(arg1, arg2): + pass + +scheduler.add_job(run_every_day_from_program_start, "interval", days=1, id="xxx") +``` diff --git a/docs/api/typing.md b/docs/api/typing.md index 92eb4fe4..f68bdfcb 100644 --- a/docs/api/typing.md +++ b/docs/api/typing.md @@ -19,7 +19,7 @@ sidebarDepth: 0 * **类型** - BaseDriver + `BaseDriver` @@ -35,7 +35,7 @@ sidebarDepth: 0 * **类型** - BaseWebSocket + `BaseWebSocket` @@ -51,7 +51,7 @@ sidebarDepth: 0 * **类型** - BaseBot + `BaseBot` @@ -67,7 +67,7 @@ sidebarDepth: 0 * **类型** - BaseEvent + `BaseEvent` @@ -83,7 +83,7 @@ sidebarDepth: 0 * **类型** - BaseMessage + `BaseMessage` @@ -99,7 +99,7 @@ sidebarDepth: 0 * **类型** - BaseMessageSegment + `BaseMessageSegment` @@ -115,7 +115,7 @@ sidebarDepth: 0 * **类型** - Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]] + `Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]` @@ -131,7 +131,7 @@ sidebarDepth: 0 * **类型** - Matcher + `Matcher` @@ -147,7 +147,7 @@ sidebarDepth: 0 * **类型** - Rule + `Rule` @@ -163,7 +163,7 @@ sidebarDepth: 0 * **类型** - Callable[[Bot, Event, dict], Awaitable[bool]] + `Callable[[Bot, Event, dict], Union[bool, Awaitable[bool]]]` @@ -179,7 +179,7 @@ sidebarDepth: 0 * **类型** - Permission + `Permission` @@ -195,7 +195,7 @@ sidebarDepth: 0 * **类型** - Callable[[Bot, Event], Awaitable[bool]] + `Callable[[Bot, Event], Union[bool, Awaitable[bool]]]` @@ -211,7 +211,7 @@ sidebarDepth: 0 * **类型** - Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]] + `Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]` @@ -227,7 +227,7 @@ sidebarDepth: 0 * **类型** - Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]] + `Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]` diff --git a/docs/api/utils.md b/docs/api/utils.md new file mode 100644 index 00000000..7de3ba3f --- /dev/null +++ b/docs/api/utils.md @@ -0,0 +1,39 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +# NoneBot.utils 模块 + + +## `run_sync(func)` + + +* **说明** + + 一个用于包装 sync function 为 async function 的装饰器 + + + +* **参数** + + + * `func: Callable[..., Any]`: 被装饰的同步函数 + + + +* **返回** + + + * `Callable[..., Awaitable[Any]]` + + + +## _class_ `DataclassEncoder` + +基类:`json.encoder.JSONEncoder` + + +* **说明** + + 在JSON序列化 `Message` (List[Dataclass]) 时使用的 `JSONEncoder` diff --git a/docs/guide/README.md b/docs/guide/README.md new file mode 100644 index 00000000..6cf31e90 --- /dev/null +++ b/docs/guide/README.md @@ -0,0 +1,38 @@ +# 概览 + +:::tip 提示 +如果在阅读本文档时遇到难以理解的词汇,请随时查阅 [术语表](../glossary.md) 或使用 [Google 搜索](https://www.google.com/)。 +::: + +:::tip 提示 +初次使用时可能会觉得这里的概览过于枯燥,可以先简单略读之后直接前往 [安装](./installation.md) 查看安装方法,并进行后续的基础使用教程。 +::: + +NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。 + +除了起到解析消息的作用,NoneBot 还为插件提供了大量实用的预设操作和权限控制机制,尤其对于命令处理器,它更是提供了完善且易用的会话机制和内部调用机制,以分别适应命令的连续交互和插件内部功能复用等需求。 + +目前 NoneBot2 在 [FastAPI](https://fastapi.tiangolo.com/) 的基础上封装了与 [CQHTTP(OneBot) 协议](http://cqhttp.cc/)插件的网络交互。 + +得益于 Python 的 [asyncio](https://docs.python.org/3/library/asyncio.html) 机制,NoneBot 处理消息的吞吐量有了很大的保障,再配合 WebSocket 通信方式(也是最建议的通信方式),NoneBot 的性能可以达到 HTTP 通信方式的两倍以上,相较于传统同步 I/O 的 HTTP 通信,更是有质的飞跃。 + +需要注意的是,NoneBot 仅支持 Python 3.7+ 及 CQHTTP(OneBot) 插件 v11+。 + +## 它如何工作? + +NoneBot 的运行离不开 酷 Q 和 CQHTTP 插件。酷 Q 扮演着「无头 QQ 客户端」的角色,它进行实际的消息、通知、请求的接收和发送,当 酷 Q 收到消息时,它将这个消息包装为一个事件(通知和请求同理),并通过它自己的插件机制将事件传送给 CQHTTP 插件,后者再根据其配置中的 `post_url` 或 `ws_reverse_url` 等项来将事件发送至 NoneBot。 + +在 NoneBot 收到事件前,它底层的 aiocqhttp 实际已经先看到了事件,aiocqhttp 根据事件的类型信息,通知到 NoneBot 的相应函数。特别地,对于消息类型的事件,还将消息内容转换成了 `aiocqhttp.message.Message` 类型,以便处理。 + +NoneBot 的事件处理函数收到通知后,对于不同类型的事件,再做相应的预处理和解析,然后调用对应的插件,并向其提供适合此类事件的会话(Session)对象。NoneBot 插件的编写者要做的,就是利用 Session 对象中提供的数据,在插件的处理函数中实现所需的功能。 + +## 特色 + +- 提供直观的测试前端 +- 提供使用简易的脚手架 +- 基于异步 I/O +- 同时支持 HTTP 和反向 WebSocket 通信方式 +- 支持多个机器人账号负载均衡 +- 提供直观的交互式会话接口 +- 提供可自定义的权限控制机制 +- 多种方式渲染要发送的消息内容,使对话足够自然 diff --git a/docs/guide/basic-configuration.md b/docs/guide/basic-configuration.md new file mode 100644 index 00000000..e4c4449f --- /dev/null +++ b/docs/guide/basic-configuration.md @@ -0,0 +1,68 @@ +# 基本配置 + +到目前为止我们还在使用 NoneBot 的默认行为,在开始编写自己的插件之前,我们先尝试在配置文件上动动手脚,让 NoneBot 表现出不同的行为。 + +在上一章节中,我们创建了默认的项目结构,其中 `.env`, `.env.*` 均为项目的配置文件,下面将介绍几种 NoneBot 配置方式。 + +:::danger 警告 +请勿将敏感信息写入配置文件并提交至开源仓库! +::: + +## .env 文件 + +NoneBot 在启动时将会从系统环境变量或者 `.env` 文件中寻找变量 `ENVIRONMENT` (大小写不敏感),默认值为 `prod`。 +这将引导 NoneBot 从系统环境变量或者 `.env.{ENVIRONMENT}` 文件中进一步加载具体配置。 + +现在,我们在 `.env` 文件中写入当前环境信息 + +```bash +# .env +ENVIRONMENT=dev +``` + +## .env.\* 文件 + +详细配置文件,使用 [pydantic](https://pydantic-docs.helpmanual.io/) 加载配置。在 NoneBot 初始化时可以指定忽略 `.env` 中的环境信息转而加载某个配置文件: `nonebot.init(_env_file=".env.dev")`。 + +:::warning 提示 +由于 `pydantic` 使用 JSON 加载配置项,请确保配置项值为 JSON 能够解析的数据。如果 JSON 解析失败将作为字符串处理。 +::: + +示例及说明: + +```bash +HOST=0.0.0.0 # 配置 NoneBot 监听的 IP/主机名 +PORT=8080 # 配置 NoneBot 监听的端口 +DEBUG=true # 开启 debug 模式 **请勿在生产环境开启** +SUPERUSERS=["123456789", "987654321"] # 配置 NoneBot 超级用户 +NICKNAME=["awesome", "bot"] # 配置机器人的昵称 +COMMAND_START=["/", ""] # 配置命令起始字符 +COMMAND_SEP=["."] # 配置命令分割字符 + +# Custom Configs +CUSTOM_CONFIG1="config in env file" +CUSTOM_CONFIG2= # 留空则从系统环境变量读取,如不存在则为空字符串 +``` + +详细的配置项参考 [Config Reference](../api/config.md) 。 + +## bot.py 文件 + +配置项也可以在 NoneBot 初始化时传入。此处可以传入任意合法 Python 变量。当然也可以在初始化完成后修改或新增。 + +示例: + +```python +# bot.py +import nonebot + +nonebot.init(custom_config3="config on init") + +config = nonebot.get_driver().config +config.custom_config3 = "changed after init" +config.custom_config4 = "new config after init" +``` + +## 优先级 + +`bot.py init` > `env file` > `system env` diff --git a/docs/guide/creating-a-project.md b/docs/guide/creating-a-project.md new file mode 100644 index 00000000..74cdb24a --- /dev/null +++ b/docs/guide/creating-a-project.md @@ -0,0 +1,55 @@ +# 创建一个完整的项目 + +上一章中我们已经运行了一个最小的 NoneBot 实例,在这一章,我们将从零开始一个完整的项目。 + +## 目录结构 + +首先,我们可以使用 `nb-cli` 或者自行创建项目目录: + +```bash +pip install nonebot2[cli] +# pip install nb-cli +nb create +``` + +这将创建默认的目录结构 + + +:::vue +AweSome-Bot +├── `awesome_bot` _(**或是 src**)_ +│ └── `plugins` +├── `.env` _(**可选的**)_ +├── `.env.dev` _(**可选的**)_ +├── `.env.prod` _(**可选的**)_ +├── .gitignore +├── `bot.py` +├── docker-compose.yml +├── Dockerfile +├── `pyproject.toml` +└── README.md +::: + + +- `awesome_bot/plugins` 或 `src/plugins`: 用于存放编写的 bot 插件 +- `.env`, `.env.dev`, `.env.prod`: 各环境配置文件 +- `bot.py`: bot 入口文件 +- `pyproject.toml`: 项目依赖管理文件,默认使用 [poetry](https://python-poetry.org/) + +## 启动 Bot + +如果你使用 `nb-cli` + +```bash +nb run [--file=bot.py] [--app=app] +``` + +或者使用 + +```bash +python bot.py +``` + +:::tip 提示 +如果在 bot 入口文件内定义了 asgi server, `nb-cli` 将会为你启动**冷重载模式** +::: diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md new file mode 100644 index 00000000..d52a9396 --- /dev/null +++ b/docs/guide/getting-started.md @@ -0,0 +1,146 @@ +# 开始使用 + +一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 NoneBot 实例的准备。 + +## 最小实例 + +使用你最熟悉的编辑器或 IDE,创建一个名为 `bot.py` 的文件,内容如下: + +```python{3,4,7} +import nonebot + +nonebot.init() +nonebot.load_builtin_plugins() + +if __name__ == "__main__": + nonebot.run() +``` + +这几行高亮代码将依次: + +1. 使用默认配置初始化 NoneBot 包 +2. 加载 NoneBot 内置的插件 +3. 在地址 `127.0.0.1:8080` 运行 NoneBot + +在命令行使用如下命令即可运行这个 NoneBot 实例: + +```bash +python bot.py +``` + +运行后会产生如下日志: + +```default +09-14 21:02:00 [INFO] nonebot | Succeeded to import "nonebot.plugins.base" +09-14 21:02:00 [INFO] nonebot | Running NoneBot... +09-14 21:02:00 [INFO] uvicorn | Started server process [1234] +09-14 21:02:00 [INFO] uvicorn | Waiting for application startup. +09-14 21:02:00 [INFO] nonebot | Scheduler Started +09-14 21:02:00 [INFO] uvicorn | Application startup complete. +09-14 21:02:00 [INFO] uvicorn | Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit) +``` + +## 配置 QQ 协议端 + +单纯运行 NoneBot 实例并不会产生任何效果,因为此刻 QQ 这边还不知道 NoneBot 的存在,也就无法把消息发送给它,因此现在需要使用一个无头 QQ 来把消息等事件上报给 NoneBot。 + +目前支持的协议有: + +- [OneBot(CQHTTP)](https://github.com/howmanybots/onebot) + +QQ 协议端举例: + +- [Mirai](https://github.com/mamoe/mirai) + [cqhttp-mirai](https://github.com/yyuueexxiinngg/cqhttp-mirai) +- [cqhttp-mirai-embedded](https://github.com/yyuueexxiinngg/cqhttp-mirai/tree/embedded) +- [Mirai](https://github.com/mamoe/mirai) + [Mirai Native](https://github.com/iTXTech/mirai-native) + [CQHTTP](https://github.com/richardchien/coolq-http-api) +- [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) (基于 [MiraiGo](https://github.com/Mrs4s/MiraiGo)) +- [OICQ-http-api](https://github.com/takayama-lily/onebot) (基于 [OICQ](https://github.com/takayama-lily/oicq)) + +这里以 [go-cqhttp](https://github.com/Mrs4s/go-cqhttp) 为例 + +1. 下载 go-cqhttp 对应平台的 release 文件 +2. 双击 exe 文件或者使用 `./go-cqhttp` 启动 +3. 生成默认配置文件并修改默认配置 + +```json{2,3,30-31} +{ + "uin": 你的QQ号, + "password": "你的密码", + "encrypt_password": false, + "password_encrypted": "", + "enable_db": true, + "access_token": "", + "relogin": { + "enabled": true, + "relogin_delay": 3, + "max_relogin_times": 0 + }, + "ignore_invalid_cqcode": false, + "force_fragmented": true, + "heartbeat_interval": 0, + "http_config": { + "enabled": false, + "host": "0.0.0.0", + "port": 5700, + "timeout": 0, + "post_urls": {} + }, + "ws_config": { + "enabled": false, + "host": "0.0.0.0", + "port": 6700 + }, + "ws_reverse_servers": [ + { + "enabled": true, + "reverse_url": "ws://127.0.0.1:8080/cqhttp/ws", + "reverse_api_url": "", + "reverse_event_url": "", + "reverse_reconnect_interval": 3000 + } + ], + "post_message_format": "string", + "debug": false, + "log_level": "" +} +``` + +其中 `ws://127.0.0.1:8080/cqhttp/ws` 中的 `127.0.0.1` 和 `8080` 应分别对应 nonebot 配置的 HOST 和 PORT + +## 历史性的第一次对话 + +一旦新的配置文件正确生效之后,NoneBot 所在的控制台(如果正在运行的话)应该会输出类似下面的内容(两条访问日志): + +```default +09-14 21:31:16 [INFO] uvicorn | ('127.0.0.1', 12345) - "WebSocket /cqhttp/ws" [accepted] +09-14 21:31:16 [INFO] nonebot | WebSocket Connection from CQHTTP Bot 你的QQ号 Accepted! +``` + +这表示 QQ 协议端已经成功地使用 CQHTTP 协议连接上了 NoneBot。 + +:::warning 注意 +如果到这一步你没有看到上面这样的成功日志,CQHTTP 的日志中在不断地重连或无反应,请注意检查配置中的 IP 和端口是否确实可以访问。比较常见的出错点包括: + +- NoneBot 监听 `0.0.0.0`,然后在 CQHTTP 配置中填了 `ws://0.0.0.0:8080/cqhttp/ws` +- 在 Docker 容器内运行 CQHTTP,并通过 `127.0.0.1` 访问宿主机上的 NoneBot +- 想从公网访问,但没有修改云服务商的安全组策略或系统防火墙 +- NoneBot 所监听的端口存在冲突,已被其它程序占用 +- 弄混了 NoneBot 的 `host`、`port` 参数与 CQHTTP 配置中的 `host`、`port` 参数 +- 使用了 `ws_reverse_api_url` 和 `ws_reverse_event_url` 而非 universal client +- `ws://` 错填为 `http://` +- CQHTTP 或 NoneBot 启动时遭到外星武器干扰 + +请尝试重启 CQHTTP、重启 NoneBot、更换端口、修改防火墙、重启系统、仔细阅读前面的文档及提示、更新 CQHTTP 和 NoneBot 到最新版本等方式来解决。 +::: + +现在,尝试向你的 QQ 机器人账号发送如下内容: + +```default +/say 你好,世界 +``` + +到这里如果一切 OK,你应该会收到机器人给你回复了 `你好,世界`。这一历史性的对话标志着你已经成功地运行了一个 NoneBot 的最小实例,开始了编写更强大的 QQ 机器人的创意之旅! + + + + diff --git a/docs/guide/installation.md b/docs/guide/installation.md new file mode 100644 index 00000000..06a88598 --- /dev/null +++ b/docs/guide/installation.md @@ -0,0 +1,73 @@ +# 安装 + +## NoneBot + +:::warning 注意 +请确保你的 Python 版本 >= 3.7。 +::: + +```bash +pip install nonebot2 +``` + +如果你需要使用最新的(可能尚未发布的)特性,可以克隆 Git 仓库后手动安装: + +```bash +git clone https://github.com/nonebot/nonebot2.git +cd nonebot2 +poetry install --no-dev # 推荐 +pip install . # 不推荐 +``` + +## 额外依赖 + +### APScheduler + +A task scheduling library for Python. + +可用于计划任务,后台执行任务等 + +```bash +pip install nonebot2[scheduler] +poetry add nonebot2[scheduler] +``` + +[View On GitHub](https://github.com/agronholm/apscheduler) + +### NoneBot-Test + +A test frontend for nonebot2. + +通过前端展示 nonebot 已加载的插件以及运行状态,同时可以用于模拟发送事件测试机器人 + +```bash +pip install nonebot2[test] +poetry add nonebot2[test] +``` + +[View On GitHub](https://github.com/nonebot/nonebot-test) + +### CLI + +CLI for nonebot2. + +一个多功能脚手架 + +```bash +pip install nonebot2[cli] +poetry add nonebot2[cli] +``` + +[View On GitHub](https://github.com/yanyongyu/nb-cli) + +### 我全都要 + +```bash +pip install nonebot2[full] +poetry add nonebot2[full] +``` + +```bash +pip install nonebot2[cli,scheduler] +poetry add nonebot2[cli,scheduler] +``` diff --git a/docs/guide/writing-a-plugin.md b/docs/guide/writing-a-plugin.md new file mode 100644 index 00000000..bd176750 --- /dev/null +++ b/docs/guide/writing-a-plugin.md @@ -0,0 +1,290 @@ +# 编写插件 + +本章将以一个天气查询插件为例,教学如何编写自己的命令。 + +## 加载插件 + +在 [创建一个完整的项目](creating-a-project) 一章节中,我们已经创建了插件目录 `awesome_bot/plugins`,现在我们在机器人入口文件中加载它。当然,你也可以单独加载一个插件。 + +:::tip 提示 +加载插件目录时,目录下以 `_` 下划线开头的插件将不会被加载! +::: + +在 `bot.py` 文件中添加以下行: + +```python{5,7} +import nonebot + +nonebot.init() +# 加载单独的一个插件,参数为合法的python包名 +nonebot.load_plugin("nonebot.plugins.base") +# 加载插件目录,该目录下为各插件,以下划线开头的插件将不会被加载 +nonebot.load_plugins("awesome_bot/plugins") + +app = nonebot.get_asgi() + +if __name__ == "__main__": + nonebot.run() +``` + +尝试运行 `nb run` 或者 `python bot.py`,可以看到日志输出了类似如下内容: + +```plain +09-19 21:51:59 [INFO] nonebot | Succeeded to import "nonebot.plugins.base" +09-19 21:51:59 [INFO] nonebot | Succeeded to import "plugin_in_folder" +``` + +## 创建插件 + +现在我们已经有了一个空的插件目录,我们可以开始创建插件了!插件有两种形式 + +### 单文件形式 + +在插件目录下创建名为 `weather.py` 的 Python 文件,暂时留空,此时目录结构如下: + + +:::vue +AweSome-Bot +├── awesome_bot +│ └── plugins +│ └── `weather.py` +├── .env +├── .env.dev +├── .env.prod +├── .gitignore +├── bot.py +├── docker-compose.yml +├── Dockerfile +├── pyproject.toml +└── README.md +::: + + +这个时候它已经可以被称为一个插件了,尽管它还什么都没做。 + +### 包形式 + +在插件目录下创建文件夹 `weather`,并在该文件夹下创建文件 `__init__.py`,此时目录结构如下: + + +:::vue +AweSome-Bot +├── awesome_bot +│ └── plugins +│ └── `weather` +│ └── `__init__.py` +├── .env +├── .env.dev +├── .env.prod +├── .gitignore +├── bot.py +├── docker-compose.yml +├── Dockerfile +├── pyproject.toml +└── README.md +::: + + +这个时候 `weather` 就是一个合法的 Python 包了,同时也是合法的 NoneBot 插件,插件内容可以在 `__init__.py` 中编写。 + +## 编写真正的内容 + +好了,现在插件已经可以正确加载,我们可以开始编写命令的实际代码了。在 `weather.py` 中添加如下代码: + +```python +from nonebot import on_command +from nonebot.rule import to_me +from nonebot.adapters.cqhttp import Bot, Event + +weather = on_command("天气", rule=to_me(), priority=5) + + +@weather.handle() +async def handle_first_receive(bot: Bot, event: Event, state: dict): + args = str(event.message).strip() # 首次发送命令时跟随的参数,例:/天气 上海,则args为上海 + if args: + state["city"] = args # 如果用户发送了参数则直接赋值 + + +@weather.got("city", prompt="你想查询哪个城市的天气呢?") +async def handle_city(bot: Bot, event: Event, state: dict): + city = state["city"] + if city not in ["上海", "北京"]: + await weather.reject("你想查询的城市暂不支持,请重新输入!") + city_weather = await get_weather(city) + await weather.finish(city_weather) + + +async def get_weather(city: str): + return f"{city}的天气是..." +``` + +为了简单起见,我们在这里的例子中没有接入真实的天气数据,但要接入也非常简单,你可以使用中国天气网、和风天气等网站提供的 API。 + +下面我们来说明这段代码是如何工作的。 + +:::tip 提示 +从这里开始,你需要对 Python 的 asyncio 编程有所了解,因为 NoneBot 是完全基于 asyncio 的,具体可以参考 [廖雪峰的 Python 教程](https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152) +::: + +### 注册一个 [事件响应器](../api/matcher.md) + +```python{4} +from nonebot import on_command +from nonebot.rule import to_me +from nonebot.permission import Permission + +weather = on_command("天气", rule=to_me(), permission=Permission(), priority=5) +``` + +在上方代码中,我们注册了一个事件响应器 `Matcher`,它由几个部分组成: + +1. `on_command` 注册一个消息类型的命令处理器 +2. `"天气"` 指定 command 参数 - 命令名 +3. `rule` 补充事件响应器的匹配规则 +4. `priority` 事件响应器优先级 +5. `block` 是否阻止事件传递 + +其他详细配置可以参考 API 文档,下面我们详细说明各个部分: + +#### 事件响应器类型 type + +事件响应器类型其实就是对应 `Event.type` ,NoneBot 提供了一个基础类型事件响应器 `on()` 以及一些内置的事件响应器。 + +- `on("事件类型")`: 基础事件响应器,第一个参数为事件类型,空字符串表示不限 +- `on_metaevent()` ~ `on("meta_event")`: 元事件响应器 +- `on_message()` ~ `on("message")`: 消息事件响应器 +- `on_request()` ~ `on("request")`: 请求事件响应器 +- `on_notice()` ~ `on("notice")`: 通知事件响应器 +- `on_startswith(str)` ~ `on("message", startswith(str))`: 消息开头匹配处理器 +- `on_endswith(str)` ~ `on("message", endswith(str))`: 消息结尾匹配处理器 +- `on_command(str|tuple)` ~ `on("message", command(str|tuple))`: 命令处理器 +- `on_regax(pattern_str)` ~ `on("message", regax(pattern_str))`: 正则匹配处理器 + +#### 匹配规则 rule + +事件响应器的匹配规则即 `Rule`,由非负个 `RuleChecker` 组成,当所有 `RuleChecker` 返回 `True` 时匹配成功。这些 `RuleChecker` 的形式如下: + +```python +async def check(bot: Bot, event: Event, state: dict) -> bool: + return True + +def check(bot: Bot, event: Event, state: dict) -> bool: + return True +``` + +`Rule` 和 `RuleChecker` 之间可以使用 `与 &` 互相组合: + +```python +from nonebot.rule import Rule + +Rule(async_checker1) & sync_checker & async_checker2 +``` + +:::danger 警告 +`Rule(*checkers)` 只接受 async function,或使用 `nonebot.utils.run_sync` 自行包裹 sync function。在使用 `与 &` 时,NoneBot 会自动包裹 sync function +::: + +#### 优先级 priority + +事件响应器的优先级代表事件响应器的执行顺序,同一优先级的事件响应器会 **同时执行!** + +:::tip 提示 +使用 `nonebot-test` 可以看到当前所有事件响应器的执行流程,有助理解事件响应流程! + +```bash +pip install nonebot2[test] +``` + +::: + +#### 阻断 block + +当有任意事件响应器发出了阻止事件传递信号时,该事件将不再会传递给下一优先级,直接结束处理。 + +NoneBot 内置的事件响应器中,所有 `message` 类的事件响应器默认会阻断事件传递,其他则不会。 + +### 编写事件处理函数 [Handler](../api/typing.md#handler) + +```python{1,2,8,9} +@weather.handle() +async def handle_first_receive(bot: Bot, event: Event, state: dict): + args = str(event.message).strip() # 首次发送命令时跟随的参数,例:/天气 上海,则args为上海 + if args: + state["city"] = args # 如果用户发送了参数则直接赋值 + + +@weather.got("city", prompt="你想查询哪个城市的天气呢?") +async def handle_city(bot: Bot, event: Event, state: dict): + city = state["city"] + if city not in ["上海", "北京"]: + await weather.reject("你想查询的城市暂不支持,请重新输入!") + city_weather = await get_weather(city) + await weather.finish(city_weather) +``` + +在上面的代码中,我们给 `weather` 事件响应器添加了两个事件处理函数:`handle_first_receive`, `handle_city` + +其中有几个要点,我们一一解释: + +#### 添加一个事件处理函数 + +在事件响应器响应事件时,事件处理函数会依次顺序执行,也就是说,与添加顺序一致。 + +我们可以使用 `@matcher.handle()` 装饰器来简单地为该事件响应器添加一个处理函数。 + +同时,NoneBot 内置了几种添加事件处理函数方式以方便处理: + +- `@matcher.receive()`: 指示 NoneBot 接收一条新的用户消息以继续执行后续处理函数。 +- `@matcher.got(key, [prompt="请输入key"], [args_parser=function])`: 指示 NoneBot 当 `state` 中不存在 `key` 时向用户发送 `prompt` 等待用户回复并赋值给 `state[key]` + +这些装饰器可以套娃使用!例如: + +```python +@matcher.got("key1") +@matcher.got("key2") +async def handle(bot: Bot, event: Event, state: dict): + pass +``` + +#### 事件处理函数参数 + +事件处理函数类型为 `Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]` 。 + +参数分别为: + +1. [nonebot.typing.Bot](../api/typing.md#bot): 即事件上报连接对应的 Bot 对象,为 BaseBot 的子类。特别注意,此处的类型注释可以替换为指定的 Bot 类型,例如:`nonebot.adapters.cqhttp.Bot`,只有在上报事件的 Bot 类型与类型注释相符时才会执行该处理函数!可用于多平台进行不同的处理。 +2. [nonebot.typing.Event](../api/typing.md#event): 即上报事件对象,可以获取到上报的所有信息。 +3. `state`: 状态字典,可以存储任意的信息 + +#### 处理事件 + +在事件处理函数中,我们只需要对 `event` 做出相应的处理,存入状态字典 `state` 中,或者向用户发送消息、调用某个机器人 API 等等。 + +在 NoneBot 中,提供了几种特殊的处理函数: + +##### `@matcher.args_parser` + +这是一个装饰器,装饰一个函数来使它成为参数的默认解析函数,当使用 `matcher.got(xxx, [args_parser])` 获取到一条消息时,会运行 `matcher.got` 的 `args_parser` ,如果不存在则运行 `@matcher.args_parser`。 + +##### `matcher.pause` + +这个函数用于结束当前事件处理函数,强制接收一条新的消息再运行**下一个消息处理函数**。 + +##### `matcher.reject` + +这个函数用于结束当前事件处理函数,强制接收一条新的消息再**再次运行当前消息处理函数**。 + +##### `matcher.finish` + +这个函数用于直接结束当前事件处理。 + +以上三个函数都拥有一个参数 `prompt`,用于向用户发送一条消息。 + +## 结语 + +至此,相信你已经能够写出一个基础的插件了,更多的用法将会在 进阶 部分进行介绍,这里给出几个小提示: + +- 请千万注意事件处理器的优先级设定 +- 在匹配规则中请勿使用耗时极长的函数 +- 同一个用户可以跨群(私聊)继续他的事件处理(除非做出权限限制,将在后续介绍) diff --git a/docs_build/README.rst b/docs_build/README.rst index 1ced2b53..cb726c38 100644 --- a/docs_build/README.rst +++ b/docs_build/README.rst @@ -2,7 +2,12 @@ NoneBot Api Reference ===================== :模块索引: - - `nonebot `_ - - `nonebot.typing `_ - - `nonebot.log `_ - - `nonebot.config `_ + - `nonebot `_ + - `nonebot.typing `_ + - `nonebot.config `_ + - `nonebot.sched `_ + - `nonebot.log `_ + - `nonebot.rule `_ + - `nonebot.permission `_ + - `nonebot.utils `_ + - `nonebot.exception `_ diff --git a/docs_build/permission.rst b/docs_build/permission.rst new file mode 100644 index 00000000..15144a62 --- /dev/null +++ b/docs_build/permission.rst @@ -0,0 +1,11 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +NoneBot.permission 模块 +==================== + +.. automodule:: nonebot.permission + :members: + :show-inheritance: diff --git a/docs_build/rule.rst b/docs_build/rule.rst new file mode 100644 index 00000000..e182de79 --- /dev/null +++ b/docs_build/rule.rst @@ -0,0 +1,12 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +NoneBot.rule 模块 +==================== + +.. automodule:: nonebot.rule + :members: + :special-members: + :show-inheritance: diff --git a/docs_build/sched.rst b/docs_build/sched.rst new file mode 100644 index 00000000..714be93d --- /dev/null +++ b/docs_build/sched.rst @@ -0,0 +1,11 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +NoneBot.sched 模块 +=================== + +.. automodule:: nonebot.sched + :members: + :show-inheritance: diff --git a/docs_build/utils.rst b/docs_build/utils.rst new file mode 100644 index 00000000..776bbfd0 --- /dev/null +++ b/docs_build/utils.rst @@ -0,0 +1,12 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +NoneBot.utils 模块 +================== + + +.. autodecorator:: nonebot.utils.run_sync +.. autoclass:: nonebot.utils.DataclassEncoder + :show-inheritance: diff --git a/nonebot/__init__.py b/nonebot/__init__.py index 5e7a2034..bd0ee80b 100644 --- a/nonebot/__init__.py +++ b/nonebot/__init__.py @@ -108,6 +108,7 @@ def get_bots() -> Union[NoReturn, Dict[str, Bot]]: return driver.bots +from nonebot.sched import scheduler from nonebot.config import Env, Config from nonebot.log import logger, default_filter from nonebot.adapters.cqhttp import Bot as CQBot @@ -135,7 +136,7 @@ def init(*, _env_file: Optional[str] = None, **kwargs): :返回: - - `None` + - ``None`` :用法: @@ -146,10 +147,10 @@ def init(*, _env_file: Optional[str] = None, **kwargs): """ global _driver if not _driver: - logger.debug("NoneBot is initializing...") + logger.info("NoneBot is initializing...") env = Env() logger.opt( - colors=True).debug(f"Current Env: {env.environment}") + colors=True).info(f"Current Env: {env.environment}") config = Config(**kwargs, _env_file=_env_file or f".env.{env.environment}") @@ -169,6 +170,9 @@ def init(*, _env_file: Optional[str] = None, **kwargs): logger.debug("Loading nonebot test frontend...") nonebot_test.init() + if scheduler: + _driver.on_startup(_start_scheduler) + def run(host: Optional[str] = None, port: Optional[int] = None, @@ -188,7 +192,7 @@ def run(host: Optional[str] = None, :返回: - - `None` + - ``None`` :用法: @@ -201,6 +205,13 @@ def run(host: Optional[str] = None, get_driver().run(host, port, *args, **kwargs) +async def _start_scheduler(): + if scheduler and not scheduler.running: + scheduler.configure(_driver.config.apscheduler_config) + scheduler.start() + logger.opt(colors=True).info("Scheduler Started") + + from nonebot.plugin import on_message, on_notice, on_request, on_metaevent from nonebot.plugin import on_startswith, on_endswith, on_command, on_regex from nonebot.plugin import load_plugin, load_plugins, load_builtin_plugins, get_loaded_plugins diff --git a/nonebot/adapters/cqhttp.py b/nonebot/adapters/cqhttp.py index 6424c1d0..dc7ad38a 100644 --- a/nonebot/adapters/cqhttp.py +++ b/nonebot/adapters/cqhttp.py @@ -61,13 +61,16 @@ async def _check_reply(bot: "Bot", event: "Event"): if event.type != "message": return - first_msg_seg = event.message[0] - if first_msg_seg.type == "reply": - msg_id = first_msg_seg.data["id"] - event.reply = await bot.get_msg(message_id=msg_id) - if event.reply["sender"]["user_id"] == event.self_id: - event.to_me = True - del event.message[0] + try: + index = list(map(lambda x: x.type == "reply", + event.message)).index(True) + except ValueError: + return + msg_seg = event.message[index] + event.reply = await bot.get_msg(message_id=msg_seg.data["id"]) + if event.reply["sender"]["user_id"] == event.self_id: + event.to_me = True + del event.message[index] def _check_at_me(bot: "Bot", event: "Event"): @@ -84,6 +87,18 @@ def _check_at_me(bot: "Bot", event: "Event"): if first_msg_seg == at_me_seg: event.to_me = True del event.message[0] + if event.message[0].type == "text": + event.message[0].data["text"] = event.message[0].data[ + "text"].lstrip() + if not event.message[0].data["text"]: + del event.message[0] + if event.message[0] == at_me_seg: + del event.message[0] + if event.message[0].type == "text": + event.message[0].data["text"] = event.message[0].data[ + "text"].lstrip() + if not event.message[0].data["text"]: + del event.message[0] if not event.to_me: # check the last segment @@ -199,14 +214,19 @@ class Bot(BaseBot): ResultStore.add_result(message) return - event = Event(message) + try: + event = Event(message) - # Check whether user is calling me - await _check_reply(self, event) - _check_at_me(self, event) - _check_nickname(self, event) + # Check whether user is calling me + await _check_reply(self, event) + _check_at_me(self, event) + _check_nickname(self, event) - await handle_event(self, event) + await handle_event(self, event) + except Exception as e: + logger.opt(colors=True, exception=e).error( + f"Failed to handle event. Raw: {message}" + ) @overrides(BaseBot) async def call_api(self, api: str, **data) -> Union[Any, NoReturn]: diff --git a/nonebot/config.py b/nonebot/config.py index 5ecc93e6..1680f40c 100644 --- a/nonebot/config.py +++ b/nonebot/config.py @@ -79,6 +79,9 @@ class BaseConfig(BaseSettings): if env_file_vars: for env_name, env_val in env_file_vars.items(): + if (env_val is None or + len(env_val) == 0) and env_name in env_vars: + env_val = env_vars[env_name] try: env_val = self.__config__.json_loads(env_val) except ValueError as e: @@ -131,7 +134,7 @@ class Config(BaseConfig): - 类型: ``IPvAnyAddress`` - 默认值: ``127.0.0.1`` - 说明: - NoneBot 的 HTTP 和 WebSocket 服务端监听的 IP/主机名。 + NoneBot 的 HTTP 和 WebSocket 服务端监听的 IP/主机名。 """ port: int = 8080 """ @@ -140,19 +143,6 @@ class Config(BaseConfig): - 说明: NoneBot 的 HTTP 和 WebSocket 服务端监听的端口。 """ - secret: Optional[str] = None - """ - - 类型: ``Optional[str]`` - - 默认值: ``None`` - - 说明: - 上报连接 NoneBot 所需的密钥。 - - 示例: - - .. code-block:: http - - POST /cqhttp/ HTTP/1.1 - Authorization: Bearer kSLuTF2GC2Q4q4ugm3 - """ debug: bool = False """ - 类型: ``bool`` @@ -170,7 +160,7 @@ class Config(BaseConfig): 以机器人 ID 为键,上报地址为值的字典,环境变量或文件中应使用 json 序列化。 - 示例: - .. code-block:: plain + .. code-block:: default API_ROOT={"123456": "http://127.0.0.1:5700"} """ @@ -186,7 +176,26 @@ class Config(BaseConfig): - 类型: ``Optional[str]`` - 默认值: ``None`` - 说明: - API 请求所需密钥,会在调用 API 时在请求头中携带。 + API 请求以及上报所需密钥,在请求头中携带。 + - 示例: + + .. code-block:: http + + POST /cqhttp/ HTTP/1.1 + Authorization: Bearer kSLuTF2GC2Q4q4ugm3 + """ + secret: Optional[str] = None + """ + - 类型: ``Optional[str]`` + - 默认值: ``None`` + - 说明: + HTTP POST 形式上报所需签名,在请求头中携带。 + - 示例: + + .. code-block:: http + + POST /cqhttp/ HTTP/1.1 + X-Signature: sha1=f9ddd4863ace61e64f462d41ca311e3d2c1176e2 """ # bot runtime configs @@ -198,7 +207,7 @@ class Config(BaseConfig): 机器人超级用户。 - 示例: - .. code-block:: plain + .. code-block:: default SUPER_USERS=[12345789] """ @@ -231,12 +240,22 @@ class Config(BaseConfig): 等待用户回复的超时时间。 - 示例: - .. code-block:: plain + .. code-block:: default SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒 SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff] SESSION_EXPIRE_TIMEOUT=P[DD]DT[HH]H[MM]M[SS]S # ISO 8601 """ + apscheduler_config: dict = {"apscheduler.timezone": "Asia/Shanghai"} + """ + - 类型: ``dict`` + - 默认值: ``{"apscheduler.timezone": "Asia/Shanghai"}`` + - 说明: + APScheduler 的配置对象,见 `Configuring the Scheduler`_ + + .. _Configuring the Scheduler: + https://apscheduler.readthedocs.io/en/latest/userguide.html#configuring-the-scheduler + """ # custom configs # custom configs can be assigned during nonebot.init diff --git a/nonebot/drivers/fastapi.py b/nonebot/drivers/fastapi.py index 663998d1..9b95a462 100644 --- a/nonebot/drivers/fastapi.py +++ b/nonebot/drivers/fastapi.py @@ -114,7 +114,8 @@ class Driver(BaseDriver): adapter: str, data: dict = Body(...), x_self_id: Optional[str] = Header(None), - x_signature: Optional[str] = Header(None)): + x_signature: Optional[str] = Header(None), + auth: Optional[str] = Depends(get_auth_bearer)): # 检查self_id if not x_self_id: logger.warning("Missing X-Self-ID Header") @@ -135,6 +136,14 @@ class Driver(BaseDriver): raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Signature is invalid") + access_token = self.config.access_token + if access_token and access_token != auth: + logger.warning("Authorization Header is invalid" + if auth else "Missing Authorization Header") + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, + detail="Authorization Header is invalid" + if auth else "Missing Authorization Header") + if not isinstance(data, dict): logger.warning("Data received is invalid") raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST) @@ -161,22 +170,25 @@ class Driver(BaseDriver): adapter: str, websocket: FastAPIWebSocket, x_self_id: str = Header(None), - access_token: Optional[str] = Depends(get_auth_bearer)): + auth: Optional[str] = Depends(get_auth_bearer)): ws = WebSocket(websocket) - secret = self.config.secret - if secret is not None and secret != access_token: + access_token = self.config.access_token + if access_token and access_token != auth: logger.warning("Authorization Header is invalid" - if access_token else "Missing Authorization Header") + if auth else "Missing Authorization Header") await ws.close(code=status.WS_1008_POLICY_VIOLATION) + return if not x_self_id: logger.warning(f"Missing X-Self-ID Header") await ws.close(code=status.WS_1008_POLICY_VIOLATION) + return if x_self_id in self._clients: logger.warning(f"Connection Conflict: self_id {x_self_id}") await ws.close(code=status.WS_1008_POLICY_VIOLATION) + return # Create Bot Object if adapter in self._adapters: diff --git a/nonebot/matcher.py b/nonebot/matcher.py index 351661b9..a2a5ad66 100644 --- a/nonebot/matcher.py +++ b/nonebot/matcher.py @@ -6,8 +6,8 @@ import typing import inspect from functools import wraps from datetime import datetime +from contextvars import ContextVar from collections import defaultdict -from contextvars import Context, ContextVar, copy_context from nonebot.rule import Rule from nonebot.permission import Permission, USER @@ -101,8 +101,7 @@ class Matcher(metaclass=MatcherMeta): @classmethod async def check_perm(cls, bot: Bot, event: Event) -> bool: - return (event.type == (cls.type or event.type) and - await cls.permission(bot, event)) + return await cls.permission(bot, event) @classmethod async def check_rule(cls, bot: Bot, event: Event, state: dict) -> bool: @@ -114,7 +113,8 @@ class Matcher(metaclass=MatcherMeta): Returns: bool: 条件成立与否 """ - return await cls.rule(bot, event, state) + return (event.type == (cls.type or event.type) and + await cls.rule(bot, event, state)) @classmethod def args_parser(cls, func: ArgsParser) -> ArgsParser: @@ -166,8 +166,8 @@ class Matcher(metaclass=MatcherMeta): raise PausedException async def _key_parser(bot: Bot, event: Event, state: dict): - if key in state: - return + # if key in state: + # return parser = args_parser or cls._default_parser if parser: await parser(bot, event, state) @@ -252,6 +252,7 @@ class Matcher(metaclass=MatcherMeta): temp=True, priority=0, block=True, + module=self.module, default_state=self.state, expire_time=datetime.now() + bot.config.session_expire_timeout) except PausedException: @@ -263,6 +264,7 @@ class Matcher(metaclass=MatcherMeta): temp=True, priority=0, block=True, + module=self.module, default_state=self.state, expire_time=datetime.now() + bot.config.session_expire_timeout) except FinishedException: diff --git a/nonebot/permission.py b/nonebot/permission.py index fefda749..1f43cf38 100644 --- a/nonebot/permission.py +++ b/nonebot/permission.py @@ -1,19 +1,49 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +""" +权限 +==== + +每个 ``Matcher`` 拥有一个 ``Permission`` ,其中是 **异步** ``PermissionChecker`` 的集合,只要有一个 ``PermissionChecker`` 检查结果为 ``True`` 时就会继续运行。 + +\:\:\:tip 提示 +``PermissionChecker`` 既可以是 async function 也可以是 sync function +\:\:\: +""" import asyncio from nonebot.utils import run_sync -from nonebot.typing import Bot, Event, Union, NoReturn, PermissionChecker +from nonebot.typing import Bot, Event, Union, NoReturn, Callable, Awaitable, PermissionChecker class Permission: __slots__ = ("checkers",) - def __init__(self, *checkers: PermissionChecker) -> None: - self.checkers = list(checkers) + def __init__(self, *checkers: Callable[[Bot, Event], + Awaitable[bool]]) -> None: + """ + :参数: + * ``*checkers: Callable[[Bot, Event], Awaitable[bool]]``: **异步** PermissionChecker + """ + self.checkers = set(checkers) + """ + :说明: + 存储 ``PermissionChecker`` + :类型: + * ``Set[Callable[[Bot, Event], Awaitable[bool]]]`` + """ async def __call__(self, bot: Bot, event: Event) -> bool: + """ + :说明: + 检查是否满足某个权限 + :参数: + * ``bot: Bot``: Bot 对象 + * ``event: Event``: Event 对象 + :返回: + - ``bool`` + """ if not self.checkers: return True results = await asyncio.gather( @@ -25,13 +55,13 @@ class Permission: def __or__(self, other: Union["Permission", PermissionChecker]) -> "Permission": - checkers = [*self.checkers] + checkers = self.checkers.copy() if isinstance(other, Permission): - checkers.extend(other.checkers) + checkers |= other.checkers elif asyncio.iscoroutinefunction(other): - checkers.append(other) + checkers.add(other) # type: ignore else: - checkers.append(run_sync(other)) + checkers.add(run_sync(other)) return Permission(*checkers) @@ -52,12 +82,31 @@ async def _metaevent(bot: Bot, event: Event) -> bool: MESSAGE = Permission(_message) +""" +- **说明**: 匹配任意 ``message`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 message type 的 Matcher。 +""" NOTICE = Permission(_notice) +""" +- **说明**: 匹配任意 ``notice`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 notice type 的 Matcher。 +""" REQUEST = Permission(_request) +""" +- **说明**: 匹配任意 ``request`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 request type 的 Matcher。 +""" METAEVENT = Permission(_metaevent) +""" +- **说明**: 匹配任意 ``meta_event`` 类型事件,仅在需要同时捕获不同类型事件时使用。优先使用 meta_event type 的 Matcher。 +""" def USER(*user: int, perm: Permission = Permission()): + """ + :说明: + 在白名单内且满足 perm + :参数: + * ``*user: int``: 白名单 + * ``perm: Permission``: 需要同时满足的权限 + """ async def _user(bot: Bot, event: Event) -> bool: return event.type == "message" and event.user_id in user and await perm( @@ -86,9 +135,21 @@ async def _private_other(bot: Bot, event: Event) -> bool: PRIVATE = Permission(_private) +""" +- **说明**: 匹配任意私聊消息类型事件 +""" PRIVATE_FRIEND = Permission(_private_friend) +""" +- **说明**: 匹配任意好友私聊消息类型事件 +""" PRIVATE_GROUP = Permission(_private_group) +""" +- **说明**: 匹配任意群临时私聊消息类型事件 +""" PRIVATE_OTHER = Permission(_private_other) +""" +- **说明**: 匹配任意其他私聊消息类型事件 +""" async def _group(bot: Bot, event: Event) -> bool: @@ -111,9 +172,25 @@ async def _group_owner(bot: Bot, event: Event) -> bool: GROUP = Permission(_group) +""" +- **说明**: 匹配任意群聊消息类型事件 +""" GROUP_MEMBER = Permission(_group_member) +""" +- **说明**: 匹配任意群员群聊消息类型事件 + +\:\:\:warning 警告 +该权限通过 event.sender 进行判断且不包含管理员以及群主! +\:\:\: +""" GROUP_ADMIN = Permission(_group_admin) +""" +- **说明**: 匹配任意群管理员群聊消息类型事件 +""" GROUP_OWNER = Permission(_group_owner) +""" +- **说明**: 匹配任意群主群聊消息类型事件 +""" async def _superuser(bot: Bot, event: Event) -> bool: @@ -121,4 +198,10 @@ async def _superuser(bot: Bot, event: Event) -> bool: SUPERUSER = Permission(_superuser) +""" +- **说明**: 匹配任意超级用户消息类型事件 +""" EVERYBODY = MESSAGE +""" +- **说明**: 匹配任意消息类型事件 +""" diff --git a/nonebot/rule.py b/nonebot/rule.py index 15dc1e6a..0b1b5cc0 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -1,5 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +""" +规则 +==== + +每个 ``Matcher`` 拥有一个 ``Rule`` ,其中是 **异步** ``RuleChecker`` 的集合,只有当所有 ``RuleChecker`` 检查结果为 ``True`` 时继续运行。 + +\:\:\:tip 提示 +``RuleChecker`` 既可以是 async function 也可以是 sync function +\:\:\: +""" import re import asyncio @@ -10,28 +20,62 @@ from pygtrie import CharTrie from nonebot import get_driver from nonebot.log import logger from nonebot.utils import run_sync -from nonebot.typing import Bot, Any, Dict, Event, Union, Tuple, NoReturn, RuleChecker +from nonebot.typing import Bot, Any, Dict, Event, Union, Tuple, NoReturn, Callable, Awaitable, RuleChecker class Rule: + """ + :说明: + ``Matcher`` 规则类,当事件传递时,在 ``Matcher`` 运行前进行检查。 + :示例: + + .. code-block:: python + + Rule(async_function) & sync_function + # 等价于 + from nonebot.utils import run_sync + Rule(async_function, run_sync(sync_function)) + """ __slots__ = ("checkers",) - def __init__(self, *checkers: RuleChecker) -> None: - self.checkers = list(checkers) + def __init__( + self, *checkers: Callable[[Bot, Event, dict], + Awaitable[bool]]) -> None: + """ + :参数: + * ``*checkers: Callable[[Bot, Event, dict], Awaitable[bool]]``: **异步** RuleChecker + """ + self.checkers = set(checkers) + """ + :说明: + 存储 ``RuleChecker`` + :类型: + * ``Set[Callable[[Bot, Event, dict], Awaitable[bool]]]`` + """ async def __call__(self, bot: Bot, event: Event, state: dict) -> bool: + """ + :说明: + 检查是否符合所有规则 + :参数: + * ``bot: Bot``: Bot 对象 + * ``event: Event``: Event 对象 + * ``state: dict``: 当前 State + :返回: + - ``bool`` + """ results = await asyncio.gather( *map(lambda c: c(bot, event, state), self.checkers)) return all(results) def __and__(self, other: Union["Rule", RuleChecker]) -> "Rule": - checkers = [*self.checkers] + checkers = self.checkers.copy() if isinstance(other, Rule): - checkers.extend(other.checkers) + checkers |= other.checkers elif asyncio.iscoroutinefunction(other): - checkers.append(other) + checkers.add(other) # type: ignore else: - checkers.append(run_sync(other)) + checkers.add(run_sync(other)) return Rule(*checkers) def __or__(self, other) -> NoReturn: @@ -111,19 +155,29 @@ class TrieRule: def startswith(msg: str) -> Rule: - TrieRule.add_prefix(msg, (msg,)) + """ + :说明: + 匹配消息开头 + :参数: + * ``msg: str``: 消息开头字符串 + """ async def _startswith(bot: Bot, event: Event, state: dict) -> bool: - return msg in state["_prefix"] + return event.plain_text.startswith(msg) return Rule(_startswith) def endswith(msg: str) -> Rule: - TrieRule.add_suffix(msg, (msg,)) + """ + :说明: + 匹配消息结尾 + :参数: + * ``msg: str``: 消息结尾字符串 + """ async def _endswith(bot: Bot, event: Event, state: dict) -> bool: - return msg in state["_suffix"] + return event.plain_text.endswith(msg) return Rule(_endswith) diff --git a/nonebot/sched.py b/nonebot/sched.py new file mode 100644 index 00000000..58447b67 --- /dev/null +++ b/nonebot/sched.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +计划任务 +======== + +计划任务使用第三方库 `APScheduler`_ ,使用文档请参考 `APScheduler使用文档`_ 。 + +.. _APScheduler: + https://github.com/agronholm/apscheduler +.. _APScheduler使用文档: + https://apscheduler.readthedocs.io/en/latest/ +""" + +try: + from apscheduler.schedulers.asyncio import AsyncIOScheduler +except ImportError: + AsyncIOScheduler = None + +if AsyncIOScheduler: + scheduler = AsyncIOScheduler() + """ + :类型: + ``Optional[apscheduler.schedulers.asyncio.AsyncIOScheduler]`` + :说明: + 当可选依赖 ``APScheduler`` 未安装时,``scheduler`` 为 None + + 使用 ``pip install nonebot[scheduler]`` 安装可选依赖 + + :常用示例: + + .. code-block:: python + + from nonebot import scheduler + + @scheduler.scheduled_job("cron", hour="*/2", id="xxx", args=[1], kwargs={arg2: 2}) + async def run_every_2_hour(arg1, arg2): + pass + + scheduler.add_job(run_every_day_from_program_start, "interval", days=1, id="xxx") + + """ +else: + scheduler = None diff --git a/nonebot/typing.py b/nonebot/typing.py index 73dc62a6..51834a08 100644 --- a/nonebot/typing.py +++ b/nonebot/typing.py @@ -46,7 +46,7 @@ def overrides(InterfaceClass: object): Driver = TypeVar("Driver", bound="BaseDriver") """ -:类型: `BaseDriver` +:类型: ``BaseDriver`` :说明: @@ -54,7 +54,7 @@ Driver = TypeVar("Driver", bound="BaseDriver") """ WebSocket = TypeVar("WebSocket", bound="BaseWebSocket") """ -:类型: `BaseWebSocket` +:类型: ``BaseWebSocket`` :说明: @@ -63,7 +63,7 @@ WebSocket = TypeVar("WebSocket", bound="BaseWebSocket") Bot = TypeVar("Bot", bound="BaseBot") """ -:类型: `BaseBot` +:类型: ``BaseBot`` :说明: @@ -71,7 +71,7 @@ Bot = TypeVar("Bot", bound="BaseBot") """ Event = TypeVar("Event", bound="BaseEvent") """ -:类型: `BaseEvent` +:类型: ``BaseEvent`` :说明: @@ -79,7 +79,7 @@ Event = TypeVar("Event", bound="BaseEvent") """ Message = TypeVar("Message", bound="BaseMessage") """ -:类型: `BaseMessage` +:类型: ``BaseMessage`` :说明: @@ -87,7 +87,7 @@ Message = TypeVar("Message", bound="BaseMessage") """ MessageSegment = TypeVar("MessageSegment", bound="BaseMessageSegment") """ -:类型: `BaseMessageSegment` +:类型: ``BaseMessageSegment`` :说明: @@ -97,7 +97,7 @@ MessageSegment = TypeVar("MessageSegment", bound="BaseMessageSegment") PreProcessor = Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]] """ -:类型: `Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]` +:类型: ``Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]`` :说明: @@ -106,7 +106,7 @@ PreProcessor = Callable[[Bot, Event, dict], Union[Awaitable[None], Matcher = TypeVar("Matcher", bound="MatcherClass") """ -:类型: `Matcher` +:类型: ``Matcher`` :说明: @@ -114,15 +114,15 @@ Matcher = TypeVar("Matcher", bound="MatcherClass") """ Rule = TypeVar("Rule", bound="RuleClass") """ -:类型: `Rule` +:类型: ``Rule`` :说明: Rule 即判断是否响应事件的处理类。内部存储 RuleChecker ,返回全为 True 则响应事件。 """ -RuleChecker = Callable[[Bot, Event, dict], Awaitable[bool]] +RuleChecker = Callable[[Bot, Event, dict], Union[bool, Awaitable[bool]]] """ -:类型: `Callable[[Bot, Event, dict], Awaitable[bool]]` +:类型: ``Callable[[Bot, Event, dict], Union[bool, Awaitable[bool]]]`` :说明: @@ -130,15 +130,15 @@ RuleChecker = Callable[[Bot, Event, dict], Awaitable[bool]] """ Permission = TypeVar("Permission", bound="PermissionClass") """ -:类型: `Permission` +:类型: ``Permission`` :说明: Permission 即判断是否响应消息的处理类。内部存储 PermissionChecker ,返回只要有一个 True 则响应消息。 """ -PermissionChecker = Callable[[Bot, Event], Awaitable[bool]] +PermissionChecker = Callable[[Bot, Event], Union[bool, Awaitable[bool]]] """ -:类型: `Callable[[Bot, Event], Awaitable[bool]]` +:类型: ``Callable[[Bot, Event], Union[bool, Awaitable[bool]]]`` :说明: @@ -147,7 +147,7 @@ PermissionChecker = Callable[[Bot, Event], Awaitable[bool]] Handler = Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]] """ -:类型: `Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]` +:类型: ``Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]`` :说明: @@ -156,7 +156,7 @@ Handler = Callable[[Bot, Event, dict], Union[Awaitable[None], ArgsParser = Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]] """ -:类型: `Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]` +:类型: ``Callable[[Bot, Event, dict], Union[Awaitable[None], Awaitable[NoReturn]]]`` :说明: diff --git a/nonebot/utils.py b/nonebot/utils.py index 631395f5..06bdaaa4 100644 --- a/nonebot/utils.py +++ b/nonebot/utils.py @@ -10,6 +10,14 @@ from nonebot.typing import Any, Callable, Awaitable, overrides def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: + """ + :说明: + 一个用于包装 sync function 为 async function 的装饰器 + :参数: + * ``func: Callable[..., Any]``: 被装饰的同步函数 + :返回: + - ``Callable[..., Awaitable[Any]]`` + """ @wraps(func) async def _wrapper(*args: Any, **kwargs: Any) -> Any: @@ -22,6 +30,10 @@ def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: class DataclassEncoder(json.JSONEncoder): + """ + :说明: + 在JSON序列化 ``Message`` (List[Dataclass]) 时使用的 ``JSONEncoder`` + """ @overrides(json.JSONEncoder) def default(self, o): diff --git a/package-lock.json b/package-lock.json index f6a0c4db..71d7b190 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,28 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@ant-design-vue/babel-helper-vue-transform-on": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@ant-design-vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.0.1.tgz", - "integrity": "sha512-dOAPf/tCM2lCG8FhvOMFBaOdMElMEGhOoocMXEWvHW2l1KIex+UibDcq4bdBEJpDMLrnbNOqci9E7P2dARP6lg==", - "dev": true - }, - "@ant-design-vue/babel-plugin-jsx": { - "version": "1.0.0-rc.1", - "resolved": "https://registry.npmjs.org/@ant-design-vue/babel-plugin-jsx/-/babel-plugin-jsx-1.0.0-rc.1.tgz", - "integrity": "sha512-x7PfAHSs5/emIuey1Df7Bh/vJU27S9KBdufzoAA7kgwTpEpY85R7CXD9gl6sJFB7aG2pZpl4Tmm+FsHlzgp7fA==", - "dev": true, - "requires": { - "@ant-design-vue/babel-helper-vue-transform-on": "^1.0.0", - "@babel/helper-module-imports": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0", - "@babel/traverse": "^7.0.0", - "@babel/types": "^7.0.0", - "camelcase": "^6.0.0", - "html-tags": "^3.1.0", - "svg-tags": "^1.0.0" - } - }, "@babel/code-frame": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", @@ -47,19 +25,19 @@ } }, "@babel/core": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.1.tgz", - "integrity": "sha512-XqF7F6FWQdKGGWAzGELL+aCO1p+lRY5Tj5/tbT3St1G8NaH70jhhDIKknIZaDans0OQBG5wRAldROLHSt44BgQ==", + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.0", + "@babel/generator": "^7.11.6", "@babel/helper-module-transforms": "^7.11.0", "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.11.1", + "@babel/parser": "^7.11.5", "@babel/template": "^7.10.4", - "@babel/traverse": "^7.11.0", - "@babel/types": "^7.11.0", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -71,12 +49,12 @@ } }, "@babel/generator": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz", - "integrity": "sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ==", + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", "dev": true, "requires": { - "@babel/types": "^7.11.0", + "@babel/types": "^7.11.5", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -150,12 +128,11 @@ } }, "@babel/helper-explode-assignable-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.10.4.tgz", - "integrity": "sha512-4K71RyRQNPRrR85sr5QY4X3VwG4wtVoXZB9+L3r1Gp38DhELyHCtovqydRi7c1Ovb17eRGiQ/FD5s8JdU0Uy5A==", + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz", + "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==", "dev": true, "requires": { - "@babel/traverse": "^7.10.4", "@babel/types": "^7.10.4" } }, @@ -246,15 +223,14 @@ } }, "@babel/helper-remap-async-to-generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.10.4.tgz", - "integrity": "sha512-86Lsr6NNw3qTNl+TBcF1oRZMaVzJtbWTyTko+CQL/tvNvcGYEFKbLXDPxtW0HKk3McNOk4KzY55itGWCAGK5tg==", + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz", + "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.10.4", "@babel/helper-wrap-function": "^7.10.4", "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", "@babel/types": "^7.10.4" } }, @@ -339,9 +315,9 @@ } }, "@babel/parser": { - "version": "7.11.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.3.tgz", - "integrity": "sha512-REo8xv7+sDxkKvoxEywIdsNFiZLybwdI7hcT5uEPyQrSMB4YQ973BfC9OOrD/81MaIjh6UxdulIQXkjmiH3PcA==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { @@ -863,9 +839,9 @@ } }, "@babel/plugin-transform-runtime": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.11.0.tgz", - "integrity": "sha512-LFEsP+t3wkYBlis8w6/kmnd6Kb1dxTd+wGJ8MlxTGzQo//ehtqlVL4S9DNUa53+dtPSQobN2CXx4d81FqC58cw==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.11.5.tgz", + "integrity": "sha512-9aIoee+EhjySZ6vY5hnLjigHzunBlscx9ANKutkeWTJTx6m5Rbq6Ic01tLvO54lSusR+BxV7u4UDdCmXv5aagg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.10.4", @@ -942,9 +918,9 @@ } }, "@babel/preset-env": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.0.tgz", - "integrity": "sha512-2u1/k7rG/gTh02dylX2kL3S0IJNF+J6bfDSp4DI2Ma8QN6Y9x9pmAax59fsCk6QUQG0yqH47yJWA+u1I1LccAg==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", + "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", "dev": true, "requires": { "@babel/compat-data": "^7.11.0", @@ -1009,7 +985,7 @@ "@babel/plugin-transform-unicode-escapes": "^7.10.4", "@babel/plugin-transform-unicode-regex": "^7.10.4", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.11.0", + "@babel/types": "^7.11.5", "browserslist": "^4.12.0", "core-js-compat": "^3.6.2", "invariant": "^2.2.2", @@ -1018,9 +994,9 @@ } }, "@babel/preset-modules": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.3.tgz", - "integrity": "sha512-Ra3JXOHBq2xd56xSF7lMKXdjBn3T772Y1Wet3yWnkDly9zHvJki029tAFzvAAK5cf4YV3yoxuP61crYRol6SVg==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -1051,26 +1027,26 @@ } }, "@babel/traverse": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz", - "integrity": "sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.0", + "@babel/generator": "^7.11.5", "@babel/helper-function-name": "^7.10.4", "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.0", - "@babel/types": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", - "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.10.4", @@ -1126,9 +1102,9 @@ } }, "@types/json-schema": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", - "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, "@types/minimatch": { @@ -1155,6 +1131,28 @@ "integrity": "sha512-6tyf5Cqm4m6v7buITuwS+jHzPlIPxbFzEhXR5JGZpbrvOcp1hiQKckd305/3C7C36wFekNTQSxAtgeM0j0yoUw==", "dev": true }, + "@vue/babel-helper-vue-transform-on": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.0.0-rc.2.tgz", + "integrity": "sha512-1+7CwjQ0Kasml6rHoNQUmbISwqLNNfFVBUcZl6QBremUl296ZmLrVQPqJP5pyAAWjZke5bpI1hlj+LVVuT7Jcg==", + "dev": true + }, + "@vue/babel-plugin-jsx": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.0.0-rc.3.tgz", + "integrity": "sha512-/Ibq0hoKsidnHWPhgRpjcjYhYcHpqEm2fiKVAPO88OXZNHGwaGgS4yXkC6TDEvlZep4mBDo+2S5T81wpbVh90Q==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/plugin-syntax-jsx": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "@vue/babel-helper-vue-transform-on": "^1.0.0-rc.2", + "camelcase": "^6.0.0", + "html-tags": "^3.1.0", + "svg-tags": "^1.0.0" + } + }, "@vue/babel-plugin-transform-vue-jsx": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.1.2.tgz", @@ -1178,12 +1176,11 @@ } }, "@vue/babel-preset-app": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-4.5.4.tgz", - "integrity": "sha512-a+2s/lL3fE3h9/ekvpMVLhZTDjR3xt+jnpTwuQtEZ3KIuzFHxbmwAjueRZh6BKEGfB6kgZ3KqZHFX3vx/DRJ4w==", + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-4.5.6.tgz", + "integrity": "sha512-Eps83UNiBJeqlbpR9afYnhvjVLElVtA4fDLNuVUr1r3RbepoxWuq+mUTr3TBArPQebnAaDcrZaNHBWTLRbfo3A==", "dev": true, "requires": { - "@ant-design-vue/babel-plugin-jsx": "^1.0.0-0", "@babel/core": "^7.11.0", "@babel/helper-compilation-targets": "^7.9.6", "@babel/helper-module-imports": "^7.8.3", @@ -1194,6 +1191,7 @@ "@babel/plugin-transform-runtime": "^7.11.0", "@babel/preset-env": "^7.11.0", "@babel/runtime": "^7.11.0", + "@vue/babel-plugin-jsx": "^1.0.0-0", "@vue/babel-preset-jsx": "^1.1.2", "babel-plugin-dynamic-import-node": "^2.3.3", "core-js": "^3.6.5", @@ -1330,18 +1328,18 @@ } }, "@vuepress/core": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vuepress/core/-/core-1.5.3.tgz", - "integrity": "sha512-ZZpDkYVtztN2eWZ5+oj5DoGMEQdV9Bz4et0doKhLXfIEQFwjWUyN6HHnIgqjnmSFIqfjzbWdOKVxMLENs8njpA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/core/-/core-1.5.4.tgz", + "integrity": "sha512-RaHJiX0Yno4S3zoV64JNd3xE55sza8rayyWvXAJY381XVMxKrsLBrgW6ntNYSkzGnZcxi6fwMV/CVOUhEtkEkA==", "dev": true, "requires": { "@babel/core": "^7.8.4", "@vue/babel-preset-app": "^4.1.2", - "@vuepress/markdown": "1.5.3", - "@vuepress/markdown-loader": "1.5.3", - "@vuepress/plugin-last-updated": "1.5.3", - "@vuepress/plugin-register-components": "1.5.3", - "@vuepress/shared-utils": "1.5.3", + "@vuepress/markdown": "1.5.4", + "@vuepress/markdown-loader": "1.5.4", + "@vuepress/plugin-last-updated": "1.5.4", + "@vuepress/plugin-register-components": "1.5.4", + "@vuepress/shared-utils": "1.5.4", "autoprefixer": "^9.5.1", "babel-loader": "^8.0.4", "cache-loader": "^3.0.0", @@ -1373,65 +1371,115 @@ "webpack-dev-server": "^3.5.1", "webpack-merge": "^4.1.2", "webpackbar": "3.2.0" + }, + "dependencies": { + "@vuepress/shared-utils": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/shared-utils/-/shared-utils-1.5.4.tgz", + "integrity": "sha512-HCeMPEAPjFN1Ongii0BUCI1iB4gBBiQ4PUgh7F4IGG8yBg4tMqWO4NHqCuDCuGEvK7lgHy8veto0SsSvdSKp3g==", + "dev": true, + "requires": { + "chalk": "^2.3.2", + "escape-html": "^1.0.3", + "fs-extra": "^7.0.1", + "globby": "^9.2.0", + "gray-matter": "^4.0.1", + "hash-sum": "^1.0.2", + "semver": "^6.0.0", + "toml": "^3.0.0", + "upath": "^1.1.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "@vuepress/markdown": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vuepress/markdown/-/markdown-1.5.3.tgz", - "integrity": "sha512-TI6pSkmvu8SZhIfZR0VbDmmGAWOaoI+zIaXMDY27ex7Ty/KQ/JIsVSgr5wbiSJMhkA0efbZzAVFu1NrHIc1waw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/markdown/-/markdown-1.5.4.tgz", + "integrity": "sha512-bgrR9LTcAa2O0WipTbH3OFKeAfXc/2oU6cUIoMkyihSKUo1Mr5yt1XKM7vHe1uFEZygNr8EAemep8chsuVuISA==", "dev": true, "requires": { - "@vuepress/shared-utils": "1.5.3", + "@vuepress/shared-utils": "1.5.4", "markdown-it": "^8.4.1", "markdown-it-anchor": "^5.0.2", "markdown-it-chain": "^1.3.0", "markdown-it-emoji": "^1.4.0", "markdown-it-table-of-contents": "^0.4.0", "prismjs": "^1.13.0" + }, + "dependencies": { + "@vuepress/shared-utils": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/shared-utils/-/shared-utils-1.5.4.tgz", + "integrity": "sha512-HCeMPEAPjFN1Ongii0BUCI1iB4gBBiQ4PUgh7F4IGG8yBg4tMqWO4NHqCuDCuGEvK7lgHy8veto0SsSvdSKp3g==", + "dev": true, + "requires": { + "chalk": "^2.3.2", + "escape-html": "^1.0.3", + "fs-extra": "^7.0.1", + "globby": "^9.2.0", + "gray-matter": "^4.0.1", + "hash-sum": "^1.0.2", + "semver": "^6.0.0", + "toml": "^3.0.0", + "upath": "^1.1.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "@vuepress/markdown-loader": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vuepress/markdown-loader/-/markdown-loader-1.5.3.tgz", - "integrity": "sha512-Y1FLkEZw1p84gPer14CjA1gPSdmc/bfPuZ/7mE0dqBtpsU3o9suaubWpFs75agjHew4IJap5TibtUs57FWGSfA==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/markdown-loader/-/markdown-loader-1.5.4.tgz", + "integrity": "sha512-3R5quGIXQm7gfPWN67SVZ9OBA7VrGEEXJjjV01MYkbfhqVGgO6lBRq73Og0XdKs4RPx4nqJUPthhL8FJVNRTIg==", "dev": true, "requires": { - "@vuepress/markdown": "1.5.3", + "@vuepress/markdown": "1.5.4", "loader-utils": "^1.1.0", "lru-cache": "^5.1.1" } }, "@vuepress/plugin-active-header-links": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-active-header-links/-/plugin-active-header-links-1.5.3.tgz", - "integrity": "sha512-x9U3bVkwwUkfXtf7db1Gg/m32UGpSWRurdl9I5ePFFxwEy8ffGmvhpzCBL878q8TNa90jd1XueQJCq6hQ9/KsQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-active-header-links/-/plugin-active-header-links-1.5.4.tgz", + "integrity": "sha512-FI1Dr/44HVqxLMRSuaVEEwegGVEGFlaWYE3nsXwL7klKr6c+2kXHEw9rSQlAxzJyzVfovTk4dd+s/AMOKuLGZQ==", "dev": true, "requires": { "lodash.debounce": "^4.0.8" } }, "@vuepress/plugin-back-to-top": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-back-to-top/-/plugin-back-to-top-1.5.3.tgz", - "integrity": "sha512-ZrEhdYUIu8ywvTIDBztzPF7YO09bWrNX+HSI9BMvF+8XhDt4MG5XRuoVDq8WTN2YZMdFTVSRzWXxlGm9aZ+eGQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-back-to-top/-/plugin-back-to-top-1.5.4.tgz", + "integrity": "sha512-FqT3F8VztSiDXtI7xjD4SOeEuyaPYx2zxxU3cH3wfe/amlFWyFPjTWJwAf1GRT5CqS13J2qfEnfWMU/Ct7Xg/Q==", "dev": true, "requires": { "lodash.debounce": "^4.0.8" } }, "@vuepress/plugin-last-updated": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-last-updated/-/plugin-last-updated-1.5.3.tgz", - "integrity": "sha512-xb4FXSRTTPrERX2DigGDAJrVFLsTQwsY4QSzRBFYSlfZkK3gcZMNmUISXS/4tDkyPgxh/TtcMwbcUiUu0LQOnQ==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-last-updated/-/plugin-last-updated-1.5.4.tgz", + "integrity": "sha512-9kezBCxPM+cevKRNML6Q7v6qkI8NQvKbVkwohlzsElM8FBmjlZmgFyZje66ksTnb/U6ogazCCq9jdOyipNcQ2A==", "dev": true, "requires": { "cross-spawn": "^6.0.5" } }, "@vuepress/plugin-medium-zoom": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-medium-zoom/-/plugin-medium-zoom-1.5.3.tgz", - "integrity": "sha512-iyr6i8FNeZIlwEV7c43cISXCShot9c6BcLj7D2U1P69gawPfxLfRDPOP/P0uCIqm6vrgQmOR9SEWBkkWpCOE5Q==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-medium-zoom/-/plugin-medium-zoom-1.5.4.tgz", + "integrity": "sha512-4lK6HmdjyBbWAyYKKGYgIjW7cM8xu/OMTsPwJZQPj/fY1QVVZzy/4FhxSvQDm47JSru6NMQaCQv7DJIdBNA7VQ==", "dev": true, "requires": { "medium-zoom": "^1.0.4" @@ -1447,12 +1495,37 @@ } }, "@vuepress/plugin-register-components": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vuepress/plugin-register-components/-/plugin-register-components-1.5.3.tgz", - "integrity": "sha512-OzL7QOKhR+biUWrDqPisQz35cXVdI274cDWw2tTUTw3yr7aPyezDw3DFRFXzPaZzk9Jo+d+kkOTwghXXC88Xbg==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-register-components/-/plugin-register-components-1.5.4.tgz", + "integrity": "sha512-Y1U9j6unZp1ZhnHjQ9yOPY+vxldUA3C1EwT6UgI75j5gxa5Hz6NakoIo6mbhaYHlGmx33o/MXrxufLPapo/YlQ==", "dev": true, "requires": { - "@vuepress/shared-utils": "1.5.3" + "@vuepress/shared-utils": "1.5.4" + }, + "dependencies": { + "@vuepress/shared-utils": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/shared-utils/-/shared-utils-1.5.4.tgz", + "integrity": "sha512-HCeMPEAPjFN1Ongii0BUCI1iB4gBBiQ4PUgh7F4IGG8yBg4tMqWO4NHqCuDCuGEvK7lgHy8veto0SsSvdSKp3g==", + "dev": true, + "requires": { + "chalk": "^2.3.2", + "escape-html": "^1.0.3", + "fs-extra": "^7.0.1", + "globby": "^9.2.0", + "gray-matter": "^4.0.1", + "hash-sum": "^1.0.2", + "semver": "^6.0.0", + "toml": "^3.0.0", + "upath": "^1.1.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "@vuepress/plugin-search": { @@ -1488,20 +1561,37 @@ } }, "@vuepress/theme-default": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@vuepress/theme-default/-/theme-default-1.5.3.tgz", - "integrity": "sha512-LRldV8U4FRV26bKXtJFT1oe5lhYbfCxPRFnRXPgf/cLZC+mQd1abl9njCAP7fjmmS33ZgF1dFARGbpCsYWY1Gg==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/theme-default/-/theme-default-1.5.4.tgz", + "integrity": "sha512-kHst1yXzqTiocVU7w9x4cfJ08vR9ZbREC6kTRtH1ytQSEUL5tM0b9HFicfg1kDp7YNq2qntRro+WmfjU9Ps/eg==", "dev": true, "requires": { - "@vuepress/plugin-active-header-links": "1.5.3", - "@vuepress/plugin-nprogress": "1.5.3", - "@vuepress/plugin-search": "1.5.3", + "@vuepress/plugin-active-header-links": "1.5.4", + "@vuepress/plugin-nprogress": "1.5.4", + "@vuepress/plugin-search": "1.5.4", "docsearch.js": "^2.5.2", "lodash": "^4.17.15", "stylus": "^0.54.5", "stylus-loader": "^3.0.2", "vuepress-plugin-container": "^2.0.2", "vuepress-plugin-smooth-scroll": "^0.0.3" + }, + "dependencies": { + "@vuepress/plugin-nprogress": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-nprogress/-/plugin-nprogress-1.5.4.tgz", + "integrity": "sha512-2bGKoO/o2e5mIfOU80q+AkxOK5wVijA/+8jGjSQVf2ccMpJw+Ly1mMi69r81Q0QkEihgfI9VN42a5+a6LUgPBw==", + "dev": true, + "requires": { + "nprogress": "^0.2.0" + } + }, + "@vuepress/plugin-search": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@vuepress/plugin-search/-/plugin-search-1.5.4.tgz", + "integrity": "sha512-wikU9XYiZ3Olbii0lI+56mcSdpzHHkduVBMB4MNEV5iob23qDxGPmvfZirjsZV20w1UnLRptERyHtZkTLW9Mbg==", + "dev": true + } } }, "@webassemblyjs/ast": { @@ -1801,6 +1891,11 @@ "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", "dev": true }, + "animate.css": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz", + "integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==" + }, "ansi-align": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", @@ -2365,9 +2460,9 @@ } }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -2527,15 +2622,15 @@ } }, "browserslist": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.0.tgz", - "integrity": "sha512-pUsXKAF2lVwhmtpeA3LJrZ76jXuusrNyhduuQs7CDFf9foT4Y38aQOserd2lMe5DSSrjf3fx34oHwryuvxAUgQ==", + "version": "4.14.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.2.tgz", + "integrity": "sha512-HI4lPveGKUR0x2StIz+2FXfDk9SfVMrxn6PLh1JeGUwcuoDkdKZebWiyLRJ68iIPDpMI4JLVDf7S7XzslgWOhw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001111", - "electron-to-chromium": "^1.3.523", + "caniuse-lite": "^1.0.30001125", + "electron-to-chromium": "^1.3.564", "escalade": "^3.0.2", - "node-releases": "^1.1.60" + "node-releases": "^1.1.61" } }, "buffer": { @@ -2755,9 +2850,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001115", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001115.tgz", - "integrity": "sha512-NZrG0439ePYna44lJX8evHX2L7Z3/z3qjVLnHgbBb/duNEnGo348u+BQS5o4HTWcrb++100dHFrU36IesIrC1Q==", + "version": "1.0.30001131", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001131.tgz", + "integrity": "sha512-4QYi6Mal4MMfQMSqGIRPGbKIbZygeN83QsWq1ixpUwvtfgAZot5BrCKzGygvZaV+CnELdTwD0S4cqUNozq7/Cw==", "dev": true }, "caseless": { @@ -2869,9 +2964,9 @@ } }, "cli-boxes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz", - "integrity": "sha512-gpaBrMAizVEANOpfZp/EEUixTXDyGt7DFzdK5hU+UbWt/J0lB0w20ncZj59Z9a93xHb9u12zF5BS6i9RKbtg4w==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", "dev": true }, "clipboard": { @@ -3192,9 +3287,9 @@ "dev": true }, "copy-webpack-plugin": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", - "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.2.tgz", + "integrity": "sha512-Uh7crJAco3AjBvgAy9Z75CjK8IG+gxaErro71THQ+vv/bl4HaQcpkexAY8KVW/T6D2W2IRr+couF/knIRkZMIQ==", "dev": true, "requires": { "cacache": "^12.0.3", @@ -3207,7 +3302,7 @@ "normalize-path": "^3.0.0", "p-limit": "^2.2.1", "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", + "serialize-javascript": "^4.0.0", "webpack-log": "^2.0.0" }, "dependencies": { @@ -3944,9 +4039,9 @@ }, "dependencies": { "domelementtype": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", - "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", + "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==", "dev": true }, "entities": { @@ -3995,9 +4090,9 @@ } }, "dot-prop": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", - "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, "requires": { "is-obj": "^2.0.0" @@ -4038,9 +4133,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.534", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.534.tgz", - "integrity": "sha512-7x2S3yUrspNHQOoPk+Eo+iHViSiJiEGPI6BpmLy1eT2KRNGCkBt/NUYqjfXLd1DpDCQp7n3+LfA1RkbG+LqTZQ==", + "version": "1.3.569", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.569.tgz", + "integrity": "sha512-HViXDebvp9yx3GHjNmMEzfl7RhE1N+r+4iHmRAswpwWTtf/UaYi4QGSfjOhYn5MACiONjh9+XwZzHA6NccAEtQ==", "dev": true }, "elliptic": { @@ -4133,9 +4228,9 @@ } }, "envinfo": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.2.tgz", - "integrity": "sha512-k3Eh5bKuQnZjm49/L7H4cHzs2FlL5QjbTB3JrPxoTI8aJG7hVMe4uKyJxSYH4ahseby2waUwk5OaKX/nAsaYgg==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz", + "integrity": "sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==", "dev": true }, "errno": { @@ -4157,9 +4252,9 @@ } }, "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "version": "1.18.0-next.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.0.tgz", + "integrity": "sha512-elZXTZXKn51hUBdJjSZGYRujuzilgXo8vSPQzjGYXLvSlGiCo8VO8ZGV3kjo9a0WNJJ57hENagwbtlRuHuzkcQ==", "dev": true, "requires": { "es-to-primitive": "^1.2.1", @@ -4167,8 +4262,9 @@ "has": "^1.0.3", "has-symbols": "^1.0.1", "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", "object-keys": "^1.1.1", "object.assign": "^4.1.0", "string.prototype.trimend": "^1.0.1", @@ -4193,9 +4289,9 @@ "dev": true }, "escalade": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.0.2.tgz", - "integrity": "sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", + "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==", "dev": true }, "escape-goat": { @@ -4233,12 +4329,20 @@ "dev": true }, "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "requires": { - "estraverse": "^4.1.0" + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } } }, "estraverse": { @@ -4260,9 +4364,9 @@ "dev": true }, "eventemitter3": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz", - "integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true }, "events": { @@ -5513,9 +5617,9 @@ "dev": true }, "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.1.tgz", + "integrity": "sha512-wliAfSzx6V+6WfMOmus1xy0XvSgf/dlStkvTfq7F0g4bOIW0PSUbnyse3NhDwdyYS1ozfUtAAySqTws3z9Eqgg==", "dev": true }, "is-ci": { @@ -5645,6 +5749,12 @@ } } }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=", + "dev": true + }, "is-npm": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", @@ -6073,9 +6183,9 @@ "dev": true }, "loglevel": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", - "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.0.tgz", + "integrity": "sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==", "dev": true }, "loose-envify": { @@ -6536,9 +6646,9 @@ } }, "node-forge": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", - "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", "dev": true }, "node-libs-browser": { @@ -6581,9 +6691,9 @@ } }, "node-releases": { - "version": "1.1.60", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.60.tgz", - "integrity": "sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA==", + "version": "1.1.61", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", + "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==", "dev": true }, "nopt": { @@ -6705,6 +6815,27 @@ "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "object-keys": { @@ -6723,15 +6854,15 @@ } }, "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" } }, "object.getownpropertydescriptors": { @@ -6742,6 +6873,27 @@ "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "object.pick": { @@ -6763,6 +6915,27 @@ "es-abstract": "^1.17.0-next.1", "function-bind": "^1.1.1", "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "obuf": { @@ -6811,9 +6984,9 @@ } }, "optimize-css-assets-webpack-plugin": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.3.tgz", - "integrity": "sha512-q9fbvCRS6EYtUKKSwI87qm2IxlyJK5b4dygW1rKUBT6mMDhdG5e5bZT63v6tnJR9F9FB/H5a0HTmtw+laUBxKA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz", + "integrity": "sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A==", "dev": true, "requires": { "cssnano": "^4.1.10", @@ -7145,9 +7318,9 @@ } }, "postcss-calc": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.3.tgz", - "integrity": "sha512-IB/EAEmZhIMEIhG7Ov4x+l47UaXOS1n2f4FBUk/aKllQhtSCxWhTzn0nJgkqN7fo/jcWySvWTSB6Syk9L+31bA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.4.tgz", + "integrity": "sha512-0I79VRAd1UTkaHzY9w83P39YGO/M3bG7/tNLrHGEunBolfoGM0hSjrGvjoeaj0JE/zIw5GsI2KZ0UwDJqv5hjw==", "dev": true, "requires": { "postcss": "^7.0.27", @@ -8057,6 +8230,27 @@ "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "regexpu-core": { @@ -8354,14 +8548,14 @@ "dev": true }, "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } }, "section-matter": { @@ -8399,12 +8593,12 @@ "dev": true }, "selfsigned": { - "version": "1.10.7", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", - "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", "dev": true, "requires": { - "node-forge": "0.9.0" + "node-forge": "^0.10.0" } }, "semver": { @@ -8483,10 +8677,13 @@ } }, "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } }, "serve-index": { "version": "1.9.1", @@ -9092,6 +9289,27 @@ "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "string.prototype.trimstart": { @@ -9102,6 +9320,27 @@ "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "string_decoder": { @@ -9327,15 +9566,6 @@ "ajv-keywords": "^3.1.0" } }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9782,9 +10012,9 @@ "dev": true }, "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -9913,6 +10143,27 @@ "es-abstract": "^1.17.2", "has-symbols": "^1.0.1", "object.getownpropertydescriptors": "^2.1.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + } } }, "utila": { @@ -9963,9 +10214,9 @@ "dev": true }, "vue": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", - "integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz", + "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg==", "dev": true }, "vue-hot-reload-api": { @@ -9994,9 +10245,9 @@ "dev": true }, "vue-server-renderer": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.11.tgz", - "integrity": "sha512-V3faFJHr2KYfdSIalL+JjinZSHYUhlrvJ9pzCIjjwSh77+pkrsXpK4PucdPcng57+N77pd1LrKqwbqjQdktU1A==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/vue-server-renderer/-/vue-server-renderer-2.6.12.tgz", + "integrity": "sha512-3LODaOsnQx7iMFTBLjki8xSyOxhCtbZ+nQie0wWY4iOVeEtTg1a3YQAjd82WvKxrWHHTshjvLb7OXMc2/dYuxw==", "dev": true, "requires": { "chalk": "^1.1.3", @@ -10005,7 +10256,7 @@ "lodash.template": "^4.5.0", "lodash.uniq": "^4.5.0", "resolve": "^1.2.0", - "serialize-javascript": "^2.1.2", + "serialize-javascript": "^3.1.0", "source-map": "0.5.6" }, "dependencies": { @@ -10028,6 +10279,15 @@ "supports-color": "^2.0.0" } }, + "serialize-javascript": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz", + "integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "source-map": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", @@ -10053,9 +10313,9 @@ } }, "vue-template-compiler": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz", - "integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz", + "integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==", "dev": true, "requires": { "de-indent": "^1.0.2", @@ -10069,13 +10329,13 @@ "dev": true }, "vuepress": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-1.5.3.tgz", - "integrity": "sha512-H9bGu6ygrZmq8GxMtDD8xNX1l9zvoyjsOA9oW+Lcttkfyt/HT/WBrpIC08kNPpRE0ZY/U4Jib1KgBfjbFZTffw==", + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/vuepress/-/vuepress-1.5.4.tgz", + "integrity": "sha512-F25r65BzxDFAJmWIN9s9sQSndLIf1ldAKEwkeXCqE4p2lsx/eVvQJL3DzOeeR2WgCFOkhFMKWIV+CthTGdNTZg==", "dev": true, "requires": { - "@vuepress/core": "1.5.3", - "@vuepress/theme-default": "1.5.3", + "@vuepress/core": "1.5.4", + "@vuepress/theme-default": "1.5.4", "cac": "^6.5.6", "envinfo": "^7.2.0", "opencollective-postinstall": "^2.0.2", @@ -10207,6 +10467,11 @@ } } }, + "vuetify": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.3.10.tgz", + "integrity": "sha512-KzL/MhZ7ajubm9kwbdCoA/cRV50RX+a5Hcqiwt7Am1Fni2crDtl2no05UNwKroTfscrYYf07gq3WIFSurPsnCA==" + }, "watchpack": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", @@ -10646,6 +10911,14 @@ "errno": "~0.1.7" } }, + "wowjs": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wowjs/-/wowjs-1.1.3.tgz", + "integrity": "sha1-RA/Bu0x+iWhA7keXIpaitZB1rL0=", + "requires": { + "animate.css": "^4.1.1" + } + }, "wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", diff --git a/package.json b/package.json index 0baf5f4d..0b6ca771 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,14 @@ }, "license": "MIT", "devDependencies": { - "@vuepress/plugin-back-to-top": "^1.3.1", - "@vuepress/plugin-medium-zoom": "^1.3.1", - "vuepress": "^1.3.1", + "@vuepress/plugin-back-to-top": "^1.5.4", + "@vuepress/plugin-medium-zoom": "^1.5.4", + "vuepress": "^1.5.4", "vuepress-plugin-versioning": "^4.5.0", "vuepress-theme-titanium": "^4.5.1" + }, + "dependencies": { + "vuetify": "^2.3.10", + "wowjs": "^1.1.3" } } diff --git a/poetry.lock b/poetry.lock index 5588003d..10ae5b4c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -197,7 +197,7 @@ description = "Chromium HSTS Preload list as a Python package and updated daily" name = "hstspreload" optional = false python-versions = ">=3.6" -version = "2020.8.18" +version = "2020.9.15" [package.source] reference = "aliyun" @@ -338,14 +338,14 @@ description = "Python logging made (stupidly) simple" name = "loguru" optional = false python-versions = ">=3.5" -version = "0.5.1" +version = "0.5.2" [package.dependencies] colorama = ">=0.3.4" win32-setctime = ">=1.0.0" [package.extras] -dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=4.3.20)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.3b0)"] +dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"] [package.source] reference = "aliyun" @@ -427,7 +427,7 @@ description = "Pygments is a syntax highlighting package written in Python." name = "pygments" optional = false python-versions = ">=3.5" -version = "2.6.1" +version = "2.7.1" [package.source] reference = "aliyun" @@ -620,10 +620,9 @@ unify = "*" yapf = "*" [package.source] -reference = "1438d33cbeaab0230c9f7e33bd059eb9f57c86d6" +reference = "792133d3bb15b956e5150c158371eb71f5670844" type = "git" url = "https://github.com/nonebot/sphinx-markdown-builder.git" - [[package]] category = "dev" description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" @@ -861,7 +860,7 @@ marker = "sys_platform == \"win32\"" name = "win32-setctime" optional = false python-versions = ">=3.5" -version = "1.0.1" +version = "1.0.2" [package.extras] dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] @@ -885,10 +884,12 @@ type = "legacy" url = "https://mirrors.aliyun.com/pypi/simple" [extras] +full = [] scheduler = ["apscheduler"] [metadata] -content-hash = "2e8f1fc9fcb89a528ecbebbf0f2315abf39e3de8eb40c133b91085a784e49173" +content-hash = "3a0f821e8ecef5548fa95e88cee11ff12910d08bed1633d8d03280f65d0bc1bf" +lock-version = "1.0" python-versions = "^3.7" [metadata.files] @@ -941,8 +942,8 @@ hpack = [ {file = "hpack-3.0.0.tar.gz", hash = "sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"}, ] hstspreload = [ - {file = "hstspreload-2020.8.18-py3-none-any.whl", hash = "sha256:5e3b6b2376c6f412086ee21cdd29cd5e0af5b28c967e5f1f026323d0f31dc84b"}, - {file = "hstspreload-2020.8.18.tar.gz", hash = "sha256:13cf2e9fcd064cd81d220432de9a66dd7e4f10862a03574c45e5f61fc522f312"}, + {file = "hstspreload-2020.9.15-py3-none-any.whl", hash = "sha256:c09f02dd4b7e3953a8353836aea964b868b22905bdb6d8932ca4b273df0f820f"}, + {file = "hstspreload-2020.9.15.tar.gz", hash = "sha256:059fc2ead7bb83ea9e85d06c1afc7112413f1d2a81e6448bd276af6bced035ac"}, ] html2text = [ {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"}, @@ -987,8 +988,8 @@ jinja2 = [ {file = "Jinja2-2.11.2.tar.gz", hash = "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0"}, ] loguru = [ - {file = "loguru-0.5.1-py3-none-any.whl", hash = "sha256:e5d362a43cd2fc2da63551d79a6830619c4d5b3a8b976515748026f92f351b61"}, - {file = "loguru-0.5.1.tar.gz", hash = "sha256:70201d5fce26da89b7a5f168caa2bb674e06b969829f56737db1d6472e53e7c3"}, + {file = "loguru-0.5.2-py3-none-any.whl", hash = "sha256:a5e5e196b9958feaf534ac2050171d16576bae633074ce3e73af7dda7e9a58ae"}, + {file = "loguru-0.5.2.tar.gz", hash = "sha256:5aecbf13bc8e2f6e5a5d0475460a345b44e2885464095ea7de44e8795857ad33"}, ] markupsafe = [ {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, @@ -1053,8 +1054,8 @@ pydash = [ {file = "pydash-4.8.0.tar.gz", hash = "sha256:546afa043ed1defa3122383bebe8b7072f43554ccc5f0c4360638f99e5ed7327"}, ] pygments = [ - {file = "Pygments-2.6.1-py3-none-any.whl", hash = "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"}, - {file = "Pygments-2.6.1.tar.gz", hash = "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44"}, + {file = "Pygments-2.7.1-py3-none-any.whl", hash = "sha256:307543fe65c0947b126e83dd5a61bd8acbd84abec11f43caebaf5534cbc17998"}, + {file = "Pygments-2.7.1.tar.gz", hash = "sha256:926c3f319eda178d1bd90851e4317e6d8cdb5e292a3386aac9bd75eca29cf9c7"}, ] pygtrie = [ {file = "pygtrie-2.3.3.tar.gz", hash = "sha256:2204dbd95584f67821da5b3771c4305ac5585552b3230b210f1f05322608db2c"}, @@ -1178,8 +1179,8 @@ websockets = [ {file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"}, ] win32-setctime = [ - {file = "win32_setctime-1.0.1-py3-none-any.whl", hash = "sha256:568fd636c68350bcc54755213fe01966fe0a6c90b386c0776425944a0382abef"}, - {file = "win32_setctime-1.0.1.tar.gz", hash = "sha256:b47e5023ec7f0b4962950902b15bc56464a380d869f59d27dbf9ab423b23e8f9"}, + {file = "win32_setctime-1.0.2-py3-none-any.whl", hash = "sha256:02b4c5959ca0b195f45c98115826c6e8a630b7cf648e724feaab1a5aa6250640"}, + {file = "win32_setctime-1.0.2.tar.gz", hash = "sha256:47aa7c43548c1fc0a4f026d1944b748b37036df116c7c4cf908e82638d854313"}, ] yapf = [ {file = "yapf-0.30.0-py2.py3-none-any.whl", hash = "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9"}, diff --git a/pyproject.toml b/pyproject.toml index 9c3f8e41..b6c78064 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [tool.poetry] -name = "nonebot" -version = "2.0.0" +name = "nonebot2" +version = "2.0.0a1" description = "An asynchronous python bot framework." authors = ["yanyongyu "] license = "MIT" readme = "README.md" homepage = "https://docs.nonebot.dev/" -repository = "https://github.com/nonebot/nonebot" +repository = "https://github.com/nonebot/nonebot2" documentation = "https://docs.nonebot.dev/" keywords = ["bot", "qq", "qqbot", "mirai", "coolq"] classifiers = [ @@ -17,27 +17,33 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python :: 3" ] +packages = [ + { include = "nonebot" } +] include = ["nonebot/py.typed"] [tool.poetry.dependencies] python = "^3.7" httpx = "^0.13.3" +loguru = "^0.5.1" pygtrie = "^2.3.3" fastapi = "^0.58.1" uvicorn = "^0.11.5" pydantic = { extras = ["dotenv"], version = "^1.6.1" } apscheduler = { version = "^3.6.3", optional = true } # nonebot-test = { version = "^0.1.0", optional = true } +# nb-cli = { version="^0.1.0", optional = true } -loguru = "^0.5.1" [tool.poetry.dev-dependencies] yapf = "^0.30.0" sphinx = "^3.1.1" sphinx-markdown-builder = { git = "https://github.com/nonebot/sphinx-markdown-builder.git" } [tool.poetry.extras] +# cli = ["nb-cli"] # test = ["nonebot-test"] scheduler = ["apscheduler"] +full = ["nb-cli", "nonebot-test", "scheduler"] [[tool.poetry.source]] name = "aliyun" diff --git a/tests/.env.dev b/tests/.env.dev index f169a095..2a129ee1 100644 --- a/tests/.env.dev +++ b/tests/.env.dev @@ -6,4 +6,5 @@ DEBUG=true COMMAND_START=["", "/", "#"] COMMAND_SEP=["/", "."] -CUSTOM_CONFIG={"custom": 1} +CUSTOM_CONFIG1=config in env +CUSTOM_CONFIG3= diff --git a/tests/bot.py b/tests/bot.py index 31ffb6c8..e66875e9 100644 --- a/tests/bot.py +++ b/tests/bot.py @@ -16,14 +16,19 @@ logger.add("error.log", level="ERROR", format=default_format) -nonebot.init() +nonebot.init(custom_config2="config on init") app = nonebot.get_asgi() # load builtin plugin -nonebot.load_plugin("nonebot.plugins.base") +nonebot.load_builtin_plugins() # load local plugins nonebot.load_plugins("test_plugins") +# modify some config / config depends on loaded configs +config = nonebot.get_driver().config +config.custom_config3 = config.custom_config1 +config.custom_config4 = "New custom config" + if __name__ == "__main__": nonebot.run(app="bot:app") diff --git a/tests/test_plugins/test_message.py b/tests/test_plugins/test_message.py index 76164bf7..e683b630 100644 --- a/tests/test_plugins/test_message.py +++ b/tests/test_plugins/test_message.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +from nonebot.rule import to_me from nonebot.typing import Event from nonebot.plugin import on_message from nonebot.adapters.cqhttp import Bot -test_message = on_message(state={"default": 1}) +test_message = on_message(to_me(), state={"default": 1}) @test_message.handle() diff --git a/tests/test_plugins/test_permission.py b/tests/test_plugins/test_permission.py new file mode 100644 index 00000000..a7f577a7 --- /dev/null +++ b/tests/test_plugins/test_permission.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +from nonebot.rule import to_me +from nonebot.typing import Event +from nonebot.plugin import on_startswith +from nonebot.adapters.cqhttp import Bot +from nonebot.permission import GROUP_ADMIN + +test_command = on_startswith("hello", to_me(), permission=GROUP_ADMIN) + + +@test_command.handle() +async def test_handler(bot: Bot, event: Event, state: dict): + await test_command.finish("hello") diff --git a/tests/test_plugins/test_weather.py b/tests/test_plugins/test_weather.py new file mode 100644 index 00000000..26effa5d --- /dev/null +++ b/tests/test_plugins/test_weather.py @@ -0,0 +1,24 @@ +from nonebot import on_command +from nonebot.rule import to_me +from nonebot.typing import Bot, Event + +weather = on_command("天气", rule=to_me(), priority=1) + + +@weather.handle() +async def handle_first_receive(bot: Bot, event: Event, state: dict): + args = str(event.message).strip() # 首次发送命令时跟随的参数,例:/天气 上海,则args为上海 + print(f"==={args}===") + if args: + state["city"] = args # 如果用户发送了参数则直接赋值 + + +@weather.got("city", prompt="你想查询哪个城市的天气呢?") +async def handle_city(bot: Bot, event: Event, state: dict): + city = state["city"] + if city not in ["上海", "北京"]: + await weather.reject("你想查询的城市暂不支持,请重新输入!") + # weather = await get_weather_from_xxx(city) + city_weather = "晴天" + # await bot.send(city_weather) + await weather.finish(city_weather)