From bd7aaa94c6d5abf89553feb92dbd02b4af8d731d Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Thu, 10 Sep 2020 18:31:53 +0800 Subject: [PATCH 01/30] :bug: fix: strip after at bot --- nonebot/adapters/cqhttp.py | 3 +++ nonebot/plugin.py | 2 ++ tests/bot.py | 2 +- tests/test_plugins/test_message.py | 3 ++- tests/test_plugins/test_permission.py | 15 +++++++++++++++ 5 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 tests/test_plugins/test_permission.py diff --git a/nonebot/adapters/cqhttp.py b/nonebot/adapters/cqhttp.py index 6424c1d0..19614ccd 100644 --- a/nonebot/adapters/cqhttp.py +++ b/nonebot/adapters/cqhttp.py @@ -84,6 +84,9 @@ 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.to_me: # check the last segment diff --git a/nonebot/plugin.py b/nonebot/plugin.py index ba526fcf..4dbfcd81 100644 --- a/nonebot/plugin.py +++ b/nonebot/plugin.py @@ -151,6 +151,8 @@ def on_command(cmd: Union[str, Tuple[str, ...]], async def _strip_cmd(bot, event, state: dict): message = event.message + print(message[0].data) + print(state["_prefix"]) event.message = message.__class__( str(message)[len(state["_prefix"]["raw_command"]):].strip()) diff --git a/tests/bot.py b/tests/bot.py index 31ffb6c8..b55faab4 100644 --- a/tests/bot.py +++ b/tests/bot.py @@ -20,7 +20,7 @@ nonebot.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") 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") From 4a890298dbaef8e99c3f0dd25d71474231f8b015 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sat, 12 Sep 2020 13:34:36 +0800 Subject: [PATCH 02/30] :sparkles: add apscheduler --- nonebot/__init__.py | 11 +++++++++++ nonebot/config.py | 10 ++++++++++ nonebot/sched.py | 12 ++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 nonebot/sched.py diff --git a/nonebot/__init__.py b/nonebot/__init__.py index 5e7a2034..4a15bd6a 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 @@ -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, @@ -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/config.py b/nonebot/config.py index 5ecc93e6..b56576a7 100644 --- a/nonebot/config.py +++ b/nonebot/config.py @@ -237,6 +237,16 @@ class Config(BaseConfig): 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/sched.py b/nonebot/sched.py new file mode 100644 index 00000000..cc54ac1e --- /dev/null +++ b/nonebot/sched.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +try: + from apscheduler.schedulers.asyncio import AsyncIOScheduler +except ImportError: + AsyncIOScheduler = None + +if AsyncIOScheduler: + scheduler = AsyncIOScheduler() +else: + scheduler = None From 60bb6c45d2c236617a88eb6de6b3d878f0282d5b Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sat, 12 Sep 2020 13:45:03 +0800 Subject: [PATCH 03/30] :memo: docs: guide, installation --- README.md | 2 +- docs/.vuepress/config.js | 15 +++++++- docs/api/config.md | 15 +++++++- docs/api/log.md | 17 ++++++--- docs/guide/README.md | 38 ++++++++++++++++++++ docs/guide/installation.md | 72 ++++++++++++++++++++++++++++++++++++++ pyproject.toml | 9 +++-- 7 files changed, 158 insertions(+), 10 deletions(-) create mode 100644 docs/guide/README.md create mode 100644 docs/guide/installation.md diff --git a/README.md b/README.md index 20e04892..664cce75 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ **NoneBot2 尚在开发中** -NoneBot2 是一个可扩展的的 Python 异步机器人框架,它会对机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。 +NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。 除了起到解析消息的作用,NoneBot 还为插件提供了大量实用的预设操作和权限控制机制,尤其对于命令处理器,它更是提供了完善且易用的会话机制和内部调用机制,以分别适应命令的连续交互和插件内部功能复用等需求。 diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 8f76a704..0ef4275e 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -47,8 +47,21 @@ module.exports = context => ({ selectText: "Languages", editLinkText: "在 GitHub 上编辑此页", lastUpdated: "上次更新", - nav: [{ text: "API", link: "/api/" }], + nav: [ + { text: "主页", link: "/" }, + { text: "指南", link: "/guide/" }, + { text: "API", link: "/api/" } + ], sidebar: { + "/guide/": [ + { + title: "指南", + path: "", + collapsable: false, + sidebar: "auto", + children: ["", "installation"] + } + ], "/api/": [ { title: "NoneBot Api Reference", diff --git a/docs/api/config.md b/docs/api/config.md index 9fe3464e..34f05069 100644 --- a/docs/api/config.md +++ b/docs/api/config.md @@ -143,7 +143,7 @@ API_ROOT={"123456": "http://127.0.0.1:5700"} * 类型: `Optional[float]` -* 默认值: `60.` +* 默认值: `30.` * 说明: @@ -242,3 +242,16 @@ 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/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/installation.md b/docs/guide/installation.md new file mode 100644 index 00000000..aaef6600 --- /dev/null +++ b/docs/guide/installation.md @@ -0,0 +1,72 @@ +# 安装 + +## NoneBot + +:::warning 注意 +请确保你的 Python 版本 >= 3.7。 +::: + +```bash +pip install nonebot2 +``` + +如果你需要使用最新的(可能尚未发布的)特性,可以克隆 Git 仓库后手动安装: + +```bash +git clone https://github.com/nonebot/nonebot2.git +cd nonebot2 +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/pyproject.toml b/pyproject.toml index 9c3f8e41..3967ff1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [tool.poetry] -name = "nonebot" +name = "nonebot2" version = "2.0.0" 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 = [ @@ -22,22 +22,25 @@ 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" From 659e2243a88ad306514477c7fee690524449ac52 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sat, 12 Sep 2020 13:53:18 +0800 Subject: [PATCH 04/30] :bug: change the project name into nonebot2 --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3967ff1c..388a5d18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,9 @@ classifiers = [ "Operating System :: OS Independent", "Programming Language :: Python :: 3" ] +packages = [ + { include = "nonebot" } +] include = ["nonebot/py.typed"] [tool.poetry.dependencies] From caa170bc339cb5b4992cd6697f9ff7acf73e1300 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sat, 12 Sep 2020 21:44:59 +0800 Subject: [PATCH 05/30] :arrow_up: bump version as pre-release --- poetry.lock | 25 +++++++++++++------------ pyproject.toml | 2 +- tests/.env.dev | 3 ++- tests/bot.py | 6 +++++- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5588003d..945b9229 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.9" [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" @@ -623,7 +623,6 @@ yapf = "*" reference = "1438d33cbeaab0230c9f7e33bd059eb9f57c86d6" 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.9-py3-none-any.whl", hash = "sha256:dcaa0cdc68e3cc581817c72adb5e9dacc992574397e01c9bab66759d41af72f5"}, + {file = "hstspreload-2020.9.9.tar.gz", hash = "sha256:61bcfe25cd4c97f014292576863fe142e962fbff6157ea567188a10341b134ef"}, ] 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"}, @@ -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 388a5d18..e471bae7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nonebot2" -version = "2.0.0" +version = "2.0.0.a1" description = "An asynchronous python bot framework." authors = ["yanyongyu "] license = "MIT" 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 b55faab4..9d9624c0 100644 --- a/tests/bot.py +++ b/tests/bot.py @@ -16,7 +16,7 @@ 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 @@ -25,5 +25,9 @@ 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 + if __name__ == "__main__": nonebot.run(app="bot:app") From 60c70804ed5c7cd96bc20ff1b74f5b30e944cad2 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 13 Sep 2020 00:18:31 +0800 Subject: [PATCH 06/30] :bulb: add scheduler docstring --- docs/.vuepress/config.js | 12 +++++++---- docs/api/README.md | 8 +++++++- docs/api/sched.md | 41 ++++++++++++++++++++++++++++++++++++++ docs/guide/installation.md | 2 +- docs_build/README.rst | 10 ++++++---- docs_build/sched.rst | 11 ++++++++++ nonebot/sched.py | 32 +++++++++++++++++++++++++++++ 7 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 docs/api/sched.md create mode 100644 docs_build/sched.rst diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 0ef4275e..eb288d04 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -76,6 +76,14 @@ module.exports = context => ({ title: "nonebot.typing 模块", path: "typing" }, + { + title: "nonebot.config 模块", + path: "config" + }, + { + title: "nonebot.sched 模块", + path: "sched" + }, { title: "nonebot.log 模块", path: "log" @@ -83,10 +91,6 @@ module.exports = context => ({ { title: "nonebot.exception 模块", path: "exception" - }, - { - title: "nonebot.config 模块", - path: "config" } ] } diff --git a/docs/api/README.md b/docs/api/README.md index 1aaf25f7..86282ced 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -10,7 +10,13 @@ * [nonebot.typing](typing.html) + * [nonebot.config](config.html) + + + * [nonebot.sched](sched.html) + + * [nonebot.log](log.html) - * [nonebot.config](config.html) + * [nonebot.exception](exception.html) 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/guide/installation.md b/docs/guide/installation.md index aaef6600..51dcb5da 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -24,7 +24,7 @@ pip install . A task scheduling library for Python. -可用于定时任务,后台执行任务等 +可用于计划任务,后台执行任务等 ```bash pip install nonebot2[scheduler] diff --git a/docs_build/README.rst b/docs_build/README.rst index 1ced2b53..cdcd2844 100644 --- a/docs_build/README.rst +++ b/docs_build/README.rst @@ -2,7 +2,9 @@ NoneBot Api Reference ===================== :模块索引: - - `nonebot `_ - - `nonebot.typing `_ - - `nonebot.log `_ - - `nonebot.config `_ + - `nonebot `_ + - `nonebot.typing `_ + - `nonebot.config `_ + - `nonebot.sched `_ + - `nonebot.log `_ + - `nonebot.exception `_ 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/nonebot/sched.py b/nonebot/sched.py index cc54ac1e..58447b67 100644 --- a/nonebot/sched.py +++ b/nonebot/sched.py @@ -1,5 +1,16 @@ #!/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 @@ -8,5 +19,26 @@ except ImportError: 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 From ce2700c1d2c1d767fec73c708ece707d18a8a724 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 13 Sep 2020 00:43:31 +0800 Subject: [PATCH 07/30] :bulb: add utils docstring --- docs/.vuepress/config.js | 4 +++ docs/api/README.md | 3 ++ docs/api/utils.md | 68 ++++++++++++++++++++++++++++++++++++++++ docs_build/README.rst | 1 + docs_build/utils.rst | 11 +++++++ nonebot/utils.py | 14 +++++++++ 6 files changed, 101 insertions(+) create mode 100644 docs/api/utils.md create mode 100644 docs_build/utils.rst diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index eb288d04..0d5de449 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -88,6 +88,10 @@ module.exports = context => ({ title: "nonebot.log 模块", path: "log" }, + { + title: "nonebot.utils 模块", + path: "utils" + }, { title: "nonebot.exception 模块", path: "exception" diff --git a/docs/api/README.md b/docs/api/README.md index 86282ced..8b1247e2 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -19,4 +19,7 @@ * [nonebot.log](log.html) + * [nonebot.utils](utils.html) + + * [nonebot.exception](exception.html) diff --git a/docs/api/utils.md b/docs/api/utils.md new file mode 100644 index 00000000..5e2db112 --- /dev/null +++ b/docs/api/utils.md @@ -0,0 +1,68 @@ +--- +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.JSONEncoder` + + + +* **说明** + + `JSONEncoder` used when encoding `Message` (List of dataclasses) + + + +### `default(o)` + +Implement this method in a subclass such that it returns +a serializable object for `o`, or calls the base implementation +(to raise a `TypeError`). + +For example, to support arbitrary iterators, you could +implement default like this: + +```default +def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + # Let the base class default method raise the TypeError + return JSONEncoder.default(self, o) +``` diff --git a/docs_build/README.rst b/docs_build/README.rst index cdcd2844..d33b9a65 100644 --- a/docs_build/README.rst +++ b/docs_build/README.rst @@ -7,4 +7,5 @@ NoneBot Api Reference - `nonebot.config `_ - `nonebot.sched `_ - `nonebot.log `_ + - `nonebot.utils `_ - `nonebot.exception `_ diff --git a/docs_build/utils.rst b/docs_build/utils.rst new file mode 100644 index 00000000..5fe73c0a --- /dev/null +++ b/docs_build/utils.rst @@ -0,0 +1,11 @@ +--- +contentSidebar: true +sidebarDepth: 0 +--- + +NoneBot.utils 模块 +================= + +.. automodule:: nonebot.utils + :members: + :show-inheritance: diff --git a/nonebot/utils.py b/nonebot/utils.py index 631395f5..81e78475 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,12 @@ def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: class DataclassEncoder(json.JSONEncoder): + """ + :类型: + ``json.JSONEncoder`` + :说明: + ``JSONEncoder`` used when encoding ``Message`` (List of dataclasses) + """ @overrides(json.JSONEncoder) def default(self, o): From f79eabdc613ae5ed78a56ebc8af3a038c381fa41 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 13 Sep 2020 13:01:23 +0800 Subject: [PATCH 08/30] :bulb: add rule utils docstring --- docs/.vuepress/config.js | 4 ++ docs/api/README.md | 3 ++ docs/api/nonebot.md | 4 +- docs/api/rule.md | 90 ++++++++++++++++++++++++++++++++++++++++ docs/api/typing.md | 28 ++++++------- docs/api/utils.md | 33 +-------------- docs_build/README.rst | 1 + docs_build/rule.rst | 12 ++++++ docs_build/utils.rst | 9 ++-- nonebot/__init__.py | 4 +- nonebot/permission.py | 15 +++---- nonebot/plugin.py | 2 - nonebot/rule.py | 58 ++++++++++++++++++++++---- nonebot/typing.py | 32 +++++++------- nonebot/utils.py | 6 +-- 15 files changed, 212 insertions(+), 89 deletions(-) create mode 100644 docs/api/rule.md create mode 100644 docs_build/rule.rst diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 0d5de449..c38ed27d 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -88,6 +88,10 @@ module.exports = context => ({ title: "nonebot.log 模块", path: "log" }, + { + title: "nonebot.rule 模块", + path: "rule" + }, { title: "nonebot.utils 模块", path: "utils" diff --git a/docs/api/README.md b/docs/api/README.md index 8b1247e2..71e3c247 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -19,6 +19,9 @@ * [nonebot.log](log.html) + * [nonebot.rule](rule.html) + + * [nonebot.utils](utils.html) 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/rule.md b/docs/api/rule.md new file mode 100644 index 00000000..ad53a09f --- /dev/null +++ b/docs/api/rule.md @@ -0,0 +1,90 @@ +--- +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` 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 index 5e2db112..7de3ba3f 100644 --- a/docs/api/utils.md +++ b/docs/api/utils.md @@ -25,7 +25,7 @@ sidebarDepth: 0 * **返回** - * Callable[..., Awaitable[Any]] + * `Callable[..., Awaitable[Any]]` @@ -34,35 +34,6 @@ sidebarDepth: 0 基类:`json.encoder.JSONEncoder` -* **类型** - - `json.JSONEncoder` - - - * **说明** - `JSONEncoder` used when encoding `Message` (List of dataclasses) - - - -### `default(o)` - -Implement this method in a subclass such that it returns -a serializable object for `o`, or calls the base implementation -(to raise a `TypeError`). - -For example, to support arbitrary iterators, you could -implement default like this: - -```default -def default(self, o): - try: - iterable = iter(o) - except TypeError: - pass - else: - return list(iterable) - # Let the base class default method raise the TypeError - return JSONEncoder.default(self, o) -``` + 在JSON序列化 `Message` (List[Dataclass]) 时使用的 `JSONEncoder` diff --git a/docs_build/README.rst b/docs_build/README.rst index d33b9a65..6ce28acd 100644 --- a/docs_build/README.rst +++ b/docs_build/README.rst @@ -7,5 +7,6 @@ NoneBot Api Reference - `nonebot.config `_ - `nonebot.sched `_ - `nonebot.log `_ + - `nonebot.rule `_ - `nonebot.utils `_ - `nonebot.exception `_ 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/utils.rst b/docs_build/utils.rst index 5fe73c0a..776bbfd0 100644 --- a/docs_build/utils.rst +++ b/docs_build/utils.rst @@ -4,8 +4,9 @@ sidebarDepth: 0 --- NoneBot.utils 模块 -================= +================== -.. automodule:: nonebot.utils - :members: - :show-inheritance: + +.. autodecorator:: nonebot.utils.run_sync +.. autoclass:: nonebot.utils.DataclassEncoder + :show-inheritance: diff --git a/nonebot/__init__.py b/nonebot/__init__.py index 4a15bd6a..68c2a8d2 100644 --- a/nonebot/__init__.py +++ b/nonebot/__init__.py @@ -136,7 +136,7 @@ def init(*, _env_file: Optional[str] = None, **kwargs): :返回: - - `None` + - ``None`` :用法: @@ -192,7 +192,7 @@ def run(host: Optional[str] = None, :返回: - - `None` + - ``None`` :用法: diff --git a/nonebot/permission.py b/nonebot/permission.py index fefda749..9cd104bb 100644 --- a/nonebot/permission.py +++ b/nonebot/permission.py @@ -4,14 +4,15 @@ 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: + self.checkers = set(checkers) async def __call__(self, bot: Bot, event: Event) -> bool: if not self.checkers: @@ -25,13 +26,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) else: - checkers.append(run_sync(other)) + checkers.add(run_sync(other)) return Permission(*checkers) diff --git a/nonebot/plugin.py b/nonebot/plugin.py index 4dbfcd81..ba526fcf 100644 --- a/nonebot/plugin.py +++ b/nonebot/plugin.py @@ -151,8 +151,6 @@ def on_command(cmd: Union[str, Tuple[str, ...]], async def _strip_cmd(bot, event, state: dict): message = event.message - print(message[0].data) - print(state["_prefix"]) event.message = message.__class__( str(message)[len(state["_prefix"]["raw_command"]):].strip()) diff --git a/nonebot/rule.py b/nonebot/rule.py index 15dc1e6a..3f34f511 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: 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 81e78475..06bdaaa4 100644 --- a/nonebot/utils.py +++ b/nonebot/utils.py @@ -16,7 +16,7 @@ def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: :参数: * ``func: Callable[..., Any]``: 被装饰的同步函数 :返回: - - Callable[..., Awaitable[Any]] + - ``Callable[..., Awaitable[Any]]`` """ @wraps(func) @@ -31,10 +31,8 @@ def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]: class DataclassEncoder(json.JSONEncoder): """ - :类型: - ``json.JSONEncoder`` :说明: - ``JSONEncoder`` used when encoding ``Message`` (List of dataclasses) + 在JSON序列化 ``Message`` (List[Dataclass]) 时使用的 ``JSONEncoder`` """ @overrides(json.JSONEncoder) From b36acc94f180f15b800beb35e10c65eeb03ac660 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 13 Sep 2020 15:21:49 +0800 Subject: [PATCH 09/30] :bug: fix reply --- nonebot/adapters/cqhttp.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/nonebot/adapters/cqhttp.py b/nonebot/adapters/cqhttp.py index 19614ccd..ba63def5 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"): @@ -87,6 +90,15 @@ def _check_at_me(bot: "Bot", event: "Event"): 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 From 75df494de277b3655cc9b0b7c82ad5d1e21fce61 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 13 Sep 2020 22:36:40 +0800 Subject: [PATCH 10/30] :zap: using set for rule and perm --- nonebot/permission.py | 2 +- nonebot/rule.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/nonebot/permission.py b/nonebot/permission.py index 9cd104bb..22a2c66b 100644 --- a/nonebot/permission.py +++ b/nonebot/permission.py @@ -30,7 +30,7 @@ class Permission: if isinstance(other, Permission): checkers |= other.checkers elif asyncio.iscoroutinefunction(other): - checkers.add(other) + checkers.add(other) # type: ignore else: checkers.add(run_sync(other)) return Permission(*checkers) diff --git a/nonebot/rule.py b/nonebot/rule.py index 3f34f511..d2f33fcf 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -155,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) From fe35e8956b7c0ab3334878f95de833356b3c35eb Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Mon, 14 Sep 2020 20:48:03 +0800 Subject: [PATCH 11/30] :bulb: add permission docstring --- docs/.vuepress/config.js | 4 ++ docs/api/README.md | 3 + docs/api/permission.md | 121 ++++++++++++++++++++++++++++++++++++++ docs/api/rule.md | 34 ++++++++++- docs_build/README.rst | 1 + docs_build/permission.rst | 11 ++++ nonebot/permission.py | 82 ++++++++++++++++++++++++++ nonebot/rule.py | 2 +- 8 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 docs/api/permission.md create mode 100644 docs_build/permission.rst diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index c38ed27d..58b69554 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -92,6 +92,10 @@ module.exports = context => ({ title: "nonebot.rule 模块", path: "rule" }, + { + title: "nonebot.permission 模块", + path: "permission" + }, { title: "nonebot.utils 模块", path: "utils" diff --git a/docs/api/README.md b/docs/api/README.md index 71e3c247..2fa1066a 100644 --- a/docs/api/README.md +++ b/docs/api/README.md @@ -22,6 +22,9 @@ * [nonebot.rule](rule.html) + * [nonebot.permission](permission.html) + + * [nonebot.utils](utils.html) 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 index ad53a09f..269abc57 100644 --- a/docs/api/rule.md +++ b/docs/api/rule.md @@ -7,7 +7,7 @@ sidebarDepth: 0 ## 规则 -每个 `Matcher` 拥有一个 `Rule` ,其中是 `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。 +每个 `Matcher` 拥有一个 `Rule` ,其中是 **异步** `RuleChecker` 的集合,只有当所有 `RuleChecker` 检查结果为 `True` 时继续运行。 :::tip 提示 `RuleChecker` 既可以是 async function 也可以是 sync function @@ -88,3 +88,35 @@ Rule(async_function, run_sync(sync_function)) * `bool` + + + +## `startswith(msg)` + + +* **说明** + + 匹配消息开头 + + + +* **参数** + + + * `msg: str`: 消息开头字符串 + + + +## `endswith(msg)` + + +* **说明** + + 匹配消息结尾 + + + +* **参数** + + + * `msg: str`: 消息结尾字符串 diff --git a/docs_build/README.rst b/docs_build/README.rst index 6ce28acd..cb726c38 100644 --- a/docs_build/README.rst +++ b/docs_build/README.rst @@ -8,5 +8,6 @@ NoneBot Api Reference - `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/nonebot/permission.py b/nonebot/permission.py index 22a2c66b..1f43cf38 100644 --- a/nonebot/permission.py +++ b/nonebot/permission.py @@ -1,5 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- +""" +权限 +==== + +每个 ``Matcher`` 拥有一个 ``Permission`` ,其中是 **异步** ``PermissionChecker`` 的集合,只要有一个 ``PermissionChecker`` 检查结果为 ``True`` 时就会继续运行。 + +\:\:\:tip 提示 +``PermissionChecker`` 既可以是 async function 也可以是 sync function +\:\:\: +""" import asyncio @@ -12,9 +22,28 @@ class Permission: 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( @@ -53,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( @@ -87,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: @@ -112,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: @@ -122,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 d2f33fcf..0b1b5cc0 100644 --- a/nonebot/rule.py +++ b/nonebot/rule.py @@ -4,7 +4,7 @@ 规则 ==== -每个 ``Matcher`` 拥有一个 ``Rule`` ,其中是 ``RuleChecker`` 的集合,只有当所有 ``RuleChecker`` 检查结果为 ``True`` 时继续运行。 +每个 ``Matcher`` 拥有一个 ``Rule`` ,其中是 **异步** ``RuleChecker`` 的集合,只有当所有 ``RuleChecker`` 检查结果为 ``True`` 时继续运行。 \:\:\:tip 提示 ``RuleChecker`` 既可以是 async function 也可以是 sync function From c2badaec4275b2033c983e312baa51809d3fb59d Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Mon, 14 Sep 2020 21:40:05 +0800 Subject: [PATCH 12/30] :memo: add getting started guide --- docs/.vuepress/config.js | 2 +- docs/guide/getting-started.md | 142 ++++++++++++++++++++++++++++++++++ nonebot/config.py | 6 +- 3 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 docs/guide/getting-started.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 58b69554..981105c3 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -59,7 +59,7 @@ module.exports = context => ({ path: "", collapsable: false, sidebar: "auto", - children: ["", "installation"] + children: ["", "installation", "getting-started"] } ], "/api/": [ diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md new file mode 100644 index 00000000..21b1de65 --- /dev/null +++ b/docs/guide/getting-started.md @@ -0,0 +1,142 @@ +# 开始使用 + +一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 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/nonebot/config.py b/nonebot/config.py index b56576a7..f88b12b5 100644 --- a/nonebot/config.py +++ b/nonebot/config.py @@ -170,7 +170,7 @@ class Config(BaseConfig): 以机器人 ID 为键,上报地址为值的字典,环境变量或文件中应使用 json 序列化。 - 示例: - .. code-block:: plain + .. code-block:: default API_ROOT={"123456": "http://127.0.0.1:5700"} """ @@ -198,7 +198,7 @@ class Config(BaseConfig): 机器人超级用户。 - 示例: - .. code-block:: plain + .. code-block:: default SUPER_USERS=[12345789] """ @@ -231,7 +231,7 @@ class Config(BaseConfig): 等待用户回复的超时时间。 - 示例: - .. code-block:: plain + .. code-block:: default SESSION_EXPIRE_TIMEOUT=120 # 单位: 秒 SESSION_EXPIRE_TIMEOUT=[DD ][HH:MM]SS[.ffffff] From adc69a276389e41a625b61277afec58d2b596c15 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Mon, 14 Sep 2020 21:51:40 +0800 Subject: [PATCH 13/30] :loud_sound: fix error uncatched in bot.handle_message --- nonebot/adapters/cqhttp.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/nonebot/adapters/cqhttp.py b/nonebot/adapters/cqhttp.py index ba63def5..dc7ad38a 100644 --- a/nonebot/adapters/cqhttp.py +++ b/nonebot/adapters/cqhttp.py @@ -214,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]: From 50601934b952cbe9733265a3a4a7567881ef12fd Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Mon, 14 Sep 2020 22:38:03 +0800 Subject: [PATCH 14/30] :wrench: load extra config value from env if empty in file --- nonebot/config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nonebot/config.py b/nonebot/config.py index f88b12b5..7b9281fb 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: From 44722a11d3474d3b375d16def6d893508de7fd36 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Tue, 15 Sep 2020 14:48:15 +0800 Subject: [PATCH 15/30] :lock: fix wrong auth check --- nonebot/__init__.py | 4 ++-- nonebot/config.py | 34 ++++++++++++++++++++-------------- nonebot/drivers/fastapi.py | 22 +++++++++++++++++----- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/nonebot/__init__.py b/nonebot/__init__.py index 68c2a8d2..bd0ee80b 100644 --- a/nonebot/__init__.py +++ b/nonebot/__init__.py @@ -147,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}") diff --git a/nonebot/config.py b/nonebot/config.py index 7b9281fb..9a9c4ffa 100644 --- a/nonebot/config.py +++ b/nonebot/config.py @@ -143,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`` @@ -189,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 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: From e124b08e4977a8b5efa8489db5da72cd301cecd1 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Thu, 17 Sep 2020 13:12:48 +0800 Subject: [PATCH 16/30] :memo: add creating project --- docs/.vuepress/components/Messenger.vue | 231 ++++++++ docs/.vuepress/config.js | 23 +- docs/.vuepress/enhanceApp.js | 15 +- docs/guide/creating-a-project.md | 55 ++ docs/guide/getting-started.md | 2 + docs/guide/installation.md | 3 +- package-lock.json | 713 ++++++++++++++++-------- package.json | 10 +- 8 files changed, 826 insertions(+), 226 deletions(-) create mode 100644 docs/.vuepress/components/Messenger.vue create mode 100644 docs/guide/creating-a-project.md diff --git a/docs/.vuepress/components/Messenger.vue b/docs/.vuepress/components/Messenger.vue new file mode 100644 index 00000000..44254061 --- /dev/null +++ b/docs/.vuepress/components/Messenger.vue @@ -0,0 +1,231 @@ + + + + + diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 981105c3..7cfa7406 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: { @@ -59,7 +67,12 @@ module.exports = context => ({ path: "", collapsable: false, sidebar: "auto", - children: ["", "installation", "getting-started"] + children: [ + "", + "installation", + "getting-started", + "creating-a-project" + ] } ], "/api/": [ @@ -124,6 +137,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/guide/creating-a-project.md b/docs/guide/creating-a-project.md new file mode 100644 index 00000000..b051d6e7 --- /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 index 21b1de65..c7ddb2cf 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -140,3 +140,5 @@ QQ 协议端举例: ``` 到这里如果一切 OK,你应该会收到机器人给你回复了 `你好,世界`。这一历史性的对话标志着你已经成功地运行了一个 NoneBot 的最小实例,开始了编写更强大的 QQ 机器人的创意之旅! + + diff --git a/docs/guide/installation.md b/docs/guide/installation.md index 51dcb5da..06a88598 100644 --- a/docs/guide/installation.md +++ b/docs/guide/installation.md @@ -15,7 +15,8 @@ pip install nonebot2 ```bash git clone https://github.com/nonebot/nonebot2.git cd nonebot2 -pip install . +poetry install --no-dev # 推荐 +pip install . # 不推荐 ``` ## 额外依赖 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" } } From f26058160897d1728343bd1be14c9e946cf02bc3 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Thu, 17 Sep 2020 13:19:50 +0800 Subject: [PATCH 17/30] :ambulance: fix build error --- docs/guide/getting-started.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index c7ddb2cf..d52a9396 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -141,4 +141,6 @@ QQ 协议端举例: 到这里如果一切 OK,你应该会收到机器人给你回复了 `你好,世界`。这一历史性的对话标志着你已经成功地运行了一个 NoneBot 的最小实例,开始了编写更强大的 QQ 机器人的创意之旅! - + + + From 7207b8c8d7e256ceec28b4f02a41ef835d1c381a Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Thu, 17 Sep 2020 18:21:13 +0800 Subject: [PATCH 18/30] :pencil2: fix typo --- docs/api/config.md | 60 ++++++++++++++++++++++++++-------------------- nonebot/config.py | 2 +- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/docs/api/config.md b/docs/api/config.md index 34f05069..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"} ``` @@ -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,7 +245,7 @@ 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 diff --git a/nonebot/config.py b/nonebot/config.py index 9a9c4ffa..1680f40c 100644 --- a/nonebot/config.py +++ b/nonebot/config.py @@ -134,7 +134,7 @@ class Config(BaseConfig): - 类型: ``IPvAnyAddress`` - 默认值: ``127.0.0.1`` - 说明: - NoneBot 的 HTTP 和 WebSocket 服务端监听的 IP/主机名。 + NoneBot 的 HTTP 和 WebSocket 服务端监听的 IP/主机名。 """ port: int = 8080 """ From 2aef71a6afd07c90427b52869a1767e796913e6c Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Thu, 17 Sep 2020 18:22:48 +0800 Subject: [PATCH 19/30] :lipstick: update info --- docs/.vuepress/components/Messenger.vue | 23 ++++------------------- docs/guide/creating-a-project.md | 6 +++--- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/docs/.vuepress/components/Messenger.vue b/docs/.vuepress/components/Messenger.vue index 44254061..7c12d979 100644 --- a/docs/.vuepress/components/Messenger.vue +++ b/docs/.vuepress/components/Messenger.vue @@ -19,22 +19,7 @@

