` 中,可据此编写 CSS 来改变文本表现。
-
-#### md_to_pic
-
-```python
-async def md_to_pic(
- md: str = "",
- md_path: str = "",
- css_path: str = "",
- *,
- max_width: int = 500,
- img_fetch_fn: ImgFetchFn = combined_img_fetcher,
- allow_refit: bool = True,
- image_format: Literal["png", "jpeg"] = "png",
- jpeg_quality: int = 100,
-) -> bytes:
- ...
-```
-
-可用于渲染 Markdown 文本。默认为 GitHub Markdown Light 风格,支持基于 `pygments` 的代码高亮。
-
-`md` 和 `md_path` 二选一,前者设置时应为 Markdown 的文本,后者设置时应为指向 Markdown 文本文件的路径。
-
-#### template_to_pic
-
-```python
-async def template_to_pic(
- template_path: str | PathLike[str] | Sequence[str | PathLike[str]],
- template_name: str,
- templates: Mapping[Any, Any],
- filters: None | Mapping[str, Any] = None,
- *,
- max_width: int = 500,
- device_height: int = 600,
- base_url: str | None = None,
- img_fetch_fn: ImgFetchFn = combined_img_fetcher,
- css_fetch_fn: CSSFetchFn = combined_css_fetcher,
- allow_refit: bool = True,
- image_format: Literal["png", "jpeg"] = "png",
- jpeg_quality: int = 100,
-) -> bytes:
- ...
-```
-
-渲染 jinja2 模板。
-
-`template_path` 为 jinja2 环境的路径,`template_name` 是环境中要加载模板的名字,`templates` 为传入模板的参数,`filters` 为过滤器名 -> 自定义过滤器的映射。
-
-### 控制外部资源获取
-
-通过传入 `img_fetch_fn` 与 `css_fetch_fn`,我们可以在实际访问资源前进行审查,修改资源的来源,或是对 IO 操作进行缓存。
-
-`img_fetch_fn` 预期为一个异步可调用对象(函数),接收图片 url 并返回对应 url 的 jpeg 或 png 二进制数据(`bytes`),可在拒绝加载时返回 `None`.
-
-`css_fetch_fn` 预期为一个异步可调用对象(函数),接收目标 CSS url 并返回对应 url 的 CSS 文本(`str`),可在拒绝加载时返回 `None`.
-
-如果你想要禁用外部资源加载/只从文件系统加载/只从网络加载,可以使用 `none_fetcher` `filesystem_***_fetcher` `network_***_fetcher`。
-
-默认的 fetcher 行为(对于 `file://` 从文件系统加载,其余从网络加载)位于 `combined_***_fetcher`,可以通过对其封装实现缓存等操作。
-
-## 配置项
-
-### 配置 fontconfig
-
-`htmlkit` 使用 `fontconfig` 查找字体,请参阅 [`fontconfig 用户手册`](https://fontconfig.pages.freedesktop.org/fontconfig/fontconfig-user) 了解环境变量的具体含义、如何通过编写配置文件修改字体配置等。
-
-#### fontconfig_file
-
-- **类型**: `str | None`
-- **默认值**: `None`
-
-覆盖默认的配置文件路径。
-
-#### fontconfig_path
-
-- **类型**: `str | None`
-- **默认值**: `None`
-
-覆盖默认的配置目录。
-
-#### fontconfig_sysroot
-
-- **类型**: `str | None`
-- **默认值**: `None`
-
-覆盖默认的 sysroot。
-
-#### fc_debug
-
-- **类型**: `str | None`
-- **默认值**: `None`
-
-设置 Fontconfig 的 debug 级别。
-
-#### fc_dbg_match_filter
-
-- **类型**: `str | None`
-- **默认值**: `None`
-
-当 `FC_DEBUG` 设置为 `MATCH2` 时,过滤 debug 输出。
-
-#### fc_lang
-
-- **类型**: `str | None`
-- **默认值**: `None`
-
-设置默认语言,否则从 `LOCALE` 环境变量获取。
-
-#### fontconfig_use_mmap
-
-- **类型**: `str | None`
-- **默认值**: `None`
-
-是否使用 `mmap(2)` 读取字体缓存。
diff --git a/website/versioned_docs/version-2.4.1/best-practice/multi-adapter.mdx b/website/versioned_docs/version-2.4.1/best-practice/multi-adapter.mdx
deleted file mode 100644
index 2ce840b7ef12..000000000000
--- a/website/versioned_docs/version-2.4.1/best-practice/multi-adapter.mdx
+++ /dev/null
@@ -1,183 +0,0 @@
----
-sidebar_position: 4
-description: 插件跨平台支持
----
-
-# 插件跨平台支持
-
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-
-由于不同平台的事件与接口之间,存在着极大的差异性,NoneBot 通过[重载](../appendices/overload.md)的方式,使得插件可以在不同平台上正确响应。但为了减少跨平台的兼容性问题,我们应该尽可能的使用基类方法实现原生跨平台,而不是使用特定平台的方法。当基类方法无法满足需求时,我们可以使用依赖注入的方式,将特定平台的事件或机器人注入到事件处理函数中,实现针对特定平台的处理。
-
-:::tip 提示
-如果需要在多平台上**使用**跨平台插件,首先应该根据[注册适配器](../advanced/adapter.md#注册适配器)一节,为机器人注册各平台对应的适配器。
-:::
-
-## 基于基类的跨平台
-
-在[事件通用信息](../advanced/adapter.md#获取事件通用信息)中,我们了解了事件基类能够提供的通用信息。同时,[事件响应器操作](../appendices/session-control.mdx#更多事件响应器操作)也为我们提供了基本的用户交互方式。使用这些方法,可以让我们的插件运行在任何平台上。例如,一个简单的命令处理插件:
-
-```python {5,11}
-from nonebot import on_command
-from nonebot.adapters import Event
-
-async def is_blacklisted(event: Event) -> bool:
- return event.get_user_id() not in BLACKLIST
-
-weather = on_command("天气", rule=is_blacklisted, priority=10, block=True)
-
-@weather.handle()
-async def handle_function():
- await weather.finish("今天的天气是...")
-```
-
-由于此插件仅使用了事件通用信息和事件响应器操作的纯文本交互方式,这些方法不使用特定平台的信息或接口,因此是原生跨平台的,并不需要额外处理。但在一些较为复杂的需求下,例如发送图片消息时,并非所有平台都具有统一的接口,因此基类便无能为力,我们需要引入特定平台的适配器了。
-
-## 基于重载的跨平台
-
-重载是 NoneBot 跨平台操作的核心,在[事件类型与重载](../appendices/overload.md#重载)一节中,我们初步了解了如何通过类型注解来实现针对不同平台事件的处理方式。在[依赖注入](../advanced/dependency.mdx)一节中,我们又对依赖注入的使用方法进行了详细的介绍。结合这两节内容,我们可以实现更复杂的跨平台操作。
-
-### 处理近似事件
-
-对于一系列**差异不大**的事件,我们往往具有相同的处理逻辑。这时,我们不希望将相同的逻辑编写两遍,而应该复用代码,以实现在同一个事件处理函数中处理多个近似事件。我们可以使用[事件重载](../advanced/dependency.mdx#event)的特性来实现这一功能。例如:
-
-
-
-
-```python
-from nonebot import on_command
-from nonebot.adapters import Message
-from nonebot.params import CommandArg
-from nonebot.adapters.onebot.v11 import MessageEvent as OnebotV11MessageEvent
-from nonebot.adapters.onebot.v12 import MessageEvent as OnebotV12MessageEvent
-
-echo = on_command("echo", priority=10, block=True)
-
-@echo.handle()
-async def handle_function(event: OnebotV11MessageEvent | OnebotV12MessageEvent, args: Message = CommandArg()):
- await echo.finish(args)
-```
-
-
-
-
-```python
-from typing import Union
-
-from nonebot import on_command
-from nonebot.adapters import Message
-from nonebot.params import CommandArg
-from nonebot.adapters.onebot.v11 import MessageEvent as OnebotV11MessageEvent
-from nonebot.adapters.onebot.v12 import MessageEvent as OnebotV12MessageEvent
-
-echo = on_command("echo", priority=10, block=True)
-
-@echo.handle()
-async def handle_function(event: Union[OnebotV11MessageEvent, OnebotV12MessageEvent], args: Message = CommandArg()):
- await echo.finish(args)
-```
-
-
-
-
-### 在依赖注入中使用重载
-
-NoneBot 依赖注入系统提供了自定义子依赖的方法,子依赖的类型同样会影响到事件处理函数的重载行为。例如:
-
-```python
-from datetime import datetime
-
-from nonebot import on_command
-from nonebot.adapters.console import MessageEvent
-
-echo = on_command("echo", priority=10, block=True)
-
-def get_event_time(event: MessageEvent):
- return event.time
-
-# 处理控制台消息事件
-@echo.handle()
-async def handle_function(time: datetime = Depends(get_event_time)):
- await echo.finish(time.strftime("%Y-%m-%d %H:%M:%S"))
-```
-
-示例中 ,我们为 `handle_function` 事件处理函数注入了自定义的 `get_event_time` 子依赖,而此子依赖注入参数为 Console 适配器的 `MessageEvent`。因此 `handle_function` 仅会响应 Console 适配器的 `MessageEvent` ,而不能响应其他事件。
-
-### 处理多平台事件
-
-不同平台的事件之间,往往存在着极大的差异性。为了满足我们插件的跨平台运行,通常我们需要抽离业务逻辑,以保证代码的复用性。一个合理的做法是,在事件响应器的处理流程中,首先先针对不同平台的事件分别进行处理,提取出核心业务逻辑所需要的信息;然后再将这些信息传递给业务逻辑处理函数;最后将业务逻辑的输出以各平台合适的方式返回给用户。也就是说,与平台绑定的处理部分应该与平台无关部分尽量分离。例如:
-
-```python
-import inspect
-
-from nonebot import on_command
-from nonebot.typing import T_State
-from nonebot.matcher import Matcher
-from nonebot.adapters import Message
-from nonebot.params import CommandArg, ArgPlainText
-from nonebot.adapters.console import Bot as ConsoleBot
-from nonebot.adapters.onebot.v11 import Bot as OnebotBot
-from nonebot.adapters.console import MessageSegment as ConsoleMessageSegment
-
-weather = on_command("天气", priority=10, block=True)
-
-@weather.handle()
-async def handle_function(matcher: Matcher, args: Message = CommandArg()):
- if args.extract_plain_text():
- matcher.set_arg("location", args)
-
-
-async def get_weather(state: T_State, location: str = ArgPlainText()):
- if location not in ["北京", "上海", "广州", "深圳"]:
- await weather.reject(f"你想查询的城市 {location} 暂不支持,请重新输入!")
-
- state["weather"] = "⛅ 多云 20℃~24℃"
-
-
-# 处理控制台询问
-@weather.got(
- "location",
- prompt=ConsoleMessageSegment.emoji("question") + "请输入地名",
- parameterless=[Depends(get_weather)],
-)
-async def handle_console(bot: ConsoleBot):
- pass
-
-# 处理 OneBot 询问
-@weather.got(
- "location",
- prompt="请输入地名",
- parameterless=[Depends(get_weather)],
-)
-async def handle_onebot(bot: OnebotBot):
- pass
-
-# 通过依赖注入或事件处理函数来进行业务逻辑处理
-
-# 处理控制台回复
-@weather.handle()
-async def handle_console_reply(bot: ConsoleBot, state: T_State, location: str = ArgPlainText()):
- await weather.send(
- ConsoleMessageSegment.markdown(
- inspect.cleandoc(
- f"""
- # {location}
-
- - 今天
-
- {state['weather']}
- """
- )
- )
- )
-
-# 处理 OneBot 回复
-@weather.handle()
-async def handle_onebot_reply(bot: OnebotBot, state: T_State, location: str = ArgPlainText()):
- await weather.send(f"今天{location}的天气是{state['weather']}")
-```
-
-:::tip 提示
-NoneBot 社区中有一些插件,例如[all4one](https://github.com/nonepkg/nonebot-plugin-all4one)、[send-anything-anywhere](https://github.com/felinae98/nonebot-plugin-send-anything-anywhere),可以帮助你更好地处理跨平台功能,包括事件处理和消息发送等。
-:::
diff --git a/website/versioned_docs/version-2.4.1/best-practice/scheduler.md b/website/versioned_docs/version-2.4.1/best-practice/scheduler.md
deleted file mode 100644
index 229b27abba19..000000000000
--- a/website/versioned_docs/version-2.4.1/best-practice/scheduler.md
+++ /dev/null
@@ -1,96 +0,0 @@
----
-sidebar_position: 0
-description: 定时执行任务
----
-
-# 定时任务
-
-[APScheduler](https://apscheduler.readthedocs.io/en/3.x/) (Advanced Python Scheduler) 是一个 Python 第三方库,其强大的定时任务功能被广泛应用于各个场景。在 NoneBot 中,定时任务作为一个额外功能,依赖于基于 APScheduler 开发的 [`nonebot-plugin-apscheduler`](https://github.com/nonebot/plugin-apscheduler) 插件进行支持。
-
-## 安装插件
-
-在使用前请先安装 `nonebot-plugin-apscheduler` 插件至项目环境中,可参考[获取商店插件](../tutorial/store.mdx#安装插件)来了解并选择安装插件的方式。如:
-
-在**项目目录**下执行以下命令:
-
-```bash
-nb plugin install nonebot-plugin-apscheduler
-```
-
-## 使用插件
-
-`nonebot-plugin-apscheduler` 本质上是对 [APScheduler](https://apscheduler.readthedocs.io/en/3.x/) 进行了封装以适用于 NoneBot 开发,因此其使用方式与 APScheduler 本身并无显著区别。在此我们会简要介绍其调用方法,更多的使用方面的功能请参考[APScheduler 官方文档](https://apscheduler.readthedocs.io/en/3.x/userguide.html)。
-
-### 导入调度器
-
-由于 `nonebot_plugin_apscheduler` 作为插件,因此需要在使用前对其进行**加载**并**导入**其中的 `scheduler` 调度器来创建定时任务。使用 `require` 方法可轻松完成这一过程,可参考 [跨插件访问](../advanced/requiring.md) 一节进行了解。
-
-```python
-from nonebot import require
-
-require("nonebot_plugin_apscheduler")
-
-from nonebot_plugin_apscheduler import scheduler
-```
-
-### 添加定时任务
-
-在 [APScheduler 官方文档](https://apscheduler.readthedocs.io/en/3.x/userguide.html#adding-jobs) 中提供了以下两种直接添加任务的方式:
-
-```python
-from nonebot import require
-
-require("nonebot_plugin_apscheduler")
-
-from nonebot_plugin_apscheduler import scheduler
-
-# 基于装饰器的方式
-@scheduler.scheduled_job("cron", hour="*/2", id="job_0", args=[1], kwargs={arg2: 2})
-async def run_every_2_hour(arg1: int, arg2: int):
- pass
-
-# 基于 add_job 方法的方式
-def run_every_day(arg1: int, arg2: int):
- pass
-
-scheduler.add_job(
- run_every_day, "interval", days=1, id="job_1", args=[1], kwargs={arg2: 2}
-)
-```
-
-:::caution 注意
-由于 APScheduler 的定时任务并不是**由事件响应器所触发的事件**,因此其任务函数无法同[事件处理函数](../tutorial/handler.mdx#事件处理函数)一样通过[依赖注入](../tutorial/event-data.mdx#认识依赖注入)获取上下文信息,也无法通过事件响应器对象的方法进行任何操作,因此我们需要使用[调用平台 API](../appendices/api-calling.mdx#调用平台-api)的方式来获取信息或收发消息。
-
-相对于事件处理依赖而言,编写定时任务更像是编写普通的函数,需要我们自行获取信息以及发送信息,请**不要**将事件处理依赖的特殊语法用于定时任务!
-:::
-
-关于 APScheduler 的更多使用方法,可以参考 [APScheduler 官方文档](https://apscheduler.readthedocs.io/en/3.x/index.html) 进行了解。
-
-### 配置项
-
-#### apscheduler_autostart
-
-- **类型**: `bool`
-- **默认值**: `True`
-
-是否自动启动 `scheduler` ,若不启动需要自行调用 `scheduler.start()`。
-
-#### apscheduler_log_level
-
-- **类型**: `int`
-- **默认值**: `30`
-
-apscheduler 输出的日志等级
-
-- `WARNING` = `30` (默认)
-- `INFO` = `20`
-- `DEBUG` = `10` (只有在开启 nonebot 的 debug 模式才会显示 debug 日志)
-
-#### apscheduler_config
-
-- **类型**: `dict`
-- **默认值**: `{ "apscheduler.timezone": "Asia/Shanghai" }`
-
-`apscheduler` 的相关配置。参考[配置调度器](https://apscheduler.readthedocs.io/en/latest/userguide.html#scheduler-config), [配置参数](https://apscheduler.readthedocs.io/en/latest/modules/schedulers/base.html#apscheduler.schedulers.base.BaseScheduler)
-
-配置需要包含 `apscheduler.` 作为前缀,例如 `apscheduler.timezone`。
diff --git a/website/versioned_docs/version-2.4.1/best-practice/testing/README.mdx b/website/versioned_docs/version-2.4.1/best-practice/testing/README.mdx
deleted file mode 100644
index 12853d960030..000000000000
--- a/website/versioned_docs/version-2.4.1/best-practice/testing/README.mdx
+++ /dev/null
@@ -1,240 +0,0 @@
----
-sidebar_position: 1
-description: 使用 NoneBug 进行单元测试
-
-slug: /best-practice/testing/
----
-
-# 配置与测试事件响应器
-
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-
-> 在计算机编程中,单元测试(Unit Testing)又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。
-
-为了保证代码的正确运行,我们不仅需要对错误进行跟踪,还需要对代码进行正确性检验,也就是测试。NoneBot 提供了一个测试工具——NoneBug,它是一个 [pytest](https://docs.pytest.org/en/stable/) 插件,可以帮助我们便捷地进行单元测试。
-
-:::tip 提示
-建议在阅读本文档前先阅读 [pytest 官方文档](https://docs.pytest.org/en/stable/)来了解 pytest 的相关术语和基本用法。
-:::
-
-## 安装 NoneBug
-
-在**项目目录**下激活虚拟环境后运行以下命令安装 NoneBug:
-
-
-
-
-```bash
-poetry add nonebug -G test
-```
-
-
-
-
-```bash
-pdm add nonebug -dG test
-```
-
-
-
-
-```bash
-pip install nonebug
-```
-
-
-
-
-要运行 NoneBug 测试,还需要额外安装 pytest 异步插件 `pytest-asyncio` 或 `anyio` 以支持异步测试。文档中,我们以 `pytest-asyncio` 为例:
-
-
-
-
-```bash
-poetry add pytest-asyncio -G test
-```
-
-
-
-
-```bash
-pdm add pytest-asyncio -dG test
-```
-
-
-
-
-```bash
-pip install pytest-asyncio
-```
-
-
-
-
-## 配置测试
-
-在开始测试之前,我们需要对测试进行一些配置,以正确启动我们的机器人。
-
-首先我们需要配置 pytest-asyncio,在 `pyproject.toml` 的 pytest 配置部分添加:
-
-```toml
-[tool.pytest.ini_options]
-asyncio_mode = "auto"
-asyncio_default_fixture_loop_scope = "session"
-```
-
-然后,我们在 `tests` 目录下新建 `conftest.py` 文件,添加以下内容:
-
-```python title=tests/conftest.py
-import pytest
-import nonebot
-from pytest_asyncio import is_async_test
-# 导入适配器
-from nonebot.adapters.console import Adapter as ConsoleAdapter
-
-def pytest_collection_modifyitems(items: list[pytest.Item]):
- pytest_asyncio_tests = (item for item in items if is_async_test(item))
- session_scope_marker = pytest.mark.asyncio(loop_scope="session")
- for async_test in pytest_asyncio_tests:
- async_test.add_marker(session_scope_marker, append=False)
-
-@pytest.fixture(scope="session", autouse=True)
-async def after_nonebot_init(after_nonebot_init: None):
- # 加载适配器
- driver = nonebot.get_driver()
- driver.register_adapter(ConsoleAdapter)
-
- # 加载插件
- nonebot.load_from_toml("pyproject.toml")
-```
-
-这样,我们就可以在测试中使用机器人的插件了。通常,我们不需要自行初始化 NoneBot,NoneBug 已经为我们运行了 `nonebot.init()`。如果需要自定义 NoneBot 初始化的参数,我们可以在 `conftest.py` 中添加 `pytest_configure` 钩子函数。例如,我们可以修改 NoneBot 配置环境为 `test` 并从环境变量中输入配置:
-
-```python {4,6,8-10} title=tests/conftest.py
-import os
-
-import pytest
-from nonebug import NONEBOT_INIT_KWARGS
-
-os.environ["ENVIRONMENT"] = "test"
-
-def pytest_configure(config: pytest.Config):
- config.stash[NONEBOT_INIT_KWARGS] = {"secret": os.getenv("INPUT_SECRET")}
-```
-
-NoneBug 默认也会为我们管理 lifespan 的 startup 与 shutdown。如果不希望 NoneBug 管理 lifespan,你可以在 `pytest_configure` 里添加以下配置:
-
-```python
-import pytest
-from nonebug import NONEBOT_START_LIFESPAN
-
-def pytest_configure(config: pytest.Config):
- config.stash[NONEBOT_START_LIFESPAN] = False
-```
-
-## 编写插件测试
-
-在配置完成插件加载后,我们就可以在测试中使用插件了。NoneBug 通过 pytest fixture `app` 提供各种测试方法,我们可以在测试中使用它来测试插件。现在,我们创建一个测试脚本来测试[深入指南](../../appendices/session-control.mdx)中编写的天气插件。首先,我们先要导入我们需要的模块:
-
-
- 插件示例
-
-```python title=weather/__init__.py
-from nonebot import on_command
-from nonebot.rule import to_me
-from nonebot.matcher import Matcher
-from nonebot.adapters import Message
-from nonebot.params import CommandArg, ArgPlainText
-
-weather = on_command("天气", rule=to_me(), aliases={"weather", "天气预报"})
-
-@weather.handle()
-async def handle_function(matcher: Matcher, args: Message = CommandArg()):
- if args.extract_plain_text():
- matcher.set_arg("location", args)
-
-@weather.got("location", prompt="请输入地名")
-async def got_location(location: str = ArgPlainText()):
- if location not in ["北京", "上海", "广州", "深圳"]:
- await weather.reject(f"你想查询的城市 {location} 暂不支持,请重新输入!")
- await weather.finish(f"今天{location}的天气是...")
-```
-
-
-
-```python {4,5,9,11-16} title=tests/test_weather.py
-from datetime import datetime
-
-import pytest
-from nonebug import App
-from nonebot.adapters.console import User, Message, MessageEvent
-
-@pytest.mark.asyncio
-async def test_weather(app: App):
- from awesome_bot.plugins.weather import weather
-
- event = MessageEvent(
- time=datetime.now(),
- self_id="test",
- message=Message("/天气 北京"),
- user=User(id="user"),
- )
-```
-
-在上面的代码中,我们引入了 NoneBug 的测试 `App` 对象,以及必要的适配器消息与事件定义等。在测试函数 `test_weather` 中,我们导入了要进行测试的事件响应器 `weather`。请注意,由于需要等待 NoneBot 初始化并加载插件完毕,插件内容必须在**测试函数内部**进行导入。然后,我们创建了一个 `MessageEvent` 事件对象,它模拟了一个用户发送了 `/天气 北京` 的消息。接下来,我们使用 `app.test_matcher` 方法来测试 `weather` 事件响应器:
-
-```python {11-15} title=tests/test_weather.py
-@pytest.mark.asyncio
-async def test_weather(app: App):
- from awesome_bot.plugins.weather import weather
-
- event = MessageEvent(
- time=datetime.now(),
- self_id="test",
- message=Message("/天气 北京"),
- user=User(id="user"),
- )
- async with app.test_matcher(weather) as ctx:
- bot = ctx.create_bot()
- ctx.receive_event(bot, event)
- ctx.should_call_send(event, "今天北京的天气是...", result=None)
- ctx.should_finished(weather)
-```
-
-这里我们使用 `async with` 语句并通过参数指定要测试的事件响应器 `weather` 来进入测试上下文。在测试上下文中,我们可以使用 `ctx.create_bot` 方法创建一个虚拟的机器人实例,并使用 `ctx.receive_event` 方法来模拟机器人接收到消息事件。然后,我们就可以定义预期行为来测试机器人是否正确运行。在上面的代码中,我们使用 `ctx.should_call_send` 方法来断言机器人应该发送 `今天北京的天气是...` 这条消息,并且将发送函数的调用结果作为第三个参数返回给事件处理函数。如果断言失败,测试将会不通过。我们也可以使用 `ctx.should_finished` 方法来断言机器人应该结束会话。
-
-为了测试更复杂的情况,我们可以为添加更多的测试用例。例如,我们可以测试用户输入了一个不支持的地名时机器人的反应:
-
-```python {17-21,23-26} title=tests/test_weather.py
-def make_event(message: str = "") -> MessageEvent:
- return MessageEvent(
- time=datetime.now(),
- self_id="test",
- message=Message(message),
- user=User(id="user"),
- )
-
-@pytest.mark.asyncio
-async def test_weather(app: App):
- from awesome_bot.plugins.weather import weather
-
- async with app.test_matcher(weather) as ctx:
- ... # 省略前面的测试用例
-
- async with app.test_matcher(weather) as ctx:
- bot = ctx.create_bot()
- event = make_event("/天气 南京")
- ctx.receive_event(bot, event)
- ctx.should_call_send(event, "你想查询的城市 南京 暂不支持,请重新输入!", result=None)
- ctx.should_rejected(weather)
-
- event = make_event("北京")
- ctx.receive_event(bot, event)
- ctx.should_call_send(event, "今天北京的天气是...", result=None)
- ctx.should_finished(weather)
-```
-
-在上面的代码中,我们使用 `ctx.should_rejected` 来断言机器人应该请求用户重新输入。然后,我们再次使用 `ctx.receive_event` 方法来模拟用户回复了 `北京`,并使用 `ctx.should_finished` 来断言机器人应该结束会话。
-
-更多的 NoneBug 用法将在后续章节中介绍。
diff --git a/website/versioned_docs/version-2.4.1/best-practice/testing/_category_.json b/website/versioned_docs/version-2.4.1/best-practice/testing/_category_.json
deleted file mode 100644
index d315ef117497..000000000000
--- a/website/versioned_docs/version-2.4.1/best-practice/testing/_category_.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "label": "单元测试",
- "position": 5
-}
diff --git a/website/versioned_docs/version-2.4.1/best-practice/testing/behavior.mdx b/website/versioned_docs/version-2.4.1/best-practice/testing/behavior.mdx
deleted file mode 100644
index 37d55c136c4f..000000000000
--- a/website/versioned_docs/version-2.4.1/best-practice/testing/behavior.mdx
+++ /dev/null
@@ -1,290 +0,0 @@
----
-sidebar_position: 2
-description: 测试事件响应、平台接口调用和会话控制
----
-
-# 测试事件响应与会话操作
-
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-
-在 NoneBot 接收到事件时,事件响应器根据优先级依次通过权限、响应规则来判断当前事件是否应该触发。事件响应流程中,机器人可能会通过 `send` 发送消息或者调用平台接口来执行预期的操作。因此,我们需要对这两种操作进行单元测试。
-
-在上一节中,我们对单个事件响应器进行了简单测试。但是在实际场景中,机器人可能定义了多个事件响应器,由于优先级和响应规则的存在,预期的事件响应器可能并不会被触发。NoneBug 支持同时测试多个事件响应器,以此来测试机器人的整体行为。
-
-## 测试事件响应
-
-NoneBug 提供了六种定义 `Rule` 和 `Permission` 预期行为的方法:
-
-- `should_pass_rule`
-- `should_not_pass_rule`
-- `should_ignore_rule`
-- `should_pass_permission`
-- `should_not_pass_permission`
-- `should_ignore_permission`
-
-:::tip 提示
-事件响应器类型的检查属于 `Permission` 的一部分,因此可以通过 `should_pass_permission` 和 `should_not_pass_permission` 方法来断言事件响应器类型的检查。
-:::
-
-下面我们根据插件示例来测试事件响应行为,我们首先定义两个事件响应器作为测试的对象:
-
-```python title=example.py
-from nonebot import on_command
-
-def never_pass():
- return False
-
-foo = on_command("foo")
-bar = on_command("bar", permission=never_pass)
-```
-
-在这两个事件响应器中,`foo` 当收到 `/foo` 消息时会执行,而 `bar` 则不会执行。我们使用 NoneBug 来测试它们:
-
-
-
-
-```python {21,22,28,29} title=tests/test_example.py
-from datetime import datetime
-
-import pytest
-from nonebug import App
-from nonebot.adapters.console import User, Message, MessageEvent
-
-def make_event(message: str = "") -> MessageEvent:
- return MessageEvent(
- time=datetime.now(),
- self_id="test",
- message=Message(message),
- user=User(id="user"),
- )
-
-@pytest.mark.asyncio
-async def test_example(app: App):
- from awesome_bot.plugins.example import foo, bar
-
- async with app.test_matcher(foo) as ctx:
- bot = ctx.create_bot()
- event = make_event("/foo")
- ctx.receive_event(bot, event)
- ctx.should_pass_rule()
- ctx.should_pass_permission()
-
- async with app.test_matcher(bar) as ctx:
- bot = ctx.create_bot()
- event = make_event("/foo")
- ctx.receive_event(bot, event)
- ctx.should_not_pass_rule()
- ctx.should_not_pass_permission()
-```
-
-在上面的代码中,我们分别对 `foo` 和 `bar` 事件响应器进行响应测试。我们使用 `ctx.should_pass_rule` 和 `ctx.should_pass_permission` 断言 `foo` 事件响应器应该被触发,使用 `ctx.should_not_pass_rule` 和 `ctx.should_not_pass_permission` 断言 `bar` 事件响应器应该被忽略。
-
-
-
-
-```python title=tests/test_example.py
-from datetime import datetime
-
-import pytest
-from nonebug import App
-from nonebot.adapters.console import User, Message, MessageEvent
-
-def make_event(message: str = "") -> MessageEvent:
- return MessageEvent(
- time=datetime.now(),
- self_id="test",
- message=Message(message),
- user=User(id="user"),
- )
-
-@pytest.mark.asyncio
-async def test_example(app: App):
- from awesome_bot.plugins.example import foo, bar
-
- async with app.test_matcher() as ctx:
- bot = ctx.create_bot()
- event = make_event("/foo")
- ctx.receive_event(bot, event)
- ctx.should_pass_rule(foo)
- ctx.should_pass_permission(foo)
- ctx.should_not_pass_rule(bar)
- ctx.should_not_pass_permission(bar)
-```
-
-在上面的代码中,我们对 `foo` 和 `bar` 事件响应器一起进行响应测试。我们使用 `ctx.should_pass_rule` 和 `ctx.should_pass_permission` 断言 `foo` 事件响应器应该被触发,使用 `ctx.should_not_pass_rule` 和 `ctx.should_not_pass_permission` 断言 `bar` 事件响应器应该被忽略。通过参数,我们可以指定断言的事件响应器。
-
-
-
-
-当然,如果需要忽略某个事件响应器的响应规则和权限检查,强行进入响应流程,我们可以使用 `should_ignore_rule` 和 `should_ignore_permission` 方法:
-
-```python {21,22} title=tests/test_example.py
-from datetime import datetime
-
-import pytest
-from nonebug import App
-from nonebot.adapters.console import User, Message, MessageEvent
-
-def make_event(message: str = "") -> MessageEvent:
- return MessageEvent(
- time=datetime.now(),
- self_id="test",
- message=Message(message),
- user=User(id="user"),
- )
-
-@pytest.mark.asyncio
-async def test_example(app: App):
- from awesome_bot.plugins.example import foo, bar
-
- async with app.test_matcher(bar) as ctx:
- bot = ctx.create_bot()
- event = make_event("/foo")
- ctx.receive_event(bot, event)
- ctx.should_ignore_rule(bar)
- ctx.should_ignore_permission(bar)
-```
-
-在忽略了响应规则和权限检查之后,就会进入 `bar` 事件响应器的响应流程。
-
-## 测试平台接口使用
-
-上一节的示例插件测试中,我们已经尝试了测试插件对事件的消息回复。通常情况下,事件处理流程中对平台接口的使用会通过事件响应器操作或者调用平台 API 两种途径进行。针对这两种途径,NoneBug 分别提供了 `ctx.should_call_send` 和 `ctx.should_call_api` 方法来测试平台接口的使用情况。
-
-1. `should_call_send`
-
- 定义事件响应器预期发送的消息,即通过[事件响应器操作 send](../../appendices/session-control.mdx#send)进行的操作。`should_call_send` 有四个参数:
- - `event`:回复的目标事件。
- - `message`:预期的消息对象,可以是 `str`、`Message` 或 `MessageSegment`。
- - `result`:send 的返回值,将会返回给插件。
- - `bot`(可选):发送消息的 bot 对象。
- - `**kwargs`:send 方法的额外参数。
-
-2. `should_call_api`
- 定义事件响应器预期调用的平台 API 接口,即通过[调用平台 API](../../appendices/api-calling.mdx#调用平台-api)进行的操作。`should_call_api` 有四个参数:
- - `api`:API 名称。
- - `data`:预期的请求数据。
- - `result`:call_api 的返回值,将会返回给插件。
- - `adapter`(可选):调用 API 的平台适配器对象。
- - `**kwargs`:call_api 方法的额外参数。
-
-下面是一个使用 `should_call_send` 和 `should_call_api` 方法的示例:
-
-我们先定义一个测试插件,在响应流程中向用户发送一条消息并调用 `Console` 适配器的 `bell` API。
-
-```python {8,9} title=example.py
-from nonebot import on_command
-from nonebot.adapters.console import Bot
-
-foo = on_command("foo")
-
-@foo.handle()
-async def _(bot: Bot):
- await foo.send("message")
- await bot.bell()
-```
-
-然后我们对该插件进行测试:
-
-```python title=tests/test_example.py
-from datetime import datetime
-
-import pytest
-import nonebot
-from nonebug import App
-from nonebot.adapters.console import Bot, User, Adapter, Message, MessageEvent
-
-def make_event(message: str = "") -> MessageEvent:
- return MessageEvent(
- time=datetime.now(),
- self_id="test",
- message=Message(message),
- user=User(id="user"),
- )
-
-@pytest.mark.asyncio
-async def test_example(app: App):
- from awesome_bot.plugins.example import foo
-
- async with app.test_matcher(foo) as ctx:
- # highlight-start
- adapter = nonebot.get_adapter(Adapter)
- bot = ctx.create_bot(base=Bot, adapter=adapter)
- # highlight-end
- event = make_event("/foo")
- ctx.receive_event(bot, event)
- # highlight-start
- ctx.should_call_send(event, "message", result=None, bot=bot)
- ctx.should_call_api("bell", {}, result=None, adapter=adapter)
- # highlight-end
-```
-
-请注意,对于在依赖注入中使用了非基类对象的情况,我们需要在 `create_bot` 方法中指定 `base` 和 `adapter` 参数,确保不会因为重载功能而出现非预期情况。
-
-## 测试会话控制
-
-在[会话控制](../../appendices/session-control.mdx)一节中,我们介绍了如何使用事件响应器操作来实现对用户的交互式会话。在上一节的示例插件测试中,我们其实已经使用了 `ctx.should_finished` 来断言会话结束。NoneBug 针对各种流程控制操作分别提供了相应的方法来定义预期的会话处理行为。它们分别是:
-
-- `should_finished`:断言会话结束,对应 `matcher.finish` 操作。
-- `should_rejected`:断言会话等待用户输入并重新执行当前事件处理函数,对应 `matcher.reject` 系列操作。
-- `should_paused`: 断言会话等待用户输入并执行下一个事件处理函数,对应 `matcher.pause` 操作。
-
-我们仅需在测试用例中的正确位置调用这些方法,就可以断言会话的预期行为。例如:
-
-```python title=example.py
-from nonebot import on_command
-from nonebot.typing import T_State
-
-foo = on_command("foo")
-
-@foo.got("key", prompt="请输入密码")
-async def _(state: T_State, key: str = ArgPlainText()):
- if key != "some password":
- try_count = state.get("try_count", 1)
- if try_count >= 3:
- await foo.finish("密码错误次数过多")
- else:
- state["try_count"] = try_count + 1
- await foo.reject("密码错误,请重新输入")
- await foo.finish("密码正确")
-```
-
-```python title=tests/test_example.py
-from datetime import datetime
-
-import pytest
-from nonebug import App
-from nonebot.adapters.console import User, Message, MessageEvent
-
-def make_event(message: str = "") -> MessageEvent:
- return MessageEvent(
- time=datetime.now(),
- self_id="test",
- message=Message(message),
- user=User(id="user"),
- )
-
-@pytest.mark.asyncio
-async def test_example(app: App):
- from awesome_bot.plugins.example import foo
-
- async with app.test_matcher(foo) as ctx:
- bot = ctx.create_bot()
- event = make_event("/foo")
- ctx.receive_event(bot, event)
- ctx.should_call_send(event, "请输入密码", result=None)
- ctx.should_rejected(foo)
- event = make_event("wrong password")
- ctx.receive_event(bot, event)
- ctx.should_call_send(event, "密码错误,请重新输入", result=None)
- ctx.should_rejected(foo)
- event = make_event("wrong password")
- ctx.receive_event(bot, event)
- ctx.should_call_send(event, "密码错误,请重新输入", result=None)
- ctx.should_rejected(foo)
- event = make_event("wrong password")
- ctx.receive_event(bot, event)
- ctx.should_call_send(event, "密码错误次数过多", result=None)
- ctx.should_finished(foo)
-```
diff --git a/website/versioned_docs/version-2.4.1/best-practice/testing/mock-network.md b/website/versioned_docs/version-2.4.1/best-practice/testing/mock-network.md
deleted file mode 100644
index 8ed56b5c1a8d..000000000000
--- a/website/versioned_docs/version-2.4.1/best-practice/testing/mock-network.md
+++ /dev/null
@@ -1,96 +0,0 @@
----
-sidebar_position: 3
-description: 模拟网络通信以进行测试
----
-
-# 模拟网络通信
-
-NoneBot 驱动器提供了多种方法来帮助适配器进行网络通信,主要包括客户端和服务端两种类型。模拟网络通信可以帮助我们更加接近实际机器人应用场景,进行更加真实的集成测试。同时,通过这种途径,我们还可以完成对适配器的测试。
-
-NoneBot 中的网络通信主要包括以下几种:
-
-- HTTP 服务端(WebHook)
-- WebSocket 服务端
-- HTTP 客户端
-- WebSocket 客户端
-
-下面我们将分别介绍如何使用 NoneBug 来模拟这几种通信方式。
-
-## 测试 HTTP 服务端
-
-当 NoneBot 作为 ASGI 服务端应用时,我们可以定义一系列的路由来处理 HTTP 请求,适配器同样也可以通过定义路由来响应机器人相关的网络通信。下面假设我们使用了一个适配器 `fake` ,它定义了一个路由 `/fake/http` ,用于接收平台 WebHook 并处理。实际应用测试时,应将该路由地址替换为**真实适配器注册的路由地址**。
-
-我们首先需要获取测试用模拟客户端:
-
-```python {5,6} title=tests/test_http_server.py
-from nonebug import App
-
-@pytest.mark.asyncio
-async def test_http_server(app: App):
- async with app.test_server() as ctx:
- client = ctx.get_client()
-```
-
-默认情况下,`app.test_server()` 会通过 `nonebot.get_asgi` 获取测试对象,我们也可以通过参数指定 ASGI 应用:
-
-```python
-async with app.test_server(asgi=asgi_app) as ctx:
- ...
-```
-
-获取到模拟客户端后,即可像 `requests`、`httpx` 等库类似的方法进行使用:
-
-```python {3,11-14,16} title=tests/test_http_server.py
-import nonebot
-from nonebug import App
-from nonebot.adapters.fake import Adapter
-
-@pytest.mark.asyncio
-async def test_http_server(app: App):
- adapter = nonebot.get_adapter(Adapter)
-
- async with app.test_server() as ctx:
- client = ctx.get_client()
- response = await client.post("/fake/http", json={"bot_id": "fake"})
- assert response.status_code == 200
- assert response.json() == {"status": "success"}
- assert "fake" in nonebot.get_bots()
-
- adapter.bot_disconnect(nonebot.get_bot("fake"))
-```
-
-在上面的测试中,我们向 `/fake/http` 发送了一个模拟 POST 请求,适配器将会对该请求进行处理,我们可以通过检查请求返回是否正确、Bot 对象是否创建等途径来验证机器人是否正确运行。在完成测试后,我们通常需要对 Bot 对象进行清理,以避免对其他测试产生影响。
-
-## 测试 WebSocket 服务端
-
-当 NoneBot 作为 ASGI 服务端应用时,我们还可以定义一系列的路由来处理 WebSocket 通信。下面假设我们使用了一个适配器 `fake` ,它定义了一个路由 `/fake/ws` ,用于处理平台 WebSocket 连接信息。实际应用测试时,应将该路由地址替换为**真实适配器注册的路由地址**。
-
-我们同样需要通过 `app.test_server()` 获取测试用模拟客户端,这里就不再赘述。在获取到模拟客户端后,我们可以通过 `client.websocket_connect` 方法来模拟 WebSocket 连接:
-
-```python {3,11-15} title=tests/test_ws_server.py
-import nonebot
-from nonebug import App
-from nonebot.adapters.fake import Adapter
-
-@pytest.mark.asyncio
-async def test_ws_server(app: App):
- adapter = nonebot.get_adapter(Adapter)
-
- async with app.test_server() as ctx:
- client = ctx.get_client()
- async with client.websocket_connect("/fake/ws") as ws:
- await ws.send_json({"bot_id": "fake"})
- response = await ws.receive_json()
- assert response == {"status": "success"}
- assert "fake" in nonebot.get_bots()
-```
-
-在上面的测试中,我们向 `/fake/ws` 进行了 WebSocket 模拟通信,通过发送消息与机器人进行交互,然后检查机器人发送的信息是否正确。
-
-## 测试 HTTP 客户端
-
-~~暂不支持~~
-
-## 测试 WebSocket 客户端
-
-~~暂不支持~~
diff --git a/website/versioned_docs/version-2.4.1/community/contact.md b/website/versioned_docs/version-2.4.1/community/contact.md
deleted file mode 100644
index c38fa19a4980..000000000000
--- a/website/versioned_docs/version-2.4.1/community/contact.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-sidebar-position: 0
-description: 遇到问题如何获取帮助
----
-
-# 参与讨论
-
-如果在安装或者开发 NoneBot 过程中遇到了任何问题,或者有新奇的点子,欢迎参与我们的社区讨论:
-
-1. 点击下方链接前往 GitHub,前往 Issues 页面,在 `New Issue` Template 中选择 `Question`
-
- NoneBot:[](https://github.com/nonebot/nonebot2)
-
-2. 通过 QQ 群(点击下方链接直达)
-
- [](https://jq.qq.com/?_wv=1027&k=5OFifDh)
-
-3. 通过 QQ 频道
-
- [](https://qun.qq.com/qqweb/qunpro/share?_wv=3&_wwv=128&appChannel=share&inviteCode=7b4a3&appChannel=share&businessType=9&from=246610&biz=ka)
-
-4. 通过 Discord 服务器(点击下方链接直达)
-
- [](https://discord.gg/VKtE6Gdc4h)
diff --git a/website/versioned_docs/version-2.4.1/community/contributing.md b/website/versioned_docs/version-2.4.1/community/contributing.md
deleted file mode 100644
index ff452818a833..000000000000
--- a/website/versioned_docs/version-2.4.1/community/contributing.md
+++ /dev/null
@@ -1,22 +0,0 @@
----
-sidebar-position: 1
-description: 如何为 NoneBot 贡献代码
----
-
-# 贡献指南
-
-## Code of Conduct
-
-请参阅 [Code of Conduct](https://github.com/nonebot/nonebot2/blob/master/CODE_OF_CONDUCT.md)。
-
-## 参与开发
-
-请参阅 [Contributing](https://github.com/nonebot/nonebot2/blob/master/CONTRIBUTING.md)。
-
-## 鸣谢
-
-感谢以下开发者对 NoneBot2 作出的贡献:
-
-
-
-
diff --git a/website/versioned_docs/version-2.4.1/developer/adapter-writing.md b/website/versioned_docs/version-2.4.1/developer/adapter-writing.md
deleted file mode 100644
index 133ab03ba931..000000000000
--- a/website/versioned_docs/version-2.4.1/developer/adapter-writing.md
+++ /dev/null
@@ -1,610 +0,0 @@
----
-sidebar_position: 1
-description: 编写适配器对接新的平台
----
-
-# 编写适配器
-
-在编写适配器之前,我们需要先了解[适配器的功能与组成](../advanced/adapter#适配器功能与组成),适配器通常由 `Adapter`、`Bot`、`Event` 和 `Message` 四个部分组成,在编写适配器时,我们需要继承 NoneBot 中的基类,并根据实际平台来编写每个部分功能。
-
-## 组织结构
-
-NoneBot 适配器项目通常以 `nonebot-adapter-{adapter-name}` 作为项目名,并以**命名空间包**的形式编写,即在 `nonebot/adapters/{adapter-name}` 目录中编写实际代码,例如:
-
-```tree
-📦 nonebot-adapter-{adapter-name}
-├── 📂 nonebot
-│ ├── 📂 adapters
-│ │ ├── 📂 {adapter-name}
-│ │ │ ├── 📜 __init__.py
-│ │ │ ├── 📜 adapter.py
-│ │ │ ├── 📜 bot.py
-│ │ │ ├── 📜 config.py
-│ │ │ ├── 📜 event.py
-│ │ │ └── 📜 message.py
-├── 📜 pyproject.toml
-└── 📜 README.md
-```
-
-:::tip 提示
-
-上述的项目结构仅作推荐,不做强制要求,保证实际可用性即可。
-
-:::
-
-### 使用 NB-CLI 创建项目
-
-我们可以使用脚手架快速创建项目:
-
-```shell
-nb adapter create
-```
-
-按照指引,输入适配器名称以及存储位置,即可创建一个带有基本结构的适配器项目。
-
-## 组成部分
-
-:::tip 提示
-
-本章节的代码中提到的 `Adapter`、`Bot`、`Event` 和 `Message` 等,均为下文中适配器所编写的类,而非 NoneBot 中的基类。
-
-:::
-
-### Log
-
-适配器在处理时通常需要打印日志,但直接使用 NoneBot 的默认 `logger` 不方便区分适配器输出和其它日志。因此我们可以使用 NoneBot 提供的 `logger_wrapper` 方法,自定义一个 `log` 函数用于快捷打印适配器日志:
-
-```python {3} title=log.py
-from nonebot.utils import logger_wrapper
-
-log = logger_wrapper("your_adapter_name")
-```
-
-这个 `log` 函数会在默认 `logger` 中添加适配器名称前缀,它接收三个参数:日志等级、日志内容以及可选的异常,具体用法如下:
-
-```python
-from .log import log
-
-log("DEBUG", "A DEBUG log.")
-log("INFO", "A INFO log.")
-
-try:
- ...
-except Exception as e:
- log("ERROR", "something error.", e)
-```
-
-### Config
-
-通常适配器需要一些配置项,例如平台连接密钥等。适配器的配置方法与[插件配置](../appendices/config#%E6%8F%92%E4%BB%B6%E9%85%8D%E7%BD%AE)类似,例如:
-
-```python title=config.py
-from pydantic import BaseModel
-
-class Config(BaseModel):
- xxx_id: str
- xxx_token: str
-```
-
-配置项的读取将在下方 [Adapter](#adapter) 中介绍。
-
-### Adapter
-
-Adapter 负责转换事件、调用接口,以及正确创建 Bot 对象并注册到 NoneBot 中。在编写平台相关内容之前,我们需要继承基类,并实现适配器的基本信息:
-
-```python {9,11,14,18} title=adapter.py
-from typing import Any
-from typing_extensions import override
-
-from nonebot.drivers import Driver
-from nonebot import get_plugin_config
-from nonebot.adapters import Adapter as BaseAdapter
-
-from .config import Config
-
-class Adapter(BaseAdapter):
- @override
- def __init__(self, driver: Driver, **kwargs: Any):
- super().__init__(driver, **kwargs)
- # 读取适配器所需的配置项
- self.adapter_config: Config = get_plugin_config(Config)
-
- @classmethod
- @override
- def get_name(cls) -> str:
- """适配器名称"""
- return "your_adapter_name"
-```
-
-#### 与平台交互
-
-NoneBot 提供了多种 [Driver](../advanced/driver) 来帮助适配器进行网络通信,主要分为客户端和服务端两种类型。我们需要**根据平台文档和特性**选择合适的通信方式,并编写相关方法用于初始化适配器,与平台建立连接和进行交互:
-
-##### 客户端通信方式
-
-```python {12,23,24} title=adapter.py
-import asyncio
-from typing_extensions import override
-
-from nonebot import get_plugin_config
-from nonebot.exception import WebSocketClosed
-from nonebot.drivers import Request, WebSocketClientMixin
-
-class Adapter(BaseAdapter):
- @override
- def __init__(self, driver: Driver, **kwargs: Any):
- super().__init__(driver, **kwargs)
- self.adapter_config: Config = get_plugin_config(Config)
- self.task: Optional[asyncio.Task] = None # 存储 ws 任务
- self.setup()
-
- def setup(self) -> None:
- if not isinstance(self.driver, WebSocketClientMixin):
- # 判断用户配置的Driver类型是否符合适配器要求,不符合时应抛出异常
- raise RuntimeError(
- f"Current driver {self.config.driver} doesn't support websocket client connections!"
- f"{self.get_name()} Adapter need a WebSocket Client Driver to work."
- )
- # 在 NoneBot 启动和关闭时进行相关操作
- self.driver.on_startup(self.startup)
- self.driver.on_shutdown(self.shutdown)
-
- async def startup(self) -> None:
- """定义启动时的操作,例如和平台建立连接"""
- self.task = asyncio.create_task(self._forward_ws()) # 建立 ws 连接
-
- async def _forward_ws(self):
- request = Request(
- method="GET",
- url="your_platform_websocket_url",
- headers={"token": "..."}, # 鉴权请求头
- )
- while True:
- try:
- async with self.websocket(request) as ws:
- try:
- # 处理 websocket
- ...
- except WebSocketClosed as e:
- log(
- "ERROR",
- "
WebSocket Closed",
- e,
- )
- except Exception as e:
- log(
- "ERROR",
- "
Error while process data from "
- "websocket platform_websocket_url. "
- "Trying to reconnect...",
- e,
- )
- finally:
- # 这里要断开 Bot 连接
- except Exception as e:
- # 尝试重连
- log(
- "ERROR",
- "
Error while setup websocket to "
- "platform_websocket_url. Trying to reconnect...",
- e,
- )
- await asyncio.sleep(3) # 重连间隔
-
- async def shutdown(self) -> None:
- """定义关闭时的操作,例如停止任务、断开连接"""
-
- # 断开 ws 连接
- if self.task is not None and not self.task.done():
- self.task.cancel()
-```
-
-##### 服务端通信方式
-
-```python {30,38} title=adapter.py
-from nonebot import get_plugin_config
-from nonebot.drivers import (
- Request,
- ASGIMixin,
- WebSocket,
- HTTPServerSetup,
- WebSocketServerSetup
-)
-
-class Adapter(BaseAdapter):
- @override
- def __init__(self, driver: Driver, **kwargs: Any):
- super().__init__(driver, **kwargs)
- self.adapter_config: Config = get_plugin_config(Config)
- self.setup()
-
- def setup(self) -> None:
- if not isinstance(self.driver, ASGIMixin):
- raise RuntimeError(
- f"Current driver {self.config.driver} doesn't support asgi server!"
- f"{self.get_name()} Adapter need a asgi server driver to work."
- )
- # 建立服务端路由
- # HTTP Webhook 路由
- http_setup = HTTPServerSetup(
- URL("your_webhook_url"), # 路由地址
- "POST", # 接收的方法
- "WEBHOOK name", # 路由名称
- self._handle_http, # 处理函数
- )
- self.setup_http_server(http_setup)
-
- # 反向 Websocket 路由
- ws_setup = WebSocketServerSetup(
- URL("your_websocket_url"), # 路由地址
- "WebSocket name", # 路由名称
- self._handle_ws, # 处理函数
- )
- self.setup_websocket_server(ws_setup)
-
-
- async def _handle_http(self, request: Request) -> Response:
- """HTTP 路由处理函数,只有一个类型为 Request 的参数,且返回值类型为 Response"""
- ...
- return Response(
- status_code=200, # 状态码
- headers={"something": "something"}, # 响应头
- content="xxx", # 响应内容
- )
-
- async def _handle_ws(self, websocket: WebSocket) -> Any:
- """WebSocket 路由处理函数,只有一个类型为 WebSocket 的参数"""
- ...
-```
-
-更多通信交互方式可以参考以下适配器:
-
-- [OneBot](https://github.com/nonebot/adapter-onebot/blob/master/nonebot/adapters/onebot/v11/adapter.py) - `WebSocket 客户端`、`WebSocket 服务端`、`HTTP WEBHOOK`、`HTTP POST`
-- [QQ](https://github.com/nonebot/adapter-qq/blob/master/nonebot/adapters/qq/adapter.py) - `WebSocket 服务端`、`HTTP WEBHOOK`
-- [Telegram](https://github.com/nonebot/adapter-telegram/blob/beta/nonebot/adapters/telegram/adapter.py) - `HTTP WEBHOOK`
-
-#### 建立 Bot 连接
-
-在与平台建立连接后,我们需要将 [Bot](#bot) 实例化,并调用适配器提供的的 `bot_connect` 方法告知 NoneBot 建立了 Bot 连接。在与平台断开连接或出现某些异常进行重连时,我们需要调用 `bot_disconnect` 方法告知 NoneBot 断开了 Bot 连接。
-
-```python {7,8,11} title=adapter.py
-from .bot import Bot
-
-class Adapter(BaseAdapter):
-
- def _handle_connect(self):
- bot_id = ... # 通过配置或者平台 API 等方式,获取到 Bot 的 ID
- bot = Bot(self, self_id=bot_id) # 实例化 Bot
- self.bot_connect(bot) # 建立 Bot 连接
-
- def _handle_disconnect(self):
- self.bot_disconnect(bot) # 断开 Bot 连接
-```
-
-#### 转换 Event 事件
-
-在接收到来自平台的事件数据后,我们需要将其转为适配器的 [Event](#event),并调用 Bot 的 `handle_event` 方法来让 Bot 对事件进行处理:
-
-```python title=adapter.py
-import asyncio
-from typing import Any, Dict
-
-from nonebot.compat import type_validate_python
-
-from .bot import Bot
-from .event import Event
-from .log import log
-
-class Adapter(BaseAdapter):
-
- @classmethod
- def payload_to_event(cls, payload: Dict[str, Any]) -> Event:
- """根据平台事件的特性,转换平台 payload 为具体 Event
-
- Event 模型继承自 pydantic.BaseModel,具体请参考 pydantic 文档
- """
-
- # 做一层异常处理,以应对平台事件数据的变更
- try:
- return type_validate_python(your_event_class, payload)
- except Exception as e:
- # 无法正常解析为具体 Event 时,给出日志提示
- log(
- "WARNING",
- f"Parse event error: {str(payload)}",
- )
- # 也可以尝试转为基础 Event 进行处理
- return type_validate_python(Event, payload)
-
-
- async def _forward(self, bot: Bot):
-
- payload: Dict[str, Any] # 接收到的事件数据
- event = self.payload_to_event(payload)
- # 让 bot 对事件进行处理
- asyncio.create_task(bot.handle_event(event))
-```
-
-#### 调用平台 API
-
-我们需要实现 `Adapter` 的 `_call_api` 方法,使开发者能够调用平台提供的 API。如果通过 WebSocket 通信可以通过 `send` 方法来发送数据,如果采用 HTTP 请求,则需要通过 NoneBot 提供的 `Request` 对象,调用 `driver` 的 `request` 方法来发送请求。
-
-```python {11} title=adapter.py
-from typing import Any
-from typing_extensions import override
-
-from nonebot.drivers import Request, WebSocket
-
-from .bot import Bot
-
-class Adapter(BaseAdapter):
-
- @override
- async def _call_api(self, bot: Bot, api: str, **data: Any) -> Any:
- log("DEBUG", f"Calling API
{api}") # 给予日志提示
- platform_data = your_handle_data_method(data) # 自行将数据转为平台所需要的格式
-
- # 采用 HTTP 请求的方式,需要构造一个 Request 对象
- request = Request(
- method="GET", # 请求方法
- url=api, # 接口地址
- headers=..., # 请求头,通常需要包含鉴权信息
- params=platform_data, # 自行处理数据的传输形式
- # json=platform_data,
- # data=platform_data,
- )
- # 发送请求,返回结果
- return await self.driver.request(request)
-
-
- # 采用 WebSocket 通信的方式,可以直接调用 send 方法发送数据
- # 通过某种方式获取到 bot 对应的 websocket 对象
- ws: WebSocket = your_get_websocket_method(bot.self_id)
-
- await ws.send_text(platform_data) # 发送 str 类型的数据
- await ws.send_bytes(platform_data) # 发送 bytes 类型的数据
- await ws.send(platform_data) # 是以上两种方式的合体
-
- # 接收并返回结果,同样的,也有 str 和 bytes 的区别
- return await ws.receive_text()
- return await ws.receive_bytes()
- return await ws.receive()
-```
-
-`调用平台 API` 实现方式具体可以参考以下适配器:
-
-Websocket:
-
-- [OneBot V11](https://github.com/nonebot/adapter-onebot/blob/54270edbbdb2a71332d744f90b1a3d7f4bf6463a/nonebot/adapters/onebot/v11/adapter.py#L167-L177)
-- [OneBot V12](https://github.com/nonebot/adapter-onebot/blob/54270edbbdb2a71332d744f90b1a3d7f4bf6463a/nonebot/adapters/onebot/v12/adapter.py#L204-L218)
-
-HTTP:
-
-- [OneBot V11](https://github.com/nonebot/adapter-onebot/blob/54270edbbdb2a71332d744f90b1a3d7f4bf6463a/nonebot/adapters/onebot/v11/adapter.py#L179-L215)
-- [OneBot V12](https://github.com/nonebot/adapter-onebot/blob/54270edbbdb2a71332d744f90b1a3d7f4bf6463a/nonebot/adapters/onebot/v12/adapter.py#L220-L266)
-- [QQ](https://github.com/nonebot/adapter-qq/blob/dc5d437e101f0e3db542de3300758a035ed7036e/nonebot/adapters/qq/adapter.py#L599-L605)
-- [Telegram](https://github.com/nonebot/adapter-telegram/blob/4a8633627e619245516767f5503dec2f58fe2193/nonebot/adapters/telegram/adapter.py#L148-L253)
-- [飞书](https://github.com/nonebot/adapter-feishu/blob/f8ab05e6d57a5e9013b944b0d019ca777725dfb0/nonebot/adapters/feishu/adapter.py#L201-L218)
-
-### Bot
-
-Bot 是机器人开发者能够直接获取并使用的核心对象,负责存储平台机器人相关信息,并提供回复事件、调用 API 的上层方法。我们需要继承基类 `Bot`,并实现相关方法:
-
-```python {20,25,34} title=bot.py
-from typing import TYPE_CHECKING, Any, Union
-from typing_extensions import override
-
-from nonebot.message import handle_event
-from nonebot.adapters import Bot as BaseBot
-
-from .event import Event
-from .message import Message, MessageSegment
-
-if TYPE_CHECKING:
- from .adapter import Adapter
-
-
-class Bot(BaseBot):
- """
- your_adapter_name 协议 Bot 适配。
- """
-
- @override
- def __init__(self, adapter: Adapter, self_id: str, **kwargs: Any):
- super().__init__(adapter, self_id)
- self.adapter: Adapter = adapter
- # 一些有关 Bot 的信息也可以在此定义和存储
-
- async def handle_event(self, event: Event):
- # 根据需要,对事件进行某些预处理,例如:
- # 检查事件是否和机器人有关操作,去除事件消息首尾的 @bot
- # 检查事件是否有回复消息,调用平台 API 获取原始消息的消息内容
- ...
- # 调用 handle_event 让 NoneBot 对事件进行处理
- await handle_event(self, event)
-
- @override
- async def send(
- self,
- event: Event,
- message: Union[str, Message, MessageSegment],
- **kwargs: Any,
- ) -> Any:
- # 根据平台实现 Bot 回复事件的方法
-
- # 将消息处理为平台所需的格式后,调用发送消息接口进行发送,例如:
- data = message_to_platform_data(message)
- await self.send_message(
- data=data,
- ...
- )
-```
-
-### Event
-
-Event 是 NoneBot 中的事件主体对象,所有平台消息在进入处理流程前需要转换为 NoneBot 事件。我们需要继承基类 `Event`,并实现相关方法:
-
-```python {5,8,13,18,23,28,33} title=event.py
-from typing_extensions import override
-
-from nonebot.compat import model_dump
-from nonebot.adapters import Event as BaseEvent
-
-class Event(BaseEvent):
-
- @override
- def get_event_name(self) -> str:
- # 返回事件的名称,用于日志打印
- return "event name"
-
- @override
- def get_event_description(self) -> str:
- # 返回事件的描述,用于日志打印,请注意转义 loguru tag
- return escape_tag(repr(model_dump(self)))
-
- @override
- def get_message(self):
- # 获取事件消息的方法,根据事件具体实现,如果事件非消息类型事件,则抛出异常
- raise ValueError("Event has no message!")
-
- @override
- def get_user_id(self) -> str:
- # 获取用户 ID 的方法,根据事件具体实现,如果事件没有用户 ID,则抛出异常
- raise ValueError("Event has no context!")
-
- @override
- def get_session_id(self) -> str:
- # 获取事件会话 ID 的方法,根据事件具体实现,如果事件没有相关 ID,则抛出异常
- raise ValueError("Event has no context!")
-
- @override
- def is_tome(self) -> bool:
- # 判断事件是否和机器人有关
- return False
-```
-
-然后根据平台消息的类型,编写各种不同的事件,并且注意要根据事件类型实现 `get_type` 方法,具体请参考[事件类型](../advanced/adapter#事件类型)。消息类型事件还应重写 `get_message` 和 `get_user_id` 等方法,例如:
-
-```python {7,16,20,25,34,42} title=event.py
-from .message import Message
-
-class HeartbeatEvent(Event):
- """心跳时间,通常为元事件"""
-
- @override
- def get_type(self) -> str:
- return "meta_event"
-
-class MessageEvent(Event):
- """消息事件"""
- message_id: str
- user_id: str
-
- @override
- def get_type(self) -> str:
- return "message"
-
- @override
- def get_message(self) -> Message:
- # 返回事件消息对应的 NoneBot Message 对象
- return self.message
-
- @override
- def get_user_id(self) -> str:
- return self.user_id
-
-class JoinRoomEvent(Event):
- """加入房间事件,通常为通知事件"""
- user_id: str
- room_id: str
-
- @override
- def get_type(self) -> str:
- return "notice"
-
-class ApplyAddFriendEvent(Event):
- """申请添加好友事件,通常为请求事件"""
- user_id: str
-
- @override
- def get_type(self) -> str:
- return "request"
-```
-
-### Message
-
-Message 负责正确序列化消息,以便机器人插件处理。我们需要继承 `MessageSegment` 和 `Message` 两个类,并实现相关方法:
-
-```python {9,12,17,22,27,30,36} title=message.py
-from typing import Type, Iterable
-from typing_extensions import override
-
-from nonebot.utils import escape_tag
-
-from nonebot.adapters import Message as BaseMessage
-from nonebot.adapters import MessageSegment as BaseMessageSegment
-
-class MessageSegment(BaseMessageSegment["Message"]):
- @classmethod
- @override
- def get_message_class(cls) -> Type["Message"]:
- # 返回适配器的 Message 类型本身
- return Message
-
- @override
- def __str__(self) -> str:
- # 返回该消息段的纯文本表现形式,通常在日志中展示
- return "text of MessageSegment"
-
- @override
- def is_text(self) -> bool:
- # 判断该消息段是否为纯文本
- return self.type == "text"
-
-
-class Message(BaseMessage[MessageSegment]):
- @classmethod
- @override
- def get_segment_class(cls) -> Type[MessageSegment]:
- # 返回适配器的 MessageSegment 类型本身
- return MessageSegment
-
- @staticmethod
- @override
- def _construct(msg: str) -> Iterable[MessageSegment]:
- # 实现从字符串中构造消息数组,如无字符串嵌入格式可直接返回文本类型 MessageSegment
- ...
-```
-
-然后根据平台具体的消息类型,来实现各种 `MessageSegment` 消息段,具体可以参考以下适配器:
-
-- [OneBot V11](https://github.com/nonebot/adapter-onebot/blob/54270edbbdb2a71332d744f90b1a3d7f4bf6463a/nonebot/adapters/onebot/v11/message.py#L25-L259)
-- [QQ](https://github.com/nonebot/adapter-qq/blob/dc5d437e101f0e3db542de3300758a035ed7036e/nonebot/adapters/qq/message.py#L30-L520)
-- [Telegram](https://github.com/nonebot/adapter-telegram/blob/4a8633627e619245516767f5503dec2f58fe2193/nonebot/adapters/telegram/message.py#L13-L414)
-
-## 适配器测试
-
-关于适配器测试相关内容在这里不再展开,开发者可以根据需要进行合适的测试。这里为开发者提供几个常见问题的解决方法:
-
-1. 在测试中无法导入 editable 模式安装的适配器代码。在 pytest 的 `conftest.py` 内添加如下代码:
-
- ```python title=tests/conftest.py
- from pathlib import Path
- import nonebot.adapters
- nonebot.adapters.__path__.append( # type: ignore
- str((Path(__file__).parent.parent / "nonebot" / "adapters").resolve())
- )
- ```
-
-2. 需要计算适配器测试覆盖率,请在 `pyproject.toml` 中添加 pytest 配置:
-
- ```toml title=pyproject.toml
- [tool.pytest.ini_options]
- addopts = "--cov nonebot/adapters/{adapter-name} --cov-report term-missing"
- ```
-
-## 后续工作
-
-在完成适配器代码的编写后,如果想要将适配器发布到 NoneBot 商店,我们需要将适配器发布到 PyPI 中,然后前往[商店](/store/adapters)页面,切换到适配器页签,点击**发布适配器**按钮,填写适配器相关信息并提交。
-
-另外建议编写适配器文档或者一些插件开发示例,以便其他开发者使用我们的适配器。
diff --git a/website/versioned_docs/version-2.4.1/developer/plugin-publishing.mdx b/website/versioned_docs/version-2.4.1/developer/plugin-publishing.mdx
deleted file mode 100644
index b9b0c883dabd..000000000000
--- a/website/versioned_docs/version-2.4.1/developer/plugin-publishing.mdx
+++ /dev/null
@@ -1,206 +0,0 @@
----
-sidebar_position: 0
-description: 在商店发布自己的插件
----
-
-# 发布插件
-
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-
-NoneBot 为开发者提供了分享插件给大家使用的方式——商店。本章节将会介绍如何将我们写好的插件发布到商店。
-
-:::tip 提示
-本章节仅包含插件发布流程指导,插件开发请查阅前述章节。
-:::
-
-## 准备工作
-
-### 插件命名规范
-
-NoneBot 插件使用下述命名规范:
-
-- 对于**项目名**,建议统一以 `nonebot-plugin-` 开头,之后为拟定的插件名字,词间用横杠 `-` 分隔;
- - **项目名**用于代码仓库名称、PyPI 包的发布名称等;
- - 本文使用 `nonebot-plugin-{your-plugin-name}` 为例。
-- 对于**模块名**,建议与**项目名**一致,但词间用下划线 `_` 分隔,即统一以 `nonebot_plugin_` 开头,之后为拟定的插件名字;
- - **模块名**用于程序导入使用,应为插件文件(夹)的名称;
- - 本文使用 `nonebot_plugin_{your_plugin_name}` 为例。
-
-### 项目结构
-
-:::tip 提示
-本段所述的项目结构仅作推荐,不做强制要求,保证实际可用性即可。
-:::
-
-插件程序本身结构可参考[插件结构](../tutorial/create-plugin.md#插件结构)一节,唯一区别在于,插件包可以直接处于项目顶层。
-
-插件项目的一种组织结构如下:
-
-```tree
-📦 nonebot-plugin-{your-plugin-name}
-├── 📂 nonebot_plugin_{your_plugin_name}
-│ ├── 📜 __init__.py
-│ └── 📜 config.py
-├── 📜 pyproject.toml
-└── 📜 README.md
-```
-
-#### 第三方项目模板
-
-一些社区用户可能会分享自己制作的项目模板方便大家使用,如:
-
-- [A-kirami/nonebot-plugin-template](https://github.com/A-kirami/nonebot-plugin-template)
-- [RF-Tar-Railt/nonebot-plugin-template](https://github.com/RF-Tar-Railt/nonebot-plugin-template)
-- [fllesser/nonebot-plugin-template](https://github.com/fllesser/nonebot-plugin-template)
-
-:::tip 提示
-本文档**不保证**第三方模板的适用性。
-
-根据项目模板提供的使用指导补全/修改相应内容后上传到 GitHub 即可。
-:::
-
-### 插件依赖
-
-本段指导填写插件依赖,避免不正确的依赖信息导致插件无法正常工作。
-
-依赖填写的基本原则:程序直接导入了什么第三方库,就添加什么第三方包依赖;能用哪些第三方库的特性,就根据使用的特性锁定第三方包版本。
-
-:::caution 注意
-
-1. 插件需要添加 `nonebot2` 为依赖以避免“幽灵依赖”;
-2. 插件需要将使用的适配器加入依赖列表,如:使用 OneBot 适配器的插件应添加 `nonebot-adapter-onebot` 依赖;
-3. 由于 `nonebot` 是指 `nonebot1` **而非** `nonebot2`,因此要注意**不要**将 `nonebot` 添加为插件的依赖,以免造成冲突;
-4. 尽可能避免使用 `==` 锁定单一版本,增强与其它插件的兼容性。
-
-:::
-
-### 填写插件元数据
-
-请注意,插件发布要求**必须**填写元数据才能通过审核。
-
-下面是一个示例:
-
-```python title=nonebot_plugin_{your_plugin_name}/__init__.py
-from nonebot.plugin import PluginMetadata
-
-from .config import Config
-
-__plugin_meta__ = PluginMetadata(
- name="{插件名称}",
- description="{插件介绍}",
- usage="{插件用法}",
-
- type="{插件分类}",
- # 发布必填,当前有效类型有:`library`(为其他插件编写提供功能),`application`(向机器人用户提供功能)。
-
- homepage="{项目主页}",
- # 发布必填。
-
- config=Config,
- # 插件配置项类,如无需配置可不填写。
-
- supported_adapters={"~onebot.v11", "~telegram"},
- # 支持的适配器集合,其中 `~` 在此处代表前缀 `nonebot.adapters.`,其余适配器亦按此格式填写。
- # 若插件可以保证兼容所有适配器(即仅使用基本适配器功能)可不填写,否则应该列出插件支持的适配器。
-)
-```
-
-:::caution 注意
-`__plugin_meta__` 变量**必须**处于插件最外层(如 `__init__.py` 中),否则无法正常识别。
-
-一般做法是在 `__init__.py` 中定义 `__plugin_meta__`。
-:::
-
-:::tip 提示
-带花括号 `{}` 的内容需要自行替换,注意**一定要把原有的花括号去掉**。
-:::
-
-### 准备项目主页
-
-通常可以使用 GitHub 项目页面作为项目主页,在 `README.md` 文件中编写插件介绍等内容。
-
-内容大致包括:
-
-- 插件功能介绍
-- 安装方法(建议至少有 `nb-cli` 方式安装,**不要**使用旧式的 `bot.py` 配置)
-- 插件配置项(若无可跳过)
-- 插件设置的触发规则(若无可跳过)
-- 插件的其它用法(按需编写)
-
-:::tip 提示
-可以参考[第三方项目模板](#第三方项目模板)。
-:::
-
-### 发布至 [PyPI](https://pypi.org)
-
-根据选用的构建系统,在项目的 `pyproject.toml` 中填入必要信息后进行构建与发布。
-
-:::tip 提示
-不同构建工具的使用可能存在差别。本文仅以 [`pdm`](https://pdm.fming.dev/latest/),
-[`poetry`](https://python-poetry.org/docs/), [`setuptools`](https://setuptools.pypa.io/en/latest/)
-构建系统**本地构建与发布**为示例讲解,其余构建/管理工具等和自动化构建的用法请读者自行探索。
-:::
-
-
-
-
-```bash
-poetry publish --build # 构建并发布
-
-# 等效于以下两个命令
-poetry build # 只构建
-poetry publish # 只发布先前的构建
-```
-
-
-
-
-
-```bash
-pdm publish # 构建并发布
-
-# 等效于以下两个命令
-pdm build # 只构建
-pdm publish --no-build # 只发布先前的构建
-```
-
-
-
-
-
-```bash
-pip install build twine # 安装通用构建与发布工具
-
-python -m build --sdist --wheel . # 只构建
-twine upload dist/* # 只发布先前的构建
-```
-
-
-
-
-:::tip 提示
-发布前建议自行测试构建包是否可用,避免遗漏代码文件或资源文件等问题。
-:::
-
-## 商店审核
-
-### 提交申请
-
-完成在 PyPI 的插件发布流程后,前往[商店](/store/plugins)页面,切换到插件页签,点击 **发布插件** 按钮。
-
-在弹出的插件信息提交表单内,填入您所要发布的相应插件信息。请注意,如果插件需要必要配置项才能正常导入,请在“插件配置项”中填写必要的内容(请勿填写密钥等敏感信息)。
-
-完成填写后,点击 **发布** 按钮,这将自动跳转 NoneBot 仓库内的“发布插件”页面。确认信息无误后点击页面下方的 `Submit new issue` 按钮进行最终提交即可。
-
-### 等待插件审核
-
-插件发布 Issue 创建后,将会经过 **NoneFlow Bot** 的自动前置检查,以确保插件信息正确无误、插件能被正确加载。
-
-:::tip 提示
-若插件检查未通过或信息有误,**不必**关闭当前 Issue。只需更新插件并上传到 PyPI/修改信息后勾选插件测试勾选框即可重新触发插件检查。
-:::
-
-之后,NoneBot 的维护者和一些插件开发者会初步检查插件代码,帮助减少该插件的问题。
-
-完成这些步骤后,您的插件将会被自动合并到[商店](/store/plugins),而您也将成为 [**NoneBot 贡献者**](https://github.com/nonebot/nonebot2/graphs/contributors)的一员。
diff --git a/website/versioned_docs/version-2.4.1/editor-support.md b/website/versioned_docs/version-2.4.1/editor-support.md
deleted file mode 100644
index 0d2f96d680db..000000000000
--- a/website/versioned_docs/version-2.4.1/editor-support.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-sidebar_position: 2
-description: 配置编辑器以获得最佳体验
----
-
-# 编辑器支持
-
-框架基于 [PEP484](https://www.python.org/dev/peps/pep-0484/)、[PEP 561](https://www.python.org/dev/peps/pep-0561/)、[PEP8](https://www.python.org/dev/peps/pep-0008/) 等规范进行开发并且**拥有完整类型注解**。框架使用 Pyright(Pylance)工具进行类型检查,确保代码可以被编辑器正确解析。
-
-## 编辑器推荐配置
-
-### Visual Studio Code
-
-在 Visual Studio Code 中,可以使用 Pylance Language Server 并启用 `Type Checking` 配置以达到最佳开发体验。
-
-1. 在 VSCode 插件视图搜索并安装 `Python (ms-python.python)` 和 `Pylance (ms-python.vscode-pylance)` 插件。
-2. 修改 VSCode 配置
- 在 VSCode 设置视图搜索配置项 `Python: Language Server` 并将其值设置为 `Pylance`,搜索配置项 `Python > Analysis: Type Checking Mode` 并将其值设置为 `basic`。
-
- 或者向项目 `.vscode` 文件夹中配置文件添加以下内容:
-
- ```json title=settings.json
- {
- "python.languageServer": "Pylance",
- "python.analysis.typeCheckingMode": "basic"
- }
- ```
-
-### 其他
-
-欢迎提交 Pull Request 添加其他编辑器配置推荐。点击左下角 `Edit this page` 前往编辑。
diff --git a/website/versioned_docs/version-2.4.1/ospp/2021.md b/website/versioned_docs/version-2.4.1/ospp/2021.md
deleted file mode 100644
index da4bd2d04667..000000000000
--- a/website/versioned_docs/version-2.4.1/ospp/2021.md
+++ /dev/null
@@ -1,193 +0,0 @@
----
-sidebar_position: 0
-description: 开源软件供应链点亮计划 - 暑期 2021
-mdx:
- format: md
----
-
-# 暑期 2021
-
-**开源软件供应链点亮计划 - 暑期 2021** 是**中国科学院软件研究所**与 **openEuler 社区**共同举办的一项面向高校学生的暑期活动,旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer.iscas.ac.cn/) 和 [帮助文档](https://summer.iscas.ac.cn/help/)。
-
-NoneBot 社区有幸作为开源社区参与了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学在上面给出的活动官网报名,或通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
-
-## NoneBot v1
-
-### 更新 NoneBot v1 文档中的“指南”部分
-
-由于 NoneBot v1 和 aiocqhttp 最初基于的 QQ 机器人平台不再提供服务,CQHTTP 接口也转型且改名为 OneBot 标准,目前 NoneBot v1 文档的“指南”部分和 aiocqhttp 文档有部分过时内容需要更新。我们希望将其中与旧的机器人平台相关的内容改为基于 go-cqhttp 或通用的 OneBot 表述,同时对 NoneBot v1 的 awesome-bot 示例做一次全面检查,修改其中可能已经不可用的部分。
-
-**难度**:低
-
-**导师**:[@cleoold](https://github.com/cleoold)
-
-**产出要求**
-
-- 修改“指南”文档和 aiocqhttp 文档中与旧的 QQ 机器人平台相关的部分
-- 检查 awesome-bot 示例是否有已经过时/不可用的地方,并更新/修复
-- 修改“图灵机器人”案例,使用其它 AI 聊天 API 提供商(需先做简单调研)
-
-**技术要求**
-
-- 熟悉 Python 编程语言及 asyncio 机制
-- 了解 Git 基本用法
-- 了解聊天机器人基本开发过程
-- 了解 VuePress 更佳
-
-### NoneBot v1 API 文档自动生成
-
-目前 NoneBot v1 的文档中“API”部分是手动编写的,在更新代码接口的同时需要手动更新文档,可能造成文档与代码不匹配,形成额外的维护成本。我们希望将 API 文档改为直接编写在 Python docstring 中,通过工具自动生成 API 文档。
-
-**难度**:中
-
-**导师**:[@cleoold](https://github.com/cleoold)
-
-**产出要求**
-
-- 调研市面上常见的 Python API 文档生成工具
-- 在代码中补充 API 文档
-- 编写或应用开源工具自动生成 API 文档
-- 配置 GitHub Actions 或其它 CI 自动化构建和部署 API 文档
-
-**技术要求**
-
-- 熟悉 Python 编程语言及 asyncio 和 Type Hints
-- 了解 Git 基本用法
-- 了解 Sphinx 等文档生成工具更佳
-- 了解 GitHub Actions 等 CI 工具更佳
-
-## NoneBot v2
-
-### NoneBot v2 自动化测试框架“NoneBug”
-
-在聊天机器人的开发过程中,一套自动化的测试机制是非常重要的,特别是对于 NoneBot 2 这类为大型机器人开发而设计的项目来说,需要手动测试每一个边际条件是非常痛苦的。我们希望能够开发一款基于 NoneBot 2 插件机制的自动化测试框架,为 NoneBot 2 用户提供一套易用便捷、高度灵活的自动化测试框架。
-
-**难度**:高
-
-**导师**:[@yanyongyu](https://github.com/yanyongyu)
-
-**产出要求**
-
-- 调研现有的 Python 和其它语言集成测试框架
-- 设计 NoneBug 的用户 API 和实现方式
-- 实现 NoneBug 自动化测试框架
-- 编写详细的使用文档
-
-**技术要求**
-
-- 熟悉 Python 编程语言及 asyncio 和 Type Hints
-- 了解 Git 基本用法
-- 了解 NoneBot v2 的基本原理和使用方式
-- 了解主流的 Python 自动化测试框架
-
-### NoneBot v2 Telegram 适配器
-
-目前 NoneBot v2 已支持 OneBot、Mirai HTTP API、钉钉协议,社区反馈有更多的平台需求,希望能在 NoneBot v2 获得更多的跨平台支持,提高机器人的便携性。同时,我们也希望随着新平台加入,提升现有 NoneBot v2 核心代码的平台通用性。Telegram 是一款较为广泛使用的安全即时聊天软件,同时其官方提供了丰富的聊天机器人 API,因此我们希望为 NoneBot v2 编写一个 Telegram 适配器来支持 Telegram 机器人的开发。
-
-**难度**:中
-
-**导师**:[@yanyongyu](https://github.com/yanyongyu)
-
-**产出要求**
-
-- 调研 Telegram Bot API 以及 WebHook 等官方接口
-- 编写 Telegram 适配器并能够使用
-- 代码遵守项目 Contributing 规范
-
-**技术要求**
-
-- 熟悉 Python 编程语言及 asyncio 和 Type Hints
-- 了解 Git 基本用法
-- 了解 Web 开发相关知识
-- 了解 Sphinx 等文档生成工具更佳
-
-### NoneBot v2 飞书适配器
-
-目前 NoneBot v2 已支持 OneBot、Mirai HTTP API、钉钉协议,社区反馈有更多的平台需求,希望能在 NoneBot v2 获得更多的跨平台支持,提高机器人的便携性。同时,我们也希望随着新平台加入,提升现有 NoneBot v2 核心代码的平台通用性。飞书是目前企业用户广泛使用的即时聊天和协作软件,其官方提供了丰富的聊天机器人 API,因此我们希望为 NoneBot v2 编写一个飞书适配器来支持飞书机器人的开发。
-
-**难度**:中
-
-**导师**:[@yanyongyu](https://github.com/yanyongyu)
-
-**产出要求**
-
-- 调研飞书机器人 API 以及 WebHook 等官方接口
-- 编写飞书适配器并能够使用
-- 代码遵守项目 Contributing 规范
-
-**技术要求**
-
-- 熟悉 Python 编程语言及 asyncio 和 Type Hints
-- 了解 Git 基本用法
-- 了解 Web 开发相关知识
-- 了解 Sphinx 等文档生成工具更佳
-
-## OneBot
-
-### 设计 OneBot v12 接口标准
-
-目前的 OneBot 标准的 v11 版本仍然与 QQ 平台有较多耦合,我们希望在 v12 去掉与 QQ 耦合的历史包袱,形成一个通用的、可扩展的、易于使用的同时易于实现的聊天机器人接口标准。
-
-**难度**:中
-
-**导师**:[@richardchien](https://github.com/richardchien)
-
-**产出要求**
-
-- 调研各聊天机器人平台的官方/非官方接口特点
-- 通用化 OneBot 核心 API,分离 QQ 特定的 API,去掉无用 API
-- 优化现有的通信、消息表示机制
-- 补充 QQ 特定的缺失 API
-- 文档需符合风格指南
-
-**技术要求**
-
-- 熟悉至少两个聊天平台的聊天机器人开发
-- 了解 Git 基本用法
-- 了解使用不同语言编写聊天机器人时的常用实践
-- 对文档的优雅性与美观性有追求更佳
-
-### 实现 Rust 版 libonebot
-
-目前最常用的 OneBot 实现包括 go-cqhttp、onebot-kotlin、node-onebot 等,这些实现都各自重复实现了 Web 通信、消息解析、配置读写等功能,当社区中的开发者想针对一个新的聊天平台实现 OneBot 时,他们往往同样需要再次实现类似逻辑。我们希望使用 Rust 编写一个 libonebot 模块,该模块实现所有 OneBot 实现所共享的功能,从而方便其他开发者们使用 Rust 快速编写具体的 OneBot 实现。同时,我们希望借此项目在聊天机器人社区中推广 Rust 编程语言。
-
-> 注:这里的逻辑是 libonebot + 针对某聊天平台的对接代码 = 某聊天平台的 OneBot 实现,libonebot 要做的是让 OneBot 实现的开发者只需编写针对特定聊天平台的对接代码,而无需关心 OneBot 标准定义的通信方式、消息格式等。
-
-**难度**:高
-
-**导师**:[@richardchien](https://github.com/richardchien)
-
-**产出要求**
-
-- 实现所有 OneBot 实现所共享的功能,包括 Web 通信、消息解析、配置读写等
-- 充分考虑同时兼容 OneBot v11 和 v12 接口
-- 能够根据用户(OneBot 实现的开发者)所实现的接口自动实现类似 get_available_apis 等接口
-- 编写详细的使用文档
-- 如果可能,与 v12 设计项目联动,实现第一手 v12 支持
-
-**技术要求**
-
-- 熟悉聊天机器人开发
-- 熟悉 Rust Web 开发
-
-### 实现自选语言版 libonebot
-
-目前最常用的 OneBot 实现包括 go-cqhttp、onebot-kotlin、node-onebot 等,这些实现都各自重复实现了 Web 通信、消息解析、配置读写等功能,当社区中的开发者想针对一个新的聊天平台实现 OneBot 时,他们往往同样需要再次实现类似逻辑。我们希望使用 Python、Go、Kotlin、Node、PHP、C#.NET 等主流语言(任选一个)编写 libonebot 模块,该模块实现所有 OneBot 实现所共享的功能,从而方便其他开发者们使用对应语言快速编写具体的 OneBot 实现。
-
-> 注:这里的逻辑是 libonebot + 针对某聊天平台的对接代码 = 某聊天平台的 OneBot 实现,libonebot 要做的是让 OneBot 实现的开发者只需编写针对特定聊天平台的对接代码,而无需关心 OneBot 标准定义的通信方式、消息格式等。
-
-**难度**:中
-
-**导师**:[@richardchien](https://github.com/richardchien)
-
-**产出要求**
-
-- 实现所有 OneBot 实现所共享的功能,包括 Web 通信、消息解析、配置读写等
-- 充分考虑同时兼容 OneBot v11 和 v12 接口
-- 编写详细的使用文档
-- 如果可能,实现更多附加特性,如根据用户(OneBot 实现的开发者)所实现的接口自动实现类似 get_available_apis 等接口、实现第一手 v12 支持等
-
-**技术要求**
-
-- 熟悉聊天机器人开发
-- 熟悉所选语言的 Web 开发
diff --git a/website/versioned_docs/version-2.4.1/ospp/2022.md b/website/versioned_docs/version-2.4.1/ospp/2022.md
deleted file mode 100644
index e23c274c79b9..000000000000
--- a/website/versioned_docs/version-2.4.1/ospp/2022.md
+++ /dev/null
@@ -1,98 +0,0 @@
----
-sidebar_position: 1
-description: 开源之夏 - 暑期 2022
-mdx:
- format: md
----
-
-# 暑期 2022
-
-**开源之夏 - 暑期 2022** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of Code(GSoC),旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
-
-NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/#/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a/) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学加入 QQ 群 [737131827](https://jq.qq.com/?_wv=1027&k=PEgyGeEu) 或通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
-
-## NoneBot2 命令行 CLI 交互体验升级
-
-NoneBot2 为用户提供了命令行脚手架 ──`nb-cli`,辅助用户更好地上手项目以及进行开发。nb-cli 主要包括:创建项目、运行项目、安装与卸载插件、部署项目等功能。随着 NoneBot2 Beta 版本的发布,脚手架功能存在一定的定位不明确、功能体验不佳。本项目旨在重新设计 nb-cli 功能框架,完善功能,优化用户体验。
-
-**难度**:进阶
-
-**导师**:[@yanyongyu](https://github.com/yanyongyu)
-
-**产出要求**
-
-- 设计 nb-cli 功能框架
- - 明确各功能模块
- - 设计用户交互模式
-- 完成 nb-cli 主要功能代码
- - 项目管理
- - 插件管理
- - 其它
-- 同步更新使用文档
-
-**技术要求**
-
-- 熟悉 Python 命令行交互代码编写
-- 熟悉 NoneBot2 框架功能
-- 熟悉 NoneBot2 项目组织方式
-
-**成果仓库**
-
--
--
-
-## NoneBot2 命令行即时交互通信设计与实现
-
-NoneBot2 在早期提供了基于网页的 nonebot-plugin-test 插件,无需平台适配接入即可对机器人进行测试,方便了开发者直观的感受机器人文本交互功能。我们希望提供一款基于命令行的适配器/驱动器,用于无平台适配接入、可以运行机器人的场景进行功能体验或测试。
-
-**难度**:进阶
-
-**导师**:[@mnixry](https://github.com/mnixry)
-
-**产出要求**
-
-- 设计命令行与 NoneBot2 通信模式
- - 直接调用/HTTP/WebSocket
-- 设计命令行交互界面
-- 实现相应适配器/驱动器
-- 同步更新使用说明文档
-
-**技术要求**
-
-- 熟悉 Python 命令行交互代码编写
-- 熟悉 NoneBot2 框架功能
-- 熟悉 NoneBot2 项目组织方式
-
-**成果仓库**
-
--
-
-## NoneBot2 用户上手与深入教程设计
-
-NoneBot2 为用户提供了详细的文档介绍,辅助用户更好的上手项目以及进行开发。文档分为基础与进阶两个部分。基础部分帮助新用户快速上手开发,主要包括:安装 NoneBot2、使用脚手架、创建配置项目、使用适配器、加载插件、定义消息事件、处理消息事件、调用平台 API 等。进阶部分向已经熟悉开发流程的用户介绍更多高级技巧,主要包括:NoneBot2 工作原理、定时任务、权限控制、钩子函数、跨插件访问、单元测试、发布插件等。目前文档对于用户而言过于费解,导致用户难以理解 NoneBot2 开发。本项目旨在优化文档内容,使其更加通俗易懂,不让文档成为用户上手的阻碍,同时完善进阶内容,让有更复杂需求的用户,同样能从文档中受益。
-
-相关 issue:
-
--
--
-
-**难度**:进阶
-
-**导师**:[@SK-415](https://github.com/SK-415)
-
-**产出要求**
-
-- 文档通俗易懂
- - 附有适当的图片指引(如 asciinema)
-- 内容完整,由浅入深
-- 适当的界面美化,合理分配布局
-
-**技术要求**
-
-- 熟悉文档结构组织与语言表达
-- 熟悉 NoneBot2 框架功能
-- 熟悉 NoneBot2 项目组织方式
-
-**成果仓库**
-
--
diff --git a/website/versioned_docs/version-2.4.1/ospp/2023.md b/website/versioned_docs/version-2.4.1/ospp/2023.md
deleted file mode 100644
index 3209537331b8..000000000000
--- a/website/versioned_docs/version-2.4.1/ospp/2023.md
+++ /dev/null
@@ -1,91 +0,0 @@
----
-sidebar_position: 2
-description: 开源之夏 - 暑期 2023
-mdx:
- format: md
----
-
-# 暑期 2023
-
-**开源之夏 - 暑期 2023** 是由**开源软件供应链点亮计划**发起、由**中国科学院软件研究所**与 **openEuler 社区**主办的一项面向高校学生的暑期活动,类似 Google Summer of Code(GSoC),旨在鼓励在校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
-
-NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
-
-## NoneBot 项目管理图形化面板
-
-NoneBot 目前提供了开箱即用的命令行脚手架来帮助初次使用的用户更快的上手编写应用。但是,对于未有一定开发经验的用户,命令行的使用仍具有一定的困难。此外,其他项目如 koishi、vue 等,均可通过图形化界面的形式为用户提供更便捷的项目开发。因此,我们希望借助现有命令行脚手架的可扩展特性,提供一个项目管理面板服务,以网页的形式帮助用户开发 NoneBot 应用。
-
-**难度**:进阶
-
-**导师**:[@mnixry](https://github.com/mnixry)
-
-**产出要求**
-
-- 设计并实现项目管理面板相关功能
- - 创建与管理项目
- - 配置与运行项目
- - NoneBot 插件管理
-- 实现相应 nb-cli 插件提供面板服务
-- 代码符合 NoneBot Contributing 规范
-
-**技术要求**
-
-- 熟悉 nb-cli 相关功能
-- 熟悉 NoneBot 框架功能
-- 熟悉前后端相关实现方式
-
-**成果仓库**
-
--
-
-## NoneBot Discord 适配器
-
-NoneBot 作为一个跨平台聊天机器人框架,目前已有 OneBot、飞书、Telegram、QQ 频道等诸多平台的适配支持。作为众多用户期待的平台适配之一,我们希望借此机会接入 Discord 聊天机器人。
-
-**难度**:进阶
-
-**导师**:[@iyume](https://github.com/iyume)
-
-**产出要求**
-
-- 调研 Discord Bot 相关功能与接口
-- 设计与编写 NoneBot Discord 适配器
-- 代码符合 NoneBot Contributing 规范
-
-**技术要求**
-
-- 熟悉 NoneBot 框架功能
-- 熟悉 NoneBot 各模块职责与适配器编写
-
-**成果仓库**
-
--
-
-## NoneBot 数据库支持插件
-
-NoneBot 的插件系统为用户实现应用提供了极高的便捷性,但因此也增加了插件统一管理的难度。目前,我们发现许多用户发布的插件中存在文件存储结构化数据、数据存放散乱等现象,同时插件间也可能产生冲突。因此,我们希望提供一个统一的数据存储与管理方式,便于用户读写应用数据。
-
-**难度**:进阶
-
-**导师**:[@yanyongyu](https://github.com/yanyongyu)
-
-**产出要求**
-
-- 设计并实现 ORM 插件
- - 提供关系模型定义功能
- - 提供模型迁移与管理功能
- - 能较好的支持 Python 类型检查与推导
-- 编写相应的用户使用文档
-- 代码符合 NoneBot Contributing 规范
-
-**技术要求**
-
-- 熟悉 NoneBot 框架功能与插件编写
-- 熟悉 SQLAlchemy 等 ORM 框架
- - 熟悉 SQLAlchemy ORM
- - 熟悉 alembic 等迁移工具
-- 熟悉 nb-cli 插件编写
-
-**成果仓库**
-
--
diff --git a/website/versioned_docs/version-2.4.1/ospp/2024.md b/website/versioned_docs/version-2.4.1/ospp/2024.md
deleted file mode 100644
index dec90b48a446..000000000000
--- a/website/versioned_docs/version-2.4.1/ospp/2024.md
+++ /dev/null
@@ -1,105 +0,0 @@
----
-sidebar_position: 3
-description: 开源之夏 - 暑期 2024
-mdx:
- format: md
----
-
-# 暑期 2024
-
-**开源之夏 - 暑期 2024** 是**中国科学院软件研究所**发起的**开源软件供应链点亮计划**系列暑期活动,旨在鼓励高校学生积极参与开源软件的开发维护,促进优秀开源软件社区的蓬勃发展。活动联合各大开源社区,针对重要开源软件的开发与维护提供项目开发任务,并向全球高校学生开放报名。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
-
-NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
-
-## NonePress 官网组件库更新与优化
-
-NoneBot 官网目前采用基于 TailwindCSS 自研的 NonePress 组件库及 Docusaurus 框架进行构建。由于相关依赖版本迭代迅速,目前官网组件库已产生了较大的版本落后。本项目希望在跟进框架新版本的基础上,对文档整体视觉体验进行重新设计,提升页面的无障碍访问性,基于 React Hydrate 特性实现完整的静态网站生成(SSG)以提升搜索引擎优化(SEO)水平。在解决以上问题的基础上,可对网页的开发以及生产构建性能做相应的优化提升,例如在生产构建使用自有的 webpack loader、替换现有的热重载逻辑以减少开发环境启动耗时等。
-
-**难度**:进阶
-
-**导师**:[@yanyongyu](https://github.com/yanyongyu)
-
-**产出要求**
-
-- 基于 Docusaurus v3 重构 NonePress 组件库及相关插件
- - 升级相关依赖并重新打造 Docusaurus theme(布局与组件)
- - 根据需求实现/修改 Docusaurus 插件使得官网内容构建正常
- - 能够提升页面渲染性能与 MDX 相关能力
-- 升级官网采用新版组件库
- - Algolia 索引与 SEO 正常
- - 桌面端与移动端显示正常
- - 优化官网开发与生产构建体验
-- (可选)优化官网部分页面
- - 优化官网过长的 changelog
- - 优化官网插件商店的展示细节
-
-**技术要求**
-
-- 熟练掌握 TS、PostCSS、TSX、MDX等相关技术
-- 掌握 React、Docusaurus、tailwind css 等框架
-- 熟悉静态网站生成 SSG、SEO 优化与 Algolia 索引原理等
-
-**成果仓库**
-
--
-
-## NoneFlow 社区自动化工作流管理优化
-
-NoneFlow 在 NoneBot 社区中承担着重要的角色,它由 NoneBot 框架基于 GitHub APP 编写而成,能够自动化的完成许多复杂流程的处理,如:用户请求提交插件到商店时进行自动化检测,并在人工审核通过后自动存储至 registry;定时自动更新 registry 内插件信息,跟进插件新版本情况等。但是,在长期的使用中发现了一些问题和不足的地方,例如:项目本身结构复杂耦合,添加新自动化流程与维护现有流程困难;目前采用了 GitHub 用户名作为插件作者名,但已有不少插件作者改名;插件存储至 registry 并定时更新,缺少统计相关信息以帮助商店更好的展示当前插件状态;插件作者想要修改插件信息时无法便捷的找到操作方式等。本项目希望针对以上问题与不足的地方进行修复与优化,提升用户体验。
-
-**难度**:进阶
-
-**导师**:[@uy/sun](https://github.com/he0119)
-
-**产出要求**
-
-- 重构现有工作流处理结构
- - 整合现有 Issue、Pull Request、Git 相关操作
- - 提供用户修改信息的处理方式
- - 正确处理 PR 的 Open、Close、Draft 状态
-- 修复流程中存在的问题
- - 插件作者名正确展示
- - registry 定时更新中需要插件测试环境隔离
-- 在 registry 定时更新的同时提供统计数据
-
-**技术要求**
-
-- 掌握 GitHub APP 开发
- - 熟悉 GitHub REST API、GraphQL 等
- - 熟悉 GitHub APP 权限限制
-- 熟悉 NoneBot 框架与 Python 相关技术
-- 熟悉 Git、GitHub Action、GitHub 工作流
-
-**成果仓库**
-
--
-
-## NoneBlockly 低代码框架开发
-
-经过深入分析社区反馈,我们发现部分新手因不熟悉编程概念或框架本身而遇到问题。为了解决初学者在使用面向开发者的聊天机器人框架 NoneBot 时遇到的挑战,我们计划引入 Blockly 提供低代码编程支持。通过减少常见的编码错误和降低入门门槛,使框架对初学者更加友好,从而提升用户体验并有助于 NoneBot 生态的成长。本项目将基于 Blockly 实现 NoneBot 插件的低代码编写,使得用户能够快速搭建聊天机器人。
-
-**难度**:进阶
-
-**导师**:[@mnixry](https://github.com/mnixry)
-
-**产出要求**
-
-- 实现 NoneBlockly 低代码开发框架
- - 能够基于 Alconna 编写跨平台插件
- - 确保插件对 Python 和 NoneBot 版本的兼容性
- - 支持对多种类型 NoneBot 事件的响应
- - 支持对 NoneBot 消息对象的便捷操作
- - 集成 localstore 文件存储、apscheduler 定时任务、网络请求等常用功能
-- 对接 NB-CLI 脚手架,通过脚手架扩展使用低代码框架
-
-**技术要求**
-
-- 掌握 Python 与 NoneBot 框架的使用
- - 熟悉 NoneBot 插件的开发,包括事件响应与消息处理等
- - 熟悉 NoneBot 生态组件(Alconna、localstore、apscheduler等)的使用
- - 了解 NB-CLI 脚手架的扩展开发
-- 熟悉 Blockly 低代码框架的使用和开发
-
-**成果仓库**
-
--
diff --git a/website/versioned_docs/version-2.4.1/ospp/2025.md b/website/versioned_docs/version-2.4.1/ospp/2025.md
deleted file mode 100644
index 5c4fa88ae1b8..000000000000
--- a/website/versioned_docs/version-2.4.1/ospp/2025.md
+++ /dev/null
@@ -1,81 +0,0 @@
----
-sidebar_position: 4
-description: 开源之夏 - 暑期 2025
-mdx:
- format: md
----
-
-# 暑期 2025
-
-**开源之夏 - 暑期 2025** 是**中国科学院软件研究所**发起的**开源软件供应链点亮计划**系列暑期活动,旨在鼓励高校学生积极参与开源软件的开发维护,培养和发掘更多优秀的开发者,促进优秀开源软件社区的蓬勃发展,助力开源软件供应链建设。活动联合各大开源社区,针对重要开源软件的开发与维护提供项目开发任务,并向全球高校学生开放报名。关于具体的活动规划、报名方式,请查看该活动的 [官网](https://summer-ospp.ac.cn/) 和 [帮助文档](https://summer-ospp.ac.cn/help/)。
-
-NoneBot 社区有幸作为开源社区 [参与](https://summer-ospp.ac.cn/org/orgdetail/e1fb5b8d-125a-4138-b756-25bd32c0a31a?lang=zh) 了本次活动,下面列出了目前我们已经发布的项目,欢迎感兴趣的同学通过 [contact@nonebot.dev](mailto:contact@nonebot.dev) 联系我们。
-
-## NoneBot HTML 图片渲染插件
-
-文字与图片一直是聊天机器人的两大主流交互方式,而图片的渲染一直是用户开发应用的一大痛点。常见的方式包括 PIL 图片编辑、浏览器渲染 HTML 截图等。PIL 图片编辑依赖人工构建图片布局,容易出现自适应问题,且提升图片特效、美观程度需要极大的开发成本。浏览器渲染方案通过 HTML 与 CSS 能够轻松完成美观自适应能力强的布局,但其部署门槛较高,难以支撑较大规模调用量。而其他轻量化渲染引擎通常不具有完整 HTML/CSS 现代化标准实现,且未提供 Python Binding 直接使用。
-
-本项目希望调研并实现一种高效、便捷的图片渲染方案。该方案需要在保障跨平台一致性、最大程度保证 HTML 与 CSS 现代化标准的前提下,低成本(资源消耗与吞吐量)将 HTML 渲染为对应图片。
-
-**难度**:进阶
-
-**导师**:[@MelodyKnit](https://github.com/MelodyKnit)
-
-**产出要求**
-
-- 调研 HTML/CSS 渲染引擎
- - 调研 litehtml 等渲染引擎 标准支持能力与兼容性
-- 基于渲染引擎实现 HTML 图片渲染插件
- - 将渲染引擎通过 binding 等方式集成为 Python 模块
- - 基于集成模块实现 HTML 图片渲染能力
- - 编写插件使用文档
-
-**技术要求**
-
-- 掌握 Python 及其异步编程
-- 熟悉 NoneBot 框架及其插件编写
-- 了解浏览器与 HTML 渲染原理
-
-**成果仓库**
-
--
-
-## NB-CLI 命令行工具交互优化
-
-NB-CLI 作为 NoneBot 生态的核心入门与管理工具,主要负责新手引导项目创建、项目运行以及插件管理几大功能。目前该脚手架工具仍存在几点缺陷:
-
-- 作为插件管理工具,由于存储数据的局限性,无法很好地展示用户项目当前安装插件状态,并进行卸载等操作;
-- 当前插件管理高度依赖云端 registry 提供插件信息,在离线情况下完全无法使用;
-- 由于插件信息繁多,工具未能向用户展示充分的信息,交互复杂 体验较差。
-
-以上问题对用户使用 NB-CLI 管理项目插件造成了极大的阻碍。
-本项目希望重点针对插件管理部分,重构工具插件管理模块,完善框架缺陷,并通过缓存等方式确保可用性。其次,调研同类工具方案与 TUI 等相关技术,优化信息展示能力、用户交互方式,提升工具整体交互体验。
-
-**相关链接**
-
-- https://github.com/nonebot/nb-cli/issues/138
-- https://github.com/nonebot/nb-cli/issues/140
-
-**难度**:基础
-
-**导师**:[@yanyongyu](https://github.com/yanyongyu)
-
-**产出要求**
-
-- 重构 NB-CLI 插件管理模块
- - 优化项目插件信息存储方式,支持列出、卸载插件等操作
- - 通过缓存 registry 数据等方式确保离线场景的可用性
-- 提升 NB-CLI 交互体验
- - 调研同类工具方案与 TUI 等相关技术
- - 优化 registry 多字段信息展示能力
- - 基于 TUI 等技术优化用户交互方式,提升整体交互体验
-
-**技术要求**
-
-- 熟练掌握 Python 及其异步编程
-- 熟悉 NoneBot 框架与 NB-CLI 使用方法
-- 了解 TUI 等终端交互技术
-
-**成果仓库**
-
--
diff --git a/website/versioned_docs/version-2.4.1/quick-start.mdx b/website/versioned_docs/version-2.4.1/quick-start.mdx
deleted file mode 100644
index 95dfcfebe8c3..000000000000
--- a/website/versioned_docs/version-2.4.1/quick-start.mdx
+++ /dev/null
@@ -1,119 +0,0 @@
----
-sidebar_position: 1
-description: 尝试使用 NoneBot
-
-options:
- menu:
- - category: tutorial
- weight: 10
----
-
-import Asciinema from "@site/src/components/Asciinema";
-import Messenger from "@site/src/components/Messenger";
-
-# 快速上手
-
-:::caution 前提条件
-
-- 请确保你的 Python 版本 >= 3.9
-- **我们强烈建议使用虚拟环境进行开发**,如果没有使用虚拟环境,请确保已经卸载可能存在的 NoneBot v1!!!
- ```bash
- pip uninstall nonebot
- ```
-
-:::
-
-在本章节中,我们将介绍如何使用脚手架来创建一个 NoneBot 简易项目。项目将基于 nb-cli 脚手架运行,并允许我们从商店安装插件。
-
-
-
-## 安装脚手架
-
-确保你已经安装了 Python 3.9 及以上版本,然后在命令行中执行以下命令:
-
-1. 安装 [pipx](https://pypa.github.io/pipx/)
-
- ```bash
- python -m pip install --user pipx
- python -m pipx ensurepath
- ```
-
- 如果在此步骤的输出中出现了“open a new terminal”或者“re-login”字样,那么请关闭当前终端并重新打开一个新的终端。
-
-2. 安装脚手架
-
- ```bash
- pipx install nb-cli
- ```
-
-安装完成后,你可以在命令行使用 `nb` 命令来使用脚手架。如果出现无法找到命令的情况(例如出现“Command not found”字样),请参考 [pipx 文档](https://pypa.github.io/pipx/) 检查你的环境变量。
-
-## 创建项目
-
-使用脚手架来创建一个项目:
-
-```bash
-nb create
-```
-
-这一指令将会执行创建项目的流程,你将会看到一些询问:
-
-1. 项目模板
-
- ```bash
- [?] 选择一个要使用的模板: bootstrap (初学者或用户)
- ```
-
- 这里我们选择 `bootstrap` 模板,它是一个简单的项目模板,能够安装商店插件。如果你需要**自行编写插件**,这里请选择 `simple` 模板。
-
-2. 项目名称
-
- ```bash
- [?] 项目名称: awesome-bot
- ```
-
- 这里我们以 `awesome-bot` 为例,作为项目名称。你可以根据自己的需要来命名。
-
-3. 其他选项
- 请注意,多选项使用**空格**选中或取消,**回车**确认。
-
- ```bash
- [?] 要使用哪些驱动器? FastAPI (FastAPI 驱动器)
- [?] 要使用哪些适配器? Console (基于终端的交互式适配器)
- [?] 立即安装依赖? (Y/n) Yes
- [?] 创建虚拟环境? (Y/n) Yes
- ```
-
- 这里我们选择了创建虚拟环境,nb-cli 在之后的操作中将会自动使用这个虚拟环境。如果你不需要自动创建虚拟环境或者已经创建了其他虚拟环境,nb-cli 将会安装依赖至当前激活的 Python 虚拟环境。
-
-4. 选择内置插件
-
- ```bash
- [?] 要使用哪些内置插件? echo
- ```
-
- 这里我们选择 `echo` 插件作为示例。这是一个简单的复读回显插件,可以用于测试你的机器人是否正常运行。
-
-## 运行项目
-
-在项目创建完成后,你可以在**项目目录**中使用以下命令来运行项目:
-
-```bash
-nb run
-```
-
-你现在应该已经运行起来了你的第一个 NoneBot 项目了!请注意,生成的项目中使用了 `FastAPI` 驱动器和 `Console` 适配器,你之后可以自行修改配置或安装其他适配器。
-
-## 尝试使用
-
-在项目运行起来后,`Console` 适配器会在你的终端启动交互模式,你可以直接在输入框中输入 `/echo hello world` 来测试你的机器人是否正常运行。
-
-
diff --git a/website/versioned_docs/version-2.4.1/tutorial/application.md b/website/versioned_docs/version-2.4.1/tutorial/application.md
deleted file mode 100644
index 3d6928917326..000000000000
--- a/website/versioned_docs/version-2.4.1/tutorial/application.md
+++ /dev/null
@@ -1,110 +0,0 @@
----
-sidebar_position: 0
-description: 创建一个 NoneBot 项目
-
-options:
- menu:
- - category: tutorial
- weight: 20
----
-
-# 手动创建项目
-
-在[快速上手](../quick-start.mdx)中,我们已经介绍了如何安装和使用 `nb-cli` 创建一个项目。在本章节中,我们将简要介绍如何在不使用 `nb-cli` 的方式创建一个机器人项目的**最小实例**并启动。如果你想要了解 NoneBot 的启动流程,也可以阅读本章节。
-
-:::caution 警告
-我们十分不推荐直接创建机器人项目,请优先考虑使用 nb-cli 进行项目创建。
-:::
-
-一个机器人项目的**最小实例**中**至少**需要包含以下内容:
-
-- 入口文件:初始化并运行机器人的 Python 文件
-- 配置文件:存储机器人启动所需的配置
-- 插件:为机器人提供具体的功能
-
-下面我们创建一个项目文件夹,来存放项目所需文件,以下步骤均在该文件夹中进行。
-
-## 安装依赖
-
-在创建项目前,我们首先需要将项目所需依赖安装至环境中。
-
-1. (可选)创建虚拟环境,以 venv 为例
-
- ```bash
- python -m venv .venv --prompt nonebot2
- # windows
- .venv\Scripts\activate
- # linux/macOS
- source .venv/bin/activate
- ```
-
-2. 安装 nonebot2 以及驱动器
-
- ```bash
- pip install 'nonebot2[fastapi]'
- ```
-
- 驱动器包名可以在 [驱动器商店](/store/drivers) 中找到。
-
-3. 安装适配器
-
- ```bash
- pip install nonebot-adapter-console
- ```
-
- 适配器包名可以在 [适配器商店](/store/adapters) 中找到。
-
-## 创建配置文件
-
-配置文件用于存放 NoneBot 运行所需要的配置项,使用 [`pydantic`](https://docs.pydantic.dev/) 以及 [`python-dotenv`](https://saurabh-kumar.com/python-dotenv/) 来读取配置。配置项需符合 dotenv 格式,复杂类型数据需使用 JSON 格式填写。具体可选配置方式以及配置项详情参考[配置](../appendices/config.mdx)。
-
-在**项目文件夹**中创建一个 `.env` 文本文件,并写入以下内容:
-
-```bash title=.env
-HOST=0.0.0.0 # 配置 NoneBot 监听的 IP / 主机名
-PORT=8080 # 配置 NoneBot 监听的端口
-COMMAND_START=["/"] # 配置命令起始字符
-COMMAND_SEP=["."] # 配置命令分割字符
-```
-
-## 创建入口文件
-
-入口文件( Entrypoint )顾名思义,是用来初始化并运行机器人的 Python 文件。入口文件需要完成框架的初始化、注册适配器、加载插件等工作。
-
-:::tip 提示
-如果你使用 `nb-cli` 创建项目,入口文件不会被创建,该文件功能会被 `nb run` 命令代替。
-:::
-
-在**项目文件夹**中创建一个 `bot.py` 文件,并写入以下内容:
-
-```python title=bot.py
-import nonebot
-from nonebot.adapters.console import Adapter as ConsoleAdapter # 避免重复命名
-
-# 初始化 NoneBot
-nonebot.init()
-
-# 注册适配器
-driver = nonebot.get_driver()
-driver.register_adapter(ConsoleAdapter)
-
-# 在这里加载插件
-nonebot.load_builtin_plugins("echo") # 内置插件
-# nonebot.load_plugin("thirdparty_plugin") # 第三方插件
-# nonebot.load_plugins("awesome_bot/plugins") # 本地插件
-
-if __name__ == "__main__":
- nonebot.run()
-```
-
-我们暂时不需要了解其中内容的含义,这些将会在稍后的章节中逐一介绍。在创建完成以上文件并确认已安装所需适配器和插件后,即可运行机器人。
-
-## 运行机器人
-
-在**项目文件夹**中,使用配置好环境的 Python 解释器运行入口文件(如果使用虚拟环境,请先激活虚拟环境):
-
-```bash
-python bot.py
-```
-
-如果你后续使用了 `nb-cli` ,你仍可以使用 `nb run` 命令来运行机器人,`nb-cli` 会自动检测入口文件 `bot.py` 是否存在并运行。同时,你也可以使用 `nb run --reload` 来自动检测代码的更改并自动重新运行入口文件。
diff --git a/website/versioned_docs/version-2.4.1/tutorial/create-plugin.md b/website/versioned_docs/version-2.4.1/tutorial/create-plugin.md
deleted file mode 100644
index 9ce4ed4dd797..000000000000
--- a/website/versioned_docs/version-2.4.1/tutorial/create-plugin.md
+++ /dev/null
@@ -1,226 +0,0 @@
----
-sidebar_position: 3
-description: 创建并加载自定义插件
-
-options:
- menu:
- - category: tutorial
- weight: 50
----
-
-# 插件编写准备
-
-在正式编写插件之前,我们需要先了解一下插件的概念。
-
-## 插件结构
-
-在 NoneBot 中,插件即是 Python 的一个[模块(module)](https://docs.python.org/zh-cn/3/glossary.html#term-module)。NoneBot 会在导入时对这些模块做一些特殊的处理使得他们成为一个插件。插件间应尽量减少耦合,可以进行有限制的相互调用,NoneBot 能够正确解析插件间的依赖关系。
-
-### 单文件插件
-
-一个普通的 `.py` 文件即可以作为一个插件,例如创建一个 `foo.py` 文件:
-
-```tree title=Project
-📂 plugins
-└── 📜 foo.py
-```
-
-这个时候模块 `foo` 已经可以被称为一个插件了,尽管它还什么都没做。
-
-### 包插件
-
-一个包含 `__init__.py` 的文件夹即是一个常规 Python [包 `package`](https://docs.python.org/zh-cn/3/glossary.html#term-regular-package),例如创建一个 `foo` 文件夹:
-
-```tree title=Project
-📂 plugins
-└── 📂 foo
- └── 📜 __init__.py
-```
-
-这个时候包 `foo` 同样是一个合法的插件,插件内容可以在 `__init__.py` 文件中编写。
-
-## 创建插件
-
-:::caution 注意
-如果在之前的[快速上手](../quick-start.mdx)章节中已经使用 `bootstrap` 模板创建了项目,那么你需要做出如下修改:
-
-1. 在项目目录中创建一个两层文件夹 `awesome_bot/plugins`
-
- ```tree title=Project
- 📦 awesome-bot
- ├── 📂 awesome_bot
- │ └── 📂 plugins
- ├── 📜 pyproject.toml
- └── 📜 README.md
- ```
-
-2. 修改 `pyproject.toml` 文件中的 `nonebot` 配置项,在 `plugin_dirs` 中添加 `awesome_bot/plugins`
-
- ```toml title=pyproject.toml
- [tool.nonebot]
- plugin_dirs = ["awesome_bot/plugins"]
- ```
-
-:::
-
-:::caution 注意
-如果在之前的[创建项目](./application.md)章节中手动创建了相关文件,那么你需要做出如下修改:
-
-1. 在项目目录中创建一个两层文件夹 `awesome_bot/plugins`
-
- ```tree title=Project
- 📦 awesome-bot
- ├── 📂 awesome_bot
- │ └── 📂 plugins
- └── 📜 bot.py
- ```
-
-2. 修改 `bot.py` 文件中的加载插件部分,取消注释或者添加如下代码
-
- ```python title=bot.py
- # 在这里加载插件
- nonebot.load_builtin_plugins("echo") # 内置插件
- nonebot.load_plugins("awesome_bot/plugins") # 本地插件
- ```
-
-:::
-
-创建插件可以通过 `nb-cli` 命令从完整模板创建,也可以手动新建空白文件。通过以下命令创建一个名为 `weather` 的插件:
-
-```bash
-$ nb plugin create
-[?] 插件名称: weather
-[?] 使用嵌套插件? (y/N) N
-[?] 输出目录: awesome_bot/plugins
-```
-
-`nb-cli` 会在 `awesome_bot/plugins` 目录下创建一个名为 `weather` 的文件夹,其中包含的文件将在稍后章节中用到。
-
-```tree title=Project
-📦 awesome-bot
-├── 📂 awesome_bot
-│ └── 📂 plugins
-| └── 📂 foo
-| ├── 📜 __init__.py
-| └── 📜 config.py
-├── 📜 pyproject.toml
-└── 📜 README.md
-```
-
-## 加载插件
-
-:::danger 警告
-请勿在插件被加载前 `import` 插件模块,这会导致 NoneBot 无法将其转换为插件而出现意料之外的情况。
-:::
-
-加载插件是在机器人入口文件中完成的,需要在框架初始化之后,运行之前进行。
-
-请注意,加载的插件模块名称(插件文件名或文件夹名)**不能相同**,且每一个插件**只能被加载一次**,重复加载将会导致异常。
-
-如果你使用 `nb-cli` 管理插件,那么你可以跳过这一节,`nb-cli` 将会自动处理加载。
-
-如果你**使用自定义的入口文件** `bot.py`,那么你需要在 `bot.py` 中加载插件。
-
-```python {5} title=bot.py
-import nonebot
-
-nonebot.init()
-
-# 加载插件
-
-nonebot.run()
-```
-
-加载插件的方式有多种,但在底层的加载逻辑是一致的。以下是为加载插件提供的几种方式:
-
-### `load_plugin`
-
-通过点分割模块名称或使用 [`pathlib`](https://docs.python.org/zh-cn/3/library/pathlib.html) 的 `Path` 对象来加载插件。通常用于加载第三方插件或者项目插件。例如:
-
-```python
-from pathlib import Path
-
-nonebot.load_plugin("path.to.your.plugin") # 加载第三方插件
-nonebot.load_plugin(Path("./path/to/your/plugin.py")) # 加载项目插件
-```
-
-:::caution 注意
-请注意,本地插件的路径应该为相对机器人 **入口文件(通常为 bot.py)** 可导入的,例如在项目 `plugins` 目录下。
-:::
-
-### `load_plugins`
-
-加载传入插件目录中的所有插件,通常用于加载一系列本地编写的项目插件。例如:
-
-```python
-nonebot.load_plugins("src/plugins", "path/to/your/plugins")
-```
-
-:::caution 注意
-请注意,插件目录应该为相对机器人 **入口文件(通常为 bot.py)** 可导入的,例如在项目 `plugins` 目录下。
-:::
-
-### `load_all_plugins`
-
-这种加载方式是以上两种方式的混合,加载所有传入的插件模块名称,以及所有给定目录下的插件。例如:
-
-```python
-nonebot.load_all_plugins(["path.to.your.plugin"], ["path/to/your/plugins"])
-```
-
-### `load_from_json`
-
-通过 JSON 文件加载插件,是 [`load_all_plugins`](#load_all_plugins) 的 JSON 变种。通过读取 JSON 文件中的 `plugins` 字段和 `plugin_dirs` 字段进行加载。例如:
-
-```json title=plugin_config.json
-{
- "plugins": ["path.to.your.plugin"],
- "plugin_dirs": ["path/to/your/plugins"]
-}
-```
-
-```python
-nonebot.load_from_json("plugin_config.json", encoding="utf-8")
-```
-
-:::tip 提示
-如果 JSON 配置文件中的字段无法满足你的需求,可以使用 [`load_all_plugins`](#load_all_plugins) 方法自行读取配置来加载插件。
-:::
-
-### `load_from_toml`
-
-通过 TOML 文件加载插件,是 [`load_all_plugins`](#load_all_plugins) 的 TOML 变种。通过读取 TOML 文件中的 `[tool.nonebot]` Table 中的 `plugins` 和 `plugin_dirs` Array 进行加载。例如:
-
-```toml title=plugin_config.toml
-[tool.nonebot]
-plugins = ["path.to.your.plugin"]
-plugin_dirs = ["path/to/your/plugins"]
-```
-
-```python
-nonebot.load_from_toml("plugin_config.toml", encoding="utf-8")
-```
-
-:::tip 提示
-如果 TOML 配置文件中的字段无法满足你的需求,可以使用 [`load_all_plugins`](#load_all_plugins) 方法自行读取配置来加载插件。
-:::
-
-### `load_builtin_plugin`
-
-加载一个内置插件,传入的插件名必须为 NoneBot 内置插件。该方法是 [`load_plugin`](#load_plugin) 的封装。例如:
-
-```python
-nonebot.load_builtin_plugin("echo")
-```
-
-### `load_builtin_plugins`
-
-加载传入插件列表中的所有内置插件。例如:
-
-```python
-nonebot.load_builtin_plugins("echo", "single_session")
-```
-
-### 其他加载方式
-
-有关其他插件加载的方式,可参考[跨插件访问](../advanced/requiring.md)和[嵌套插件](../advanced/plugin-nesting.md)。
diff --git a/website/versioned_docs/version-2.4.1/tutorial/event-data.mdx b/website/versioned_docs/version-2.4.1/tutorial/event-data.mdx
deleted file mode 100644
index 5f8ccb543bdc..000000000000
--- a/website/versioned_docs/version-2.4.1/tutorial/event-data.mdx
+++ /dev/null
@@ -1,65 +0,0 @@
----
-sidebar_position: 6
-description: 通过依赖注入获取所需事件信息
-
-options:
- menu:
- - category: tutorial
- weight: 80
----
-
-# 获取事件信息
-
-import Messenger from "@site/src/components/Messenger";
-
-在 NoneBot 事件处理流程中,获取事件信息并做出对应的操作是非常常见的场景。本章节中我们将介绍如何通过**依赖注入**获取事件信息。
-
-## 认识依赖注入
-
-在事件处理流程中,事件响应器具有自己独立的上下文,例如:当前响应的事件、收到事件的机器人或者其他处理流程中新增的信息等。这些数据可以根据我们的需求,通过依赖注入的方式,在执行事件处理流程中注入到事件处理函数中。
-
-相对于传统的信息获取方法,通过依赖注入获取信息的最大特色在于**按需获取**。如果该事件处理函数不需要任何额外信息即可运行,那么可以不进行依赖注入。如果事件处理函数需要额外的数据,可以通过依赖注入的方式灵活的标注出需要的依赖,在函数运行时便会被按需注入。
-
-## 使用依赖注入
-
-使用依赖注入获取上下文信息的方法十分简单,我们仅需要在函数的参数中声明所需的依赖,并正确的将函数添加为事件处理依赖即可。在 NoneBot 中,我们可以直接使用 `nonebot.params` 模块中定义的参数类型来声明依赖。
-
-例如,我们可以继续改进上一章节中的 `weather` 插件,使其可以获取到 `天气` 命令的地名参数,并根据地名返回天气信息。
-
-```python {9,11} title=weather/__init__.py
-from nonebot import on_command
-from nonebot.rule import to_me
-from nonebot.adapters import Message
-from nonebot.params import CommandArg
-
-weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
-
-@weather.handle()
-async def handle_function(args: Message = CommandArg()):
- # 提取参数纯文本作为地名,并判断是否有效
- if location := args.extract_plain_text():
- await weather.finish(f"今天{location}的天气是...")
- else:
- await weather.finish("请输入地名")
-```
-
-如上方示例所示,我们使用了 `args` 作为注入参数名,注入的内容为 `CommandArg()`,也就是**消息命令后跟随的内容**。在这个示例中,我们获得的参数会被检查是否有效,对无效参数则会结束事件。
-
-:::tip 提示
-命令与参数之间可以不需要空格,`CommandArg()` 获取的信息为命令后跟随的内容并去除了头部空白符。例如:`/天气 上海` 消息的参数为 `上海`。
-:::
-
-:::tip 提示
-`:=` 是 Python 3.8 引入的新语法 [Assignment Expressions](https://docs.python.org/zh-cn/3/reference/expressions.html#assignment-expressions),也称为海象表达式,可以在表达式中直接赋值。
-:::
-
-
-
-NoneBot 提供了多种依赖注入类型,可以获取不同的信息,具体内容可参考[依赖注入](../advanced/dependency.mdx)。
diff --git a/website/versioned_docs/version-2.4.1/tutorial/fundamentals.md b/website/versioned_docs/version-2.4.1/tutorial/fundamentals.md
deleted file mode 100644
index bb5eea0b656e..000000000000
--- a/website/versioned_docs/version-2.4.1/tutorial/fundamentals.md
+++ /dev/null
@@ -1,24 +0,0 @@
----
-sidebar_position: 1
-description: NoneBot 机器人构成及基本使用
-
-options:
- menu:
- - category: tutorial
- weight: 30
----
-
-# 机器人的构成
-
-了解机器人的基本构成有助于你更好地使用 NoneBot,本章节将介绍 NoneBot 中的基本组成部分,稍后的文档中将会使用到这些概念。
-
-使用 NoneBot 框架搭建的机器人具有以下几个基本组成部分:
-
-1. NoneBot 机器人框架主体:负责连接各个组成部分,提供基本的机器人功能
-2. 驱动器 `Driver`:客户端/服务端的功能实现,负责接收和发送消息(通常为 HTTP 通信)
-3. 适配器 `Adapter`:驱动器的上层,负责将**平台消息**与 NoneBot 事件/操作系统的消息格式相互转换
-4. 插件 `Plugin`:机器人的功能实现,通常为负责处理事件并进行一系列的操作
-
-除 NoneBot 机器人框架主体外,其他部分均可按需选择、互相搭配,但由于平台的兼容性问题,部分插件可能仅在某些特定平台上可用(这由插件编写者决定)。
-
-在接下来的章节中,我们将重点介绍机器人功能实现,即插件 `Plugin` 部分。
diff --git a/website/versioned_docs/version-2.4.1/tutorial/handler.mdx b/website/versioned_docs/version-2.4.1/tutorial/handler.mdx
deleted file mode 100644
index 466b15b380e2..000000000000
--- a/website/versioned_docs/version-2.4.1/tutorial/handler.mdx
+++ /dev/null
@@ -1,87 +0,0 @@
----
-sidebar_position: 5
-description: 处理接收到的特定事件
-
-options:
- menu:
- - category: tutorial
- weight: 70
----
-
-# 事件处理
-
-import Messenger from "@site/src/components/Messenger";
-
-在我们收到事件,并被某个事件响应器正确响应后,便正式开启了对于这个事件的**处理流程**。
-
-## 认识事件处理流程
-
-就像我们在解决问题时需要遵循流程一样,处理一个事件也需要一套流程。在事件响应器对一个事件进行响应之后,会依次执行一系列的**事件处理依赖**(通常是函数)。简单来说,事件处理流程并不是一个函数、一个对象或一个方法,而是一整套由开发者设计的流程。
-
-在这个流程中,我们**目前**只需要了解两个概念:函数形式的“事件处理依赖”(下称“事件处理函数”)和“事件响应器操作”。
-
-## 事件处理函数
-
-在事件响应器中,事件处理流程可以由一个或多个“事件处理函数”组成,这些事件处理函数将会按照顺序依次对事件进行处理,直到全部执行完成或被中断。我们可以采用事件响应器的“事件处理函数装饰器”来添加这些“事件处理函数”。
-
-顾名思义,“事件处理函数装饰器”是一个[装饰器(decorator)](https://docs.python.org/zh-cn/3/glossary.html#term-decorator),那么它的使用方法也同[函数定义](https://docs.python.org/zh-cn/3/reference/compound_stmts.html#function-definitions)中所展示的包装用法相同。例如:
-
-```python {6-8} title=weather/__init__.py
-from nonebot.rule import to_me
-from nonebot.plugin import on_command
-
-weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
-
-@weather.handle()
-async def handle_function():
- pass # do something here
-```
-
-如上方示例所示,我们使用 `weather` 响应器的 `handle` 装饰器装饰了一个函数 `handle_function`。`handle_function` 函数会被添加到 `weather` 的事件处理流程中。在 `weather` 响应器被触发之后,将会依次调用 `weather` 响应器的事件处理函数,即 `handle_function` 来对事件进行处理。
-
-## 事件响应器操作
-
-在事件处理流程中,我们可以使用事件响应器操作来进行一些交互或改变事件处理流程,例如向机器人用户发送消息或提前结束事件处理流程等。
-
-事件响应器操作与事件处理函数装饰器类似,通常作为事件响应器 `Matcher` 的[类方法](https://docs.python.org/zh-cn/3/library/functions.html#classmethod)存在,因此事件响应器操作的调用方法也是 `Matcher.func()` 的形式。不过不同的是,事件响应器操作并不是装饰器,因此并不需要@进行标注。
-
-```python {8,9} title=weather/__init__.py
-from nonebot.rule import to_me
-from nonebot.plugin import on_command
-
-weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
-
-@weather.handle()
-async def handle_function():
- # await weather.send("天气是...")
- await weather.finish("天气是...")
-```
-
-如上方示例所示,我们使用 `weather` 响应器的 `finish` 操作方法向机器人用户回复了 `天气是...` 并结束了事件处理流程。效果如下:
-
-
-
-值得注意的是,在执行 `finish` 方法时,NoneBot 会在向机器人用户发送消息内容后抛出 `FinishedException` 异常来结束事件响应流程。也就是说,在 `finish` 被执行后,后续的程序是不会被执行的。如果你需要回复机器人用户消息但不想事件处理流程结束,可以使用注释的部分中展示的 `send` 方法。
-
-:::danger 警告
-由于 `finish` 是通过抛出 `FinishedException` 异常来结束事件的,因此异常可能会被未加限制的 `try-except` 捕获,影响事件处理流程正确处理,导致无法正常结束此事件。请务必在异常捕获中指定错误类型或排除所有 [MatcherException](../api/exception.md#MatcherException) 类型的异常(如下所示),或将 `finish` 移出捕获范围进行使用。
-
-```python
-from nonebot.exception import MatcherException
-
-try:
- await weather.finish("天气是...")
-except MatcherException:
- raise
-except Exception as e:
- pass # do something here
-```
-
-:::
-
-目前 NoneBot 提供了多种事件响应器操作,其中包括用于机器人用户交互与流程控制两大类,进阶使用方法可以查看[会话控制](../appendices/session-control.mdx)。
diff --git a/website/versioned_docs/version-2.4.1/tutorial/matcher.md b/website/versioned_docs/version-2.4.1/tutorial/matcher.md
deleted file mode 100644
index 8d3bf3922b2b..000000000000
--- a/website/versioned_docs/version-2.4.1/tutorial/matcher.md
+++ /dev/null
@@ -1,58 +0,0 @@
----
-sidebar_position: 4
-description: 响应接收到的特定事件
-
-options:
- menu:
- - category: tutorial
- weight: 60
----
-
-# 事件响应器
-
-事件响应器(Matcher)是对接收到的事件进行响应的基本单元,所有的事件响应器都继承自 `Matcher` 基类。
-
-在 NoneBot 中,事件响应器可以通过一系列特定的规则**筛选**出**具有某种特征的事件**,并按照**特定的流程**交由**预定义的事件处理依赖**进行处理。例如,在[快速上手](../quick-start.mdx)中,我们使用了内置插件 `echo` ,它定义的事件响应器能响应机器人用户发送的“/echo hello world”消息,提取“hello world”信息并作为回复消息发送。
-
-## 事件响应器辅助函数
-
-NoneBot 中所有事件响应器均继承自 `Matcher` 基类,但直接使用 `Matcher.new()` 方法创建事件响应器过于繁琐且不能记录插件信息。因此,NoneBot 中提供了一系列“事件响应器辅助函数”(下称“辅助函数”)来辅助我们用**最简的方式**创建**带有不同规则预设**的事件响应器,提高代码可读性和书写效率。通常情况下,我们只需要使用辅助函数即可完成事件响应器的创建。
-
-在 NoneBot 中,辅助函数以 `on()` 或 `on_()` 形式出现(例如 `on_command()`),调用后根据不同的参数返回一个 `Type[Matcher]` 类型的新事件响应器。
-
-目前 NoneBot 提供了多种功能各异的辅助函数、具有共同命令名称前缀的命令组以及具有共同参数的响应器组,均可以从 `nonebot` 模块直接导入使用,具体内容参考[事件响应器进阶](../advanced/matcher.md)。
-
-## 创建事件响应器
-
-在上一节[创建插件](./create-plugin.md#创建插件)中,我们创建了一个 `weather` 插件,现在我们来实现他的功能。
-
-我们直接使用 `on_command()` 辅助函数来创建一个事件响应器:
-
-```python {3} title=weather/__init__.py
-from nonebot import on_command
-
-weather = on_command("天气")
-```
-
-这样,我们就获得一个名为 `weather` 的事件响应器了,这个事件响应器会对 `/天气` 开头的消息进行响应。
-
-:::tip 提示
-如果一条消息中包含“@机器人”或以“机器人的昵称”开始,例如 `@bot /天气` 时,协议适配器会将 `event.is_tome()` 判断为 `True` ,同时也会自动去除 `@bot`,即事件响应器收到的信息内容为 `/天气`,方便进行命令匹配。
-:::
-
-### 为事件响应器添加参数
-
-在辅助函数中,我们可以添加一些参数来对事件响应器进行更加精细的调整,例如事件响应器的优先级、匹配规则等。例如:
-
-```python {4} title=weather/__init__.py
-from nonebot import on_command
-from nonebot.rule import to_me
-
-weather = on_command("天气", rule=to_me(), aliases={"weather", "查天气"}, priority=10, block=True)
-```
-
-这样,我们就获得了一个可以响应 `天气`、`weather`、`查天气` 三个命令的响应规则,需要私聊或 `@bot` 时才会响应,优先级为 10(越小越优先),阻断事件向后续优先级传播的事件响应器了。这些内容的意义和使用方法将会在后续的章节中一一介绍。
-
-:::tip 提示
-需要注意的是,不同的辅助函数有不同的可选参数,在使用之前可以参考[事件响应器进阶 - 基本辅助函数](../advanced/matcher.md#基本辅助函数)或 [API 文档](../api/plugin/on.md#on)。
-:::
diff --git a/website/versioned_docs/version-2.4.1/tutorial/message.md b/website/versioned_docs/version-2.4.1/tutorial/message.md
deleted file mode 100644
index 86989c493ff7..000000000000
--- a/website/versioned_docs/version-2.4.1/tutorial/message.md
+++ /dev/null
@@ -1,349 +0,0 @@
----
-sidebar_position: 7
-description: 处理消息序列与消息段
-
-options:
- menu:
- - category: tutorial
- weight: 90
----
-
-# 处理消息
-
-在不同平台中,一条消息可能会有承载有各种不同的表现形式,它可能是一段纯文本、一张图片、一段语音、一篇富文本文章,也有可能是多种类型的组合等等。
-
-在 NoneBot 中,为确保消息的正常处理与跨平台兼容性,采用了扁平化的消息序列形式,即 `Message` 对象。消息序列是 NoneBot 中的消息载体,无论是接收还是发送的消息,都采用消息序列的形式进行处理。
-
-## 认识消息类型
-
-### 消息序列 `Message`
-
-在 NoneBot 中,消息序列 `Message` 的主要作用是用于表达“一串消息”。由于消息序列继承自 `List[MessageSegment]`,所以 `Message` 的本质是由若干消息段所组成的序列。因此,消息序列的使用方法与 `List` 有很多相似之处,例如切片、索引、拼接等。
-
-在上一节的[使用依赖注入](./event-data.mdx#使用依赖注入)中,我们已经通过依赖注入 `CommandArg()` 获取了命令的参数,它的类型即是消息序列。我们使用了消息序列的 `extract_plain_text()` 方法来获取消息序列中的纯文本内容。
-
-### 消息段 `MessageSegment`
-
-顾名思义,消息段 `MessageSegment` 是一段消息。由于消息序列的本质是由若干消息段所组成的序列,消息段可以被认为是构成消息序列的最小单位。简单来说,消息序列类似于一个自然段,而消息段则是组成自然段的一句话。同时,作为特殊消息载体的存在,绝大多数的平台都有着**独特的消息类型**,这些独特的内容均需要由对应的**协议适配器**所提供,以适应不同平台中的消息模式。**这也意味着,你需要导入对应的协议适配器中的消息序列和消息段后才能使用其特殊的工厂方法。**
-
-:::caution 注意
-消息段的类型是由协议适配器提供的,因此你需要参考协议适配器的文档并导入对应的消息段后才能使用其特殊的消息类型。
-
-在上一节的[使用依赖注入](./event-data.mdx#使用依赖注入)中,我们导入的为 `nonebot.adapters.Message` 抽象基类,因此我们无法使用平台特有的消息类型。仅能使用 `str` 作为纯文本消息回复。
-:::
-
-## 使用消息序列
-
-:::caution 注意
-在以下的示例中,为了更好的理解多种类型的消息组成方式,我们将使用 `Console` 协议适配器来演示消息序列的使用方法。在实际使用中,你需要确保你使用的**消息序列类型**与你所要发送的**平台类型**一致。
-:::
-
-通常情况下,适配器在接收到消息时,会将消息转换为消息序列,可以通过依赖注入 [`EventMessage`](../advanced/dependency.mdx#eventmessage),或者使用 `event.get_message()` 获取。
-
-由于消息序列是 `List[MessageSegment]` 的子类,所以你总是可以用和操作 `List` 类似的方式来处理消息序列。例如:
-
-```python
->>> from nonebot.adapters.console import Message, MessageSegment
->>> message = Message([
- MessageSegment(type="text", data={"text":"hello"}),
- MessageSegment(type="markdown", data={"markup":"**world**"}),
-])
->>> for segment in message:
-... print(segment.type, segment.data)
-...
-text {'text': 'hello'}
-markdown {'markup': '**world**'}
->>> len(message)
-2
-```
-
-### 构造消息序列
-
-在使用事件响应器操作发送消息时,既可以使用 `str` 作为消息,也可以使用 `Message`、`MessageSegment` 或者 `MessageTemplate`。那么,我们就需要先构造一个消息序列。消息序列可以通过多种方式构造:
-
-#### 直接构造
-
-`Message` 类可以直接实例化,支持 `str`、`MessageSegment`、`Iterable[MessageSegment]` 或适配器自定义类型的参数。
-
-```python
-from nonebot.adapters.console import Message, MessageSegment
-
-# str
-Message("Hello, world!")
-# MessageSegment
-Message(MessageSegment.text("Hello, world!"))
-# List[MessageSegment]
-Message([MessageSegment.text("Hello, world!")])
-```
-
-#### 运算构造
-
-`Message` 对象可以通过 `str`、`MessageSegment` 相加构造,详情请参考[拼接消息](#拼接消息)。
-
-#### 从字典数组构造
-
-`Message` 对象支持 Pydantic 自定义类型构造,可以使用 Pydantic 的 `TypeAdapter` 方法进行构造。
-
-```python
-from pydantic import TypeAdapter
-from nonebot.adapters.console import Message, MessageSegment
-
-# 由字典构造消息段
-TypeAdapter(MessageSegment).validate_python(
- {"type": "text", "data": {"text": "text"}}
-) == MessageSegment.text("text")
-
-# 由字典数组构造消息序列
-TypeAdapter(Message).validate_python(
- [MessageSegment.text("text"), {"type": "text", "data": {"text": "text"}}],
-) == Message([MessageSegment.text("text"), MessageSegment.text("text")])
-```
-
-### 获取消息纯文本
-
-由于消息中存在各种类型的消息段,因此 `str(message)` 通常**不能得到消息的纯文本**,而是一个消息序列的字符串表示。
-
-NoneBot 为消息段定义了一个方法 `is_text()` ,可以用于判断消息段是否为纯文本;也可以使用 `message.extract_plain_text()` 方法获取消息纯文本。
-
-```python
-from nonebot.adapters.console import Message, MessageSegment
-
-# 判断消息段是否为纯文本
-MessageSegment.text("text").is_text() == True
-
-# 提取消息纯文本字符串
-Message(
- [MessageSegment.text("text"), MessageSegment.markdown("**markup**")]
-).extract_plain_text() == "text"
-```
-
-### 遍历
-
-消息序列继承自 `List[MessageSegment]` ,因此可以使用 `for` 循环遍历消息段。
-
-```python
-for segment in message:
- ...
-```
-
-### 比较
-
-消息和消息段都可以使用 `==` 或 `!=` 运算符比较是否相同。
-
-```python
-MessageSegment.text("text") != MessageSegment.text("foo")
-
-some_message == Message([MessageSegment.text("text")])
-```
-
-### 检查消息段
-
-我们可以通过 `in` 运算符或消息序列的 `has` 方法来:
-
-```python
-# 是否存在消息段
-MessageSegment.text("text") in message
-# 是否存在指定类型的消息段
-"text" in message
-```
-
-我们还可以使用消息序列的 `only` 方法来检查消息中是否仅包含指定的消息段。
-
-```python
-# 是否都为指定消息段
-message.only(MessageSegment.text("test"))
-# 是否仅包含指定类型的消息段
-message.only("text")
-```
-
-### 过滤、索引与切片
-
-消息序列对列表的索引与切片进行了增强,在原有列表 `int` 索引与 `slice` 切片的基础上,支持 `type` 过滤索引与切片。
-
-```python
-from nonebot.adapters.console import Message, MessageSegment
-
-message = Message(
- [
- MessageSegment.text("test"),
- MessageSegment.markdown("test2"),
- MessageSegment.markdown("test3"),
- MessageSegment.text("test4"),
- ]
-)
-# 索引
-message[0] == MessageSegment.text("test")
-# 切片
-message[0:2] == Message(
- [MessageSegment.text("test"), MessageSegment.markdown("test2")]
-)
-# 类型过滤
-message["markdown"] == Message(
- [MessageSegment.markdown("test2"), MessageSegment.markdown("test3")]
-)
-# 类型索引
-message["markdown", 0] == MessageSegment.markdown("test2")
-# 类型切片
-message["markdown", 0:2] == Message(
- [MessageSegment.markdown("test2"), MessageSegment.markdown("test3")]
-)
-```
-
-我们也可以通过消息序列的 `include`、`exclude` 方法进行类型过滤。
-
-```python
-message.include("text", "markdown")
-message.exclude("text")
-```
-
-同样的,消息序列对列表的 `index`、`count` 方法也进行了增强,可以用于索引指定类型的消息段。
-
-```python
-# 指定类型首个消息段索引
-message.index("markdown") == 1
-# 指定类型消息段数量
-message.count("markdown") == 2
-```
-
-此外,消息序列添加了一个 `get` 方法,可以用于获取指定类型指定个数的消息段。
-
-```python
-# 获取指定类型指定个数的消息段
-message.get("markdown", 1) == Message([MessageSegment.markdown("test2")])
-```
-
-### 拼接消息
-
-`str`、`Message`、`MessageSegment` 对象之间可以直接相加,相加均会返回一个新的 `Message` 对象。
-
-```python
-# 消息序列与消息段相加
-Message([MessageSegment.text("text")]) + MessageSegment.text("text")
-# 消息序列与字符串相加
-Message([MessageSegment.text("text")]) + "text"
-# 消息序列与消息序列相加
-Message([MessageSegment.text("text")]) + Message([MessageSegment.text("text")])
-# 字符串与消息序列相加
-"text" + Message([MessageSegment.text("text")])
-# 消息段与消息段相加
-MessageSegment.text("text") + MessageSegment.text("text")
-# 消息段与字符串相加
-MessageSegment.text("text") + "text"
-# 消息段与消息序列相加
-MessageSegment.text("text") + Message([MessageSegment.text("text")])
-# 字符串与消息段相加
-"text" + MessageSegment.text("text")
-```
-
-如果需要在当前消息序列后直接拼接新的消息段,可以使用 `Message.append`、`Message.extend` 方法,或者使用自加。
-
-```python
-msg = Message([MessageSegment.text("text")])
-# 自加
-msg += "text"
-msg += MessageSegment.text("text")
-msg += Message([MessageSegment.text("text")])
-# 附加
-msg.append("text")
-msg.append(MessageSegment.text("text"))
-# 扩展
-msg.extend([MessageSegment.text("text")])
-```
-
-我们也可以通过消息段或消息序列的 `join` 方法来拼接一串消息:
-
-```python
-seg = MessageSegment.text("text")
-msg = seg.join(
- [
- MessageSegment.text("first"),
- Message(
- [
- MessageSegment.text("second"),
- MessageSegment.text("third"),
- ]
- )
- ]
-)
-msg == Message(
- [
- MessageSegment.text("first"),
- MessageSegment.text("text"),
- MessageSegment.text("second"),
- MessageSegment.text("third"),
- ]
-)
-```
-
-### 使用消息模板
-
-为了提供安全可靠的跨平台模板字符,我们提供了一个消息模板功能来构建消息序列
-
-它在以下常见场景中尤其有用:
-
-- 多行富文本编排(包含图片,文字以及表情等)
-- 客制化(由 Bot 最终用户提供消息模板时)
-
-在事实上,它的用法和 `str.format` 极为相近,所以你在使用的时候,总是可以参考[Python 文档](https://docs.python.org/zh-cn/3/library/stdtypes.html#str.format)来达到你想要的效果,这里给出几个简单的例子。
-
-默认情况下,消息模板采用 `str` 纯文本形式的格式化:
-
-```python title=基础格式化用法
->>> from nonebot.adapters import MessageTemplate
->>> MessageTemplate("{} {}").format("hello", "world")
-'hello world'
-```
-
-如果 `Message.template` 构建消息模板,那么消息模板将采用消息序列形式的格式化,此时的消息将会是平台特定的:
-
-:::caution 注意
-使用 `Message.template` 构建消息模板时,应注意消息序列为平台适配器提供的类型,不能使用 `nonebot.adapters.Message` 基类作为模板构建。使用基类构建模板与使用 `str` 构建模板的效果是一样的,因此请使用上述的 `MessageTemplate` 类直接构建模板。:
-:::
-
-```python title=平台格式化用法
->>> from nonebot.adapters.console import Message, MessageSegment
->>> Message.template("{} {}").format("hello", "world")
-Message(
- MessageSegment.text("hello"),
- MessageSegment.text(" "),
- MessageSegment.text("world")
-)
-```
-
-消息模板支持使用消息段进行格式化:
-
-```python title=对消息段进行安全的拼接
->>> from nonebot.adapters.console import Message, MessageSegment
->>> Message.template("{}{}").format(MessageSegment.markdown("**markup**"), "world")
-Message(
- MessageSegment(type='markdown', data={'markup': '**markup**'}),
- MessageSegment(type='text', data={'text': 'world'})
-)
-```
-
-消息模板同样支持使用消息序列作为模板:
-
-```python title=以消息对象作为模板
->>> from nonebot.adapters.console import Message, MessageSegment
->>> Message.template(
-... MessageSegment.text("{user_id}") + MessageSegment.emoji("tada") +
-... MessageSegment.text("{message}")
-... ).format_map({"user_id": 123456, "message": "hello world"})
-Message(
- MessageSegment(type='text', data={'text': '123456'}),
- MessageSegment(type='emoji', data={'emoji': 'tada'}),
- MessageSegment(type='text', data={'text': 'hello world'})
-)
-```
-
-:::caution 注意
-只有消息序列中的文本类型消息段才能被格式化,其他类型的消息段将会原样添加。
-:::
-
-消息模板支持使用拓展控制符来控制消息段类型:
-
-```python title=使用消息段的拓展控制符
->>> from nonebot.adapters.console import Message, MessageSegment
->>> Message.template("{name:emoji}").format(name='tada')
-Message(MessageSegment(type='emoji', data={'name': 'tada'}))
-```
diff --git a/website/versioned_docs/version-2.4.1/tutorial/store.mdx b/website/versioned_docs/version-2.4.1/tutorial/store.mdx
deleted file mode 100644
index 9332ac8ea8b5..000000000000
--- a/website/versioned_docs/version-2.4.1/tutorial/store.mdx
+++ /dev/null
@@ -1,267 +0,0 @@
----
-sidebar_position: 2
-description: 从商店安装适配器和插件
-
-options:
- menu:
- - category: tutorial
- weight: 40
----
-
-# 获取商店内容
-
-import Tabs from "@theme/Tabs";
-import TabItem from "@theme/TabItem";
-import Asciinema from "@site/src/components/Asciinema";
-
-:::tip 提示
-
-如果你暂时没有获取商店内容的需求,可以跳过本章节。
-
-:::
-
-NoneBot 提供了一个[商店](/store/plugins),商店内容均由社区开发者贡献。你可以在商店中查找你需要的适配器和插件等,进行安装或者参考其文档等。
-
-商店中每个内容的卡片都包含了其名称和简介等信息,点击**卡片右上角**链接图标即可跳转到其主页。
-
-## 安装插件
-
-
-
-在商店插件页面中,点击你需要安装的插件下方的 `点击复制安装命令` 按钮,即可复制 `nb-cli` 命令。
-
-请在你的**项目目录**下执行该命令。`nb-cli` 会自动安装插件并将其添加到加载列表中。
-
-
-
-
-```bash
-nb plugin install <插件名称>
-```
-
-
-
-
-```bash
-$ nb plugin install
-[?] 想要安装的插件名称: <插件名称>
-```
-
-
-
-
-```bash
-pip install <插件包名>
-```
-
-插件包名可以在商店插件卡片中找到,或者使用 `nb-cli` 搜索插件显示的详情中找到。安装完成后,需要参考[加载插件章节](./create-plugin.md#加载插件)自行加载。
-
-
-
-
-如果想要查看插件列表,可以使用以下命令
-
-```bash
-# 列出商店所有插件
-nb plugin list
-# 搜索商店插件
-nb plugin search [可选关键词]
-```
-
-升级和卸载插件可以使用以下命令
-
-
-
-
-```bash
-nb plugin update <插件名称>
-nb plugin uninstall <插件名称>
-```
-
-
-
-
-```bash
-$ nb plugin update
-[?] 想要安装的插件名称: <插件名称>
-$ nb plugin uninstall
-[?] 想要卸载的插件名称: <插件名称>
-```
-
-
-
-
-```bash
-pip install --upgrade <插件包名>
-pip uninstall <插件包名>
-```
-
-插件包名可以在商店插件卡片中找到,或者使用 `nb-cli` 搜索插件显示的详情中找到。卸载完成后,需要自行移除插件加载。
-
-
-
-
-## 安装适配器
-
-
-
-安装适配器与安装插件类似,只是将命令换为 `nb adapter`,这里就不再赘述。
-
-请在你的**项目目录**下执行该命令。`nb-cli` 会自动安装适配器并将其添加到注册列表中。
-
-
-
-
-```bash
-nb adapter install <适配器名称>
-```
-
-
-
-
-```bash
-$ nb adapter install
-[?] 想要安装的适配器名称: <适配器名称>
-```
-
-
-
-
-```bash
-pip install <适配器包名>
-```
-
-适配器包名可以在商店适配器卡片中找到,或者使用 `nb-cli` 搜索适配器显示的详情中找到。安装完成后,需要参考[注册适配器章节](../advanced/adapter.md#注册适配器)自行注册。
-
-
-
-
-如果想要查看适配器列表,可以使用以下命令
-
-```bash
-# 列出商店所有适配器
-nb adapter list
-# 搜索商店适配器
-nb adapter search [可选关键词]
-```
-
-升级和卸载适配器可以使用以下命令
-
-
-
-
-```bash
-nb adapter update <适配器名称>
-nb adapter uninstall <适配器名称>
-```
-
-
-
-
-```bash
-$ nb adapter update
-[?] 想要安装的适配器名称: <适配器名称>
-$ nb adapter uninstall
-[?] 想要卸载的适配器名称: <适配器名称>
-```
-
-
-
-
-```bash
-pip install --upgrade <适配器包名>
-pip uninstall <适配器包名>
-```
-
-适配器包名可以在商店适配器卡片中找到,或者使用 `nb-cli` 搜索适配器显示的详情中找到。卸载完成后,需要自行移除适配器加载。
-
-
-
-
-## 安装驱动器
-
-
-
-安装驱动器与安装插件同样类似,只是将命令换为 `nb driver`,这里就不再赘述。
-
-如果你使用了虚拟环境,请在你的**项目目录**下执行该命令,`nb-cli` 会自动安装驱动器到虚拟环境中。
-
-请注意 `nb-cli` 并不会在安装驱动器后修改项目所使用的驱动器,请自行参考[配置方法](../appendices/config.mdx)章节以及 [`DRIVER` 配置项](../appendices/config.mdx#driver)修改驱动器。
-
-
-
-
-```bash
-nb driver install <驱动器名称>
-```
-
-
-
-
-```bash
-$ nb driver install
-[?] 想要安装的驱动器名称: <驱动器名称>
-```
-
-
-
-
-```bash
-pip install <驱动器包名>
-```
-
-驱动器包名可以在商店驱动器卡片中找到,或者使用 `nb-cli` 搜索驱动器显示的详情中找到。
-
-
-
-
-如果想要查看驱动器列表,可以使用以下命令
-
-```bash
-# 列出商店所有驱动器
-nb driver list
-# 搜索商店驱动器
-nb driver search [可选关键词]
-```
-
-升级和卸载驱动器可以使用以下命令
-
-
-
-
-```bash
-nb driver update <驱动器名称>
-nb driver uninstall <驱动器名称>
-```
-
-
-
-
-```bash
-$ nb driver update
-[?] 想要安装的驱动器名称: <驱动器名称>
-$ nb driver uninstall
-[?] 想要卸载的驱动器名称: <驱动器名称>
-```
-
-
-
-
-```bash
-pip install --upgrade <驱动器包名>
-pip uninstall <驱动器包名>
-```
-
-驱动器包名可以在商店驱动器卡片中找到,或者使用 `nb-cli` 搜索驱动器显示的详情中找到。卸载完成后,需要自行移除适配器加载。
-
-
-
diff --git a/website/versioned_sidebars/version-2.4.1-sidebars.json b/website/versioned_sidebars/version-2.4.1-sidebars.json
deleted file mode 100644
index db5689b758fa..000000000000
--- a/website/versioned_sidebars/version-2.4.1-sidebars.json
+++ /dev/null
@@ -1,156 +0,0 @@
-{
- "tutorial": [
- {
- "type": "category",
- "label": "开始",
- "collapsible": false,
- "items": ["index", "quick-start", "editor-support"]
- },
- {
- "type": "category",
- "label": "指南",
- "items": [
- {
- "type": "autogenerated",
- "dirName": "tutorial"
- }
- ]
- },
- {
- "type": "category",
- "label": "深入",
- "items": [
- {
- "type": "autogenerated",
- "dirName": "appendices"
- }
- ]
- },
- {
- "type": "category",
- "label": "进阶",
- "items": [
- {
- "type": "autogenerated",
- "dirName": "advanced"
- }
- ]
- },
- {
- "type": "category",
- "label": "最佳实践",
- "items": [
- {
- "type": "autogenerated",
- "dirName": "best-practice"
- }
- ]
- },
- {
- "type": "category",
- "label": "开发者",
- "items": [
- {
- "type": "autogenerated",
- "dirName": "developer"
- }
- ]
- }
- ],
- "api": [
- {
- "type": "autogenerated",
- "dirName": "api"
- }
- ],
- "ecosystem": [
- {
- "type": "category",
- "label": "关于我们",
- "collapsible": false,
- "items": [
- {
- "type": "autogenerated",
- "dirName": "community"
- }
- ]
- },
- {
- "type": "category",
- "label": "开源之夏",
- "collapsible": true,
- "items": [
- {
- "type": "autogenerated",
- "dirName": "ospp"
- }
- ]
- },
- {
- "type": "category",
- "label": "社区资源",
- "collapsible": false,
- "items": [
- {
- "type": "link",
- "label": "插件商店",
- "href": "/store/plugins"
- },
- {
- "type": "link",
- "label": "适配器商店",
- "href": "/store/adapters"
- },
- {
- "type": "link",
- "label": "驱动器商店",
- "href": "/store/drivers"
- },
- {
- "type": "link",
- "label": "机器人商店",
- "href": "/store/bots"
- },
- {
- "type": "link",
- "label": "Awesome NoneBot",
- "href": "https://awesome.nonebot.dev"
- },
- {
- "type": "link",
- "label": "论坛",
- "href": "https://discussions.nonebot.dev"
- }
- ]
- }
- ],
- "changelog": [
- {
- "type": "category",
- "label": "更新日志",
- "collapsible": false,
- "items": [
- {
- "type": "link",
- "label": "v2.4.1",
- "href": "/changelog/"
- },
- {
- "type": "link",
- "label": "v2.1.1",
- "href": "/changelog/1"
- },
- {
- "type": "link",
- "label": "v2.0.0-beta.3",
- "href": "/changelog/2"
- },
- {
- "type": "link",
- "label": "v2.0.0a8",
- "href": "/changelog/3"
- }
- ]
- }
- ]
-}
diff --git a/website/versions.json b/website/versions.json
index b9df8cd8ef7e..351bee2e6ca2 100644
--- a/website/versions.json
+++ b/website/versions.json
@@ -1 +1 @@
-["2.4.3", "2.4.2", "2.4.1"]
+["2.4.3", "2.4.2"]