NoneBot

- - - Terminal - - - - Clear - + fa-user @@ -136,9 +121,9 @@ - + + + diff --git a/docs/guide/creating-a-project.md b/docs/guide/creating-a-project.md index b051d6e7..74cdb24a 100644 --- a/docs/guide/creating-a-project.md +++ b/docs/guide/creating-a-project.md @@ -19,9 +19,9 @@ nb create AweSome-Bot ├── `awesome_bot` _(**或是 src**)_ │ └── `plugins` -├── `.env` -├── `.env.dev` -├── `.env.prod` +├── `.env` _(**可选的**)_ +├── `.env.dev` _(**可选的**)_ +├── `.env.prod` _(**可选的**)_ ├── .gitignore ├── `bot.py` ├── docker-compose.yml From 2add5d38878da6ec8854c6f909f390513271d806 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Thu, 17 Sep 2020 18:23:41 +0800 Subject: [PATCH 20/30] :memo: add config guide --- docs/.vuepress/config.js | 3 +- docs/guide/basic-configuration.md | 68 +++++++++++++++++++++++++++++++ tests/bot.py | 1 + 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 docs/guide/basic-configuration.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 7cfa7406..e36ea552 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -71,7 +71,8 @@ module.exports = context => ({ "", "installation", "getting-started", - "creating-a-project" + "creating-a-project", + "basic-configuration" ] } ], diff --git a/docs/guide/basic-configuration.md b/docs/guide/basic-configuration.md new file mode 100644 index 00000000..611bb3a6 --- /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/tests/bot.py b/tests/bot.py index 9d9624c0..e66875e9 100644 --- a/tests/bot.py +++ b/tests/bot.py @@ -28,6 +28,7 @@ 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") From e58788360a270cb09d5157dfbeab6c79e692fab8 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Thu, 17 Sep 2020 18:40:21 +0800 Subject: [PATCH 21/30] :memo: update README --- README.md | 4 ++-- docs/guide/basic-configuration.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 664cce75..78caed43 100644 --- a/README.md +++ b/README.md @@ -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/docs/guide/basic-configuration.md b/docs/guide/basic-configuration.md index 611bb3a6..e4c4449f 100644 --- a/docs/guide/basic-configuration.md +++ b/docs/guide/basic-configuration.md @@ -44,7 +44,7 @@ CUSTOM_CONFIG1="config in env file" CUSTOM_CONFIG2= # 留空则从系统环境变量读取,如不存在则为空字符串 ``` -详细的配置项参考 [Config Reference](/api/config.md) 。 +详细的配置项参考 [Config Reference](../api/config.md) 。 ## bot.py 文件 From bf93da38234da8a07da04ee84731e85dc23ffb40 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sat, 19 Sep 2020 21:29:58 +0800 Subject: [PATCH 22/30] :pushpin: bump version --- poetry.lock | 14 +++++++------- pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index 945b9229..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.9.9" +version = "2020.9.15" [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,7 +620,7 @@ unify = "*" yapf = "*" [package.source] -reference = "1438d33cbeaab0230c9f7e33bd059eb9f57c86d6" +reference = "792133d3bb15b956e5150c158371eb71f5670844" type = "git" url = "https://github.com/nonebot/sphinx-markdown-builder.git" [[package]] @@ -942,8 +942,8 @@ hpack = [ {file = "hpack-3.0.0.tar.gz", hash = "sha256:8eec9c1f4bfae3408a3f30500261f7e6a65912dc138526ea054f9ad98892e9d2"}, ] hstspreload = [ - {file = "hstspreload-2020.9.9-py3-none-any.whl", hash = "sha256:dcaa0cdc68e3cc581817c72adb5e9dacc992574397e01c9bab66759d41af72f5"}, - {file = "hstspreload-2020.9.9.tar.gz", hash = "sha256:61bcfe25cd4c97f014292576863fe142e962fbff6157ea567188a10341b134ef"}, + {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"}, @@ -1054,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"}, diff --git a/pyproject.toml b/pyproject.toml index e471bae7..b6c78064 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nonebot2" -version = "2.0.0.a1" +version = "2.0.0a1" description = "An asynchronous python bot framework." authors = ["yanyongyu "] license = "MIT" From 4d2dc361b625f64b0e99c623fbda460f44e3d682 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sat, 19 Sep 2020 22:08:06 +0800 Subject: [PATCH 23/30] :memo: add plugin guide --- docs/.vuepress/config.js | 3 +- docs/guide/writing-a-plugin.md | 90 ++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 docs/guide/writing-a-plugin.md diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index e36ea552..3b469642 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -72,7 +72,8 @@ module.exports = context => ({ "installation", "getting-started", "creating-a-project", - "basic-configuration" + "basic-configuration", + "writing-a-plugin" ] } ], diff --git a/docs/guide/writing-a-plugin.md b/docs/guide/writing-a-plugin.md new file mode 100644 index 00000000..f90527dd --- /dev/null +++ b/docs/guide/writing-a-plugin.md @@ -0,0 +1,90 @@ +# 编写插件 + +本章将以一个天气查询插件为例,教学如何编写自己的命令。 + +## 加载插件 + +在 [创建一个完整的项目](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` 中编写。 + +## 编写真正的内容 From 3916cdc244f47336a8855914b1531dece8ef9602 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Sun, 20 Sep 2020 21:58:33 +0800 Subject: [PATCH 24/30] :memo: add weather --- docs/guide/writing-a-plugin.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/guide/writing-a-plugin.md b/docs/guide/writing-a-plugin.md index f90527dd..fbf15695 100644 --- a/docs/guide/writing-a-plugin.md +++ b/docs/guide/writing-a-plugin.md @@ -88,3 +88,29 @@ AweSome-Bot 这个时候 `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_from_xxx(city) + await weather.finish(city_weather) +``` From 342d879addb063a02849d16482b18a2161991360 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Tue, 22 Sep 2020 00:18:57 +0800 Subject: [PATCH 25/30] :memo: add matcher tutorial --- docs/guide/writing-a-plugin.md | 71 +++++++++++++++++++++++++++++++++- nonebot/matcher.py | 6 +-- 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/docs/guide/writing-a-plugin.md b/docs/guide/writing-a-plugin.md index fbf15695..2a6635d8 100644 --- a/docs/guide/writing-a-plugin.md +++ b/docs/guide/writing-a-plugin.md @@ -111,6 +111,75 @@ 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_from_xxx(city) + 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 + +weather = on_command("天气", rule=to_me(), priority=5) +``` + +在上方代码中,我们注册了一个事件响应器 `Matcher`,它由几个部分组成: + +1. `on_command` 注册一个消息类型的命令处理器 +2. `"天气"` 指定 command 参数 - 命令名 +3. `rule` 补充事件响应器的匹配规则 +4. `priority` 事件响应器优先级 +5. `permission` 事件响应器的“使用权限” + +其他详细配置可以参考 API 文档,下面我们详细说明各个部分: + +#### 事件响应器类型 + +事件响应器类型其实就是对应 `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`,由非负个 `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 +::: diff --git a/nonebot/matcher.py b/nonebot/matcher.py index 351661b9..87321876 100644 --- a/nonebot/matcher.py +++ b/nonebot/matcher.py @@ -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: From e68ce8b3a95a42d3a872b6254e9e55ddbc709496 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Tue, 22 Sep 2020 11:28:38 +0800 Subject: [PATCH 26/30] :bug: fix args_parser: dont ignore if exists --- nonebot/matcher.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nonebot/matcher.py b/nonebot/matcher.py index 87321876..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 @@ -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: From 5c02e5a835495bc7abcc7f3b203049dbd8abe255 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Tue, 22 Sep 2020 15:41:56 +0800 Subject: [PATCH 27/30] :memo: add priority and block --- docs/.vuepress/config.js | 1 + docs/guide/writing-a-plugin.md | 30 ++++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 3b469642..aa2e8ea1 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -60,6 +60,7 @@ module.exports = context => ({ { text: "指南", link: "/guide/" }, { text: "API", link: "/api/" } ], + sidebarDepth: 2, sidebar: { "/guide/": [ { diff --git a/docs/guide/writing-a-plugin.md b/docs/guide/writing-a-plugin.md index 2a6635d8..8e396c85 100644 --- a/docs/guide/writing-a-plugin.md +++ b/docs/guide/writing-a-plugin.md @@ -132,8 +132,9 @@ async def get_weather(city: str): ```python{4} from nonebot import on_command from nonebot.rule import to_me +from nonebot.permission import Permission -weather = on_command("天气", rule=to_me(), priority=5) +weather = on_command("天气", rule=to_me(), permission=Permission(), priority=5) ``` 在上方代码中,我们注册了一个事件响应器 `Matcher`,它由几个部分组成: @@ -142,11 +143,11 @@ weather = on_command("天气", rule=to_me(), priority=5) 2. `"天气"` 指定 command 参数 - 命令名 3. `rule` 补充事件响应器的匹配规则 4. `priority` 事件响应器优先级 -5. `permission` 事件响应器的“使用权限” +5. `block` 是否阻止事件传递 其他详细配置可以参考 API 文档,下面我们详细说明各个部分: -#### 事件响应器类型 +#### 事件响应器类型 type 事件响应器类型其实就是对应 `Event.type` ,NoneBot 提供了一个基础类型事件响应器 `on()` 以及一些内置的事件响应器。 @@ -160,7 +161,7 @@ weather = on_command("天气", rule=to_me(), priority=5) - `on_command(str|tuple)` ~ `on("message", command(str|tuple))`: 命令处理器 - `on_regax(pattern_str)` ~ `on("message", regax(pattern_str))`: 正则匹配处理器 -#### 匹配规则 +#### 匹配规则 rule 事件响应器的匹配规则即 `Rule`,由非负个 `RuleChecker` 组成,当所有 `RuleChecker` 返回 `True` 时匹配成功。这些 `RuleChecker` 的形式如下: @@ -183,3 +184,24 @@ 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) From 3f01963452d2dd4eeb06b3c6dcc5f8fdfeb0e493 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Tue, 22 Sep 2020 19:44:37 +0800 Subject: [PATCH 28/30] :memo: add handler --- docs/guide/writing-a-plugin.md | 53 +++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/docs/guide/writing-a-plugin.md b/docs/guide/writing-a-plugin.md index 8e396c85..1e183703 100644 --- a/docs/guide/writing-a-plugin.md +++ b/docs/guide/writing-a-plugin.md @@ -204,4 +204,55 @@ pip install nonebot2[test] NoneBot 内置的事件响应器中,所有 `message` 类的事件响应器默认会阻断事件传递,其他则不会。 -### 编写事件处理函数 [Handler](../api/typing.md#Handler) +### 编写事件处理函数 [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`: From 3b5e37f64b4fde45b9463e170da4484685404910 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Wed, 23 Sep 2020 22:10:38 +0800 Subject: [PATCH 29/30] :memo: finish plugin introduction --- docs/guide/writing-a-plugin.md | 36 ++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/guide/writing-a-plugin.md b/docs/guide/writing-a-plugin.md index 1e183703..bd176750 100644 --- a/docs/guide/writing-a-plugin.md +++ b/docs/guide/writing-a-plugin.md @@ -254,5 +254,37 @@ async def handle(bot: Bot, event: Event, state: dict): 参数分别为: 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`: +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`,用于向用户发送一条消息。 + +## 结语 + +至此,相信你已经能够写出一个基础的插件了,更多的用法将会在 进阶 部分进行介绍,这里给出几个小提示: + +- 请千万注意事件处理器的优先级设定 +- 在匹配规则中请勿使用耗时极长的函数 +- 同一个用户可以跨群(私聊)继续他的事件处理(除非做出权限限制,将在后续介绍) From e53599dc17037b1990a2ad4c03056d4c03141578 Mon Sep 17 00:00:00 2001 From: yanyongyu Date: Wed, 23 Sep 2020 23:01:06 +0800 Subject: [PATCH 30/30] :bookmark: pre release 2.0.0a1 --- archive/2.0.0a1/README.md | 15 + archive/2.0.0a1/api/README.md | 31 ++ archive/2.0.0a1/api/config.md | 265 +++++++++++++++++ archive/2.0.0a1/api/exception.md | 156 ++++++++++ archive/2.0.0a1/api/log.md | 55 ++++ archive/2.0.0a1/api/nonebot.md | 208 +++++++++++++ archive/2.0.0a1/api/permission.md | 121 ++++++++ archive/2.0.0a1/api/rule.md | 122 ++++++++ archive/2.0.0a1/api/sched.md | 41 +++ archive/2.0.0a1/api/typing.md | 236 +++++++++++++++ archive/2.0.0a1/api/utils.md | 39 +++ archive/2.0.0a1/guide/README.md | 38 +++ archive/2.0.0a1/guide/basic-configuration.md | 68 +++++ archive/2.0.0a1/guide/creating-a-project.md | 55 ++++ archive/2.0.0a1/guide/getting-started.md | 146 ++++++++++ archive/2.0.0a1/guide/installation.md | 73 +++++ archive/2.0.0a1/guide/writing-a-plugin.md | 290 +++++++++++++++++++ archive/2.0.0a1/sidebar.config.json | 88 ++++++ docs/.vuepress/versions.json | 3 + tests/test_plugins/test_weather.py | 24 ++ 20 files changed, 2074 insertions(+) create mode 100644 archive/2.0.0a1/README.md create mode 100644 archive/2.0.0a1/api/README.md create mode 100644 archive/2.0.0a1/api/config.md create mode 100644 archive/2.0.0a1/api/exception.md create mode 100644 archive/2.0.0a1/api/log.md create mode 100644 archive/2.0.0a1/api/nonebot.md create mode 100644 archive/2.0.0a1/api/permission.md create mode 100644 archive/2.0.0a1/api/rule.md create mode 100644 archive/2.0.0a1/api/sched.md create mode 100644 archive/2.0.0a1/api/typing.md create mode 100644 archive/2.0.0a1/api/utils.md create mode 100644 archive/2.0.0a1/guide/README.md create mode 100644 archive/2.0.0a1/guide/basic-configuration.md create mode 100644 archive/2.0.0a1/guide/creating-a-project.md create mode 100644 archive/2.0.0a1/guide/getting-started.md create mode 100644 archive/2.0.0a1/guide/installation.md create mode 100644 archive/2.0.0a1/guide/writing-a-plugin.md create mode 100644 archive/2.0.0a1/sidebar.config.json create mode 100644 docs/.vuepress/versions.json create mode 100644 tests/test_plugins/test_weather.py 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/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/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)