Compare commits

...

64 Commits

Author SHA1 Message Date
Ju4tCode
8821218bfd 🔀 Merge pull request #202
Pre Release 2.0.0a10
2021-02-07 23:42:08 +08:00
Ju4tCode
27b9b413df Restore .env.dev 2021-02-07 23:36:56 +08:00
yanyongyu
2b67b0f12e 🔇 remove unused type hint 2021-02-07 23:36:04 +08:00
nonebot
d26d7bba55 📝 update api docs 2021-02-07 13:30:19 +00:00
Mix
0761a60443 🩹 fix reply process in mirai adapter 2021-02-07 21:28:58 +08:00
yanyongyu
217b1a5fac revert command change 2021-02-07 20:57:08 +08:00
yanyongyu
ef98a6f23c 🔖 bump version 2.0.0a10 2021-02-07 20:51:09 +08:00
nonebot
c6a6dc6e21 📝 update api docs 2021-02-07 09:17:49 +00:00
Mix
f3b77a7f60 ✏️ fix typo in docs 2021-02-07 17:16:12 +08:00
Mix
f625c34269 📝 update descriptions about plugin 2021-02-07 17:10:29 +08:00
Mix
ac5c4acf09 🔖 bump version 2.0.0a9.post2 2021-02-07 16:39:34 +08:00
Ju4tCode
9a3464744f 🔀 Merge pull request #201
Hotfix: error module name for plugin wordbank
2021-02-07 14:10:25 +08:00
yanyongyu
91bdfc4704 🚑 hotfix error module name for plugin-wordbank 2021-02-07 14:06:48 +08:00
Ju4tCode
6f8aa49d0e 🔀 Merge pull request #200
Doc typo
2021-02-07 14:03:23 +08:00
StarHeartHunt
be57798eac 📝 update api docs 2021-02-07 05:39:13 +00:00
StarHeart
4855e65b1a 📝 typo 2021-02-07 13:37:15 +08:00
Ju4tCode
a3fe3a1ad8 🔀 Merge pull request #199
Fix mirai adapter command process
2021-02-07 12:55:49 +08:00
Mix
382a9b6e12 🔊 improve message logging 2021-02-07 12:40:31 +08:00
Mix
24349953e3 update test case 2021-02-07 12:17:34 +08:00
Mix
85aba9e36f 🐛 fix bug founded during test in mirai adapter 2021-02-07 12:17:21 +08:00
Mix
49010bf5b7 ⚗️ trying to change mirai adapter message processing behavior 2021-02-07 11:52:50 +08:00
yanyongyu
b1c7f309f8 📝 update doc 2021-02-07 11:09:20 +08:00
Ju4tCode
458ddaa167 🔀 Merge pull request #196
New: Quart driver
2021-02-07 11:02:23 +08:00
Ju4tCode
ab97334cf8 🔀 Merge pull request #197
Docs: export and require
2021-02-07 11:01:20 +08:00
Ju4tCode
112fdf7ed3 📝 update export require doc 2021-02-07 10:59:13 +08:00
Mix
b59ff03abf revert changes to change implement method
This reverts commit bf7b2a8cbe.
2021-02-07 10:15:18 +08:00
Mix
bdd9f5ae30 🐛 fix bad type hinting 2021-02-07 02:27:09 +08:00
Mix
abcdbc4de9 💥 🐛 add support for non-plaintext start message 2021-02-07 02:21:31 +08:00
AkiraXie
1715139494 📝 update docs 2021-02-06 20:53:25 +08:00
AkiraXie
b862c506e4 📝 update export-and-require doc 2021-02-06 17:34:06 +08:00
nonebot
4fd4fbfb08 📝 update api docs 2021-02-06 03:44:30 +00:00
Mix
6cb9fda53a 🎨 remove unused imports 2021-02-06 11:43:01 +08:00
Mix
4f7a033b9c use dynamic routing in quart driver 2021-02-06 11:38:21 +08:00
Mix
c537841bc1 📝 add index in document for quart driver 2021-02-06 11:15:46 +08:00
Mix
7d9a8eaf19 💚 add extra install in document build 2021-02-06 11:15:46 +08:00
Mix
86965ee06d 💡 add comments in quart driver 2021-02-06 11:15:30 +08:00
Mix
496f64f103 🐛 fix bugs in quart driver 2021-02-06 10:34:52 +08:00
Mix
9e0862bc97 finish quart driver implement 2021-02-06 09:41:17 +08:00
Mix
6b43ad5575 add quart as a extra requirement 2021-02-06 09:40:57 +08:00
yanyongyu
bf7b2a8cbe matcher.send will return bot.send 2021-02-05 23:13:35 +08:00
Ju4tCode
7232c89292 🔀 Merge pull request #195
Fix: mirai adapter not escape tag in log
2021-02-05 20:55:54 +08:00
Mix
bc164ca2f2 add a dependcies for quart 2021-02-05 20:29:53 +08:00
Mix
ad8429e7fa 🐛 fix log escape in mirai adapter 2021-02-05 20:09:19 +08:00
yanyongyu
c268e0105d 🐛 fix escape comma 2021-02-05 14:26:03 +08:00
Ju4tCode
050137fb3f 🔀 Merge pull request #194
Pre Release 2.0.0a9.post1
2021-02-05 13:41:35 +08:00
yanyongyu
f2d04c598c 📝 update doc 2021-02-05 13:34:38 +08:00
nonebot
49307a3df2 📝 update api docs 2021-02-05 05:32:52 +00:00
yanyongyu
ff9f0bc74a 📝 update doc 2021-02-05 13:31:33 +08:00
yanyongyu
f929f25abd 🔖 Pre Release 2.0.0a9.post1 2021-02-05 13:05:46 +08:00
Ju4tCode
6af0a2574d 🔀 Merge pull request #193
Fix: MatcherGroup rule override
2021-02-05 11:56:05 +08:00
nonebot
c61a159eff 📝 update api docs 2021-02-05 03:51:53 +00:00
yanyongyu
59bdd03b1e 🐛 fix rule override bug 2021-02-05 11:49:12 +08:00
yanyongyu
35ea1e78ec 📝 update readme 2021-02-03 14:47:26 +08:00
yanyongyu
7db386b752 🔖 bump version a9.post1 2021-02-03 11:29:51 +08:00
yanyongyu
e6a68feb0c 🚑 hotfix missing return when rule check failed 2021-02-03 11:23:13 +08:00
Ju4tCode
78b0450d66 🔀 Merge pull request #188
update doc
2021-02-02 22:01:49 +08:00
Ju4tCode
bd5fb46e0a 🔥 Delete yarn.lock 2021-02-02 21:56:12 +08:00
Ju4tCode
cc3ed735f1 📝 update doc 2021-02-02 21:55:18 +08:00
StarHeartHunt
3f0c3f1f37 🔧 revert e1ff0a7 and add yarn.lock 2021-02-02 20:00:49 +08:00
StarHeartHunt
e1ff0a7221 🔧 ignore yarn.lock 2021-02-02 19:45:17 +08:00
StarHeartHunt
2bf8b4d313 📝 docs grammar 2021-02-02 19:45:17 +08:00
StarHeartHunt
188aa8a50a 🎨 lint 2021-02-02 19:45:17 +08:00
StarHeart
1538d2094a 📝 Update runtime-hook.md 2021-02-02 19:44:52 +08:00
StarHeart
16eb74e88b 📝 Update scheduler.md 2021-02-02 18:32:08 +08:00
93 changed files with 1328 additions and 420 deletions

View File

@@ -30,7 +30,7 @@ jobs:
- name: Set up dependencies
run: |
poetry install
poetry install -E all
- name: Build Doc
run: poetry run sphinx-build -M markdown ./docs_build ./build

View File

@@ -75,7 +75,7 @@ NoneBot2 的驱动框架 `Driver` 以及通信协议 `Adapter` 均可**自定义
- [OneBot(CQHTTP) 协议](https://github.com/howmanybots/onebot/blob/master/README.md) (QQ 等)
- [Mirai-API-HTTP 协议](https://github.com/project-mirai/mirai-api-http)
- [钉钉](https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p) _开发中_
- [钉钉](https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p)
- [Telegram](https://core.telegram.org/bots/api) _计划中_
## 即刻开始

View File

@@ -1,7 +1,7 @@
---
home: true
heroImage: /logo.png
tagline: An asynchronous bot framework.
tagline: 跨平台 Python 异步 QQ 机器人框架
actionText: 开始使用
actionLink: guide/
features:

View File

@@ -0,0 +1 @@
# 钩子函数

View File

@@ -4,7 +4,7 @@
> Advanced Python Scheduler (APScheduler) is a Python library that lets you schedule your Python code to be executed later, either just once or periodically. You can add new jobs or remove old ones on the fly as you please. If you store your jobs in a database, they will also survive scheduler restarts and maintain their state. When the scheduler is restarted, it will then run all the jobs it should have run while it was offline.
## 从 v1 迁移
## 从 NoneBot v1 迁移
`APScheduler` 作为 `nonebot` v1 的可选依赖,为众多 bot 提供了方便的定时任务功能。`nonebot2` 已将 `APScheduler` 独立为 `nonebot_plugin_apscheduler` 插件,你可以在 [插件广场](https://v2.nonebot.dev/plugin-store.html) 中找到它。
@@ -21,7 +21,7 @@ nb plugin install nonebot_plugin_apscheduler
```
:::tip 提示
`nb-cli` 默认通过 `pypi` 安装,你可以使用 `-i [mirror]``--index [mirror]` 使用镜像源安装。
`nb-cli` 默认通过 `pypi` 安装,你可以添加命令参数 `-i [mirror]``--index [mirror]` 使用镜像源安装。
:::
### 通过 poetry
@@ -96,10 +96,14 @@ scheduler = require('nonebot_plugin_apscheduler').scheduler
对于大多数情况,我们需要在 `nonebot2` 项目被启动时启动定时任务,则此处设为 `true`
##### 在 `.env` 中添加
```bash
APSCHEDULER_AUTOSTART=true
```
##### 在 `bot.py` 中添加
```python
nonebot.init(apscheduler_autostart=True)
```
@@ -116,10 +120,14 @@ nonebot.init(apscheduler_autostart=True)
> 官方文档在绝大多数时候能提供最准确和最具时效性的指南
##### 在 `.env` 中添加
```bash
APSCHEDULER_CONFIG={"apscheduler.timezone": "Asia/Shanghai"}
```
##### 在 `bot.py` 中添加
```python
nonebot.init(apscheduler_config={
"apscheduler.timezone": "Asia/Shanghai"

View File

@@ -9,6 +9,23 @@ sidebarDepth: 0
协议详情请看: [CQHTTP](https://github.com/howmanybots/onebot/blob/master/README.md) | [OneBot](https://github.com/howmanybots/onebot/blob/master/README.md)
# NoneBot.adapters.cqhttp.config 模块
## _class_ `Config`
CQHTTP 配置类
* **配置项**
* `access_token` / `cqhttp_access_token`: CQHTTP 协议授权令牌
* `secret` / `cqhttp_secret`: CQHTTP HTTP 上报数据签名口令
# NoneBot.adapters.cqhttp.utils 模块

View File

@@ -9,6 +9,23 @@ sidebarDepth: 0
协议详情请看: [钉钉文档](https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p)
# NoneBot.adapters.ding.config 模块
## _class_ `Config`
钉钉配置类
* **配置项**
* `access_token` / `ding_access_token`: 钉钉令牌
* `secret` / `ding_secret`: 钉钉 HTTP 上报数据签名口令
# NoneBot.adapters.ding.exception 模块

View File

@@ -21,6 +21,26 @@ Mirai-API-HTTP 的适配器以 [AGPLv3许可](https://opensource.org/licenses/AG
这意味着在使用该适配器时需要 **以该许可开源您的完整程序代码**
:::
# NoneBot.adapters.mirai.config 模块
## _class_ `Config`
Mirai 配置类
* **必填**
* `auth_key` / `mirai_auth_key`: mirai-api-http 的 auth_key
* `mirai_host`: mirai-api-http 的地址
* `mirai_port`: mirai-api-http 的端口
# NoneBot.adapters.mirai.bot 模块
@@ -690,28 +710,6 @@ mirai-api-http 正向 Websocket 协议 Bot 适配。
* `qq: int`: 要使用的Bot的QQ号 **注意: 在使用正向Websocket时必须指定该值!**
# NoneBot.adapters.mirai.config 模块
## _class_ `Config`
基类:`pydantic.main.BaseModel`
Mirai 配置类
* **必填**
* `mirai_auth_key`: mirai-api-http的auth_key
* `mirai_host`: mirai-api-http的地址
* `mirai_port`: mirai-api-http的端口
# NoneBot.adapters.mirai.message 模块

View File

@@ -120,7 +120,7 @@ Driver 基类。将后端框架封装,以满足适配器使用。
### `register_adapter(name, adapter)`
### `register_adapter(name, adapter, **kwargs)`
* **说明**

View File

@@ -10,6 +10,58 @@ sidebarDepth: 0
后端使用方法请参考: [FastAPI 文档](https://fastapi.tiangolo.com/)
## _class_ `Config`
基类:`pydantic.env_settings.BaseSettings`
FastAPI 驱动框架设置,详情参考 FastAPI 文档
### `fastapi_openapi_url`
* **类型**
`Optional[str]`
* **说明**
openapi.json 地址,默认为 None 即关闭
### `fastapi_docs_url`
* **类型**
`Optional[str]`
* **说明**
swagger 地址,默认为 None 即关闭
### `fastapi_redoc_url`
* **类型**
`Optional[str]`
* **说明**
redoc 地址,默认为 None 即关闭
## _class_ `Driver`
基类:[`nonebot.drivers.Driver`](README.md#nonebot.drivers.Driver)

View File

@@ -960,7 +960,7 @@ def something_else():
### `on_startswith(msg, rule=None, **kwargs)`
### `on_startswith(msg, **kwargs)`
* **说明**
@@ -1007,7 +1007,7 @@ def something_else():
### `on_endswith(msg, rule=None, **kwargs)`
### `on_endswith(msg, **kwargs)`
* **说明**
@@ -1054,7 +1054,7 @@ def something_else():
### `on_keyword(keywords, rule=None, **kwargs)`
### `on_keyword(keywords, **kwargs)`
* **说明**
@@ -1101,7 +1101,7 @@ def something_else():
### `on_command(cmd, rule=None, aliases=None, **kwargs)`
### `on_command(cmd, aliases=None, **kwargs)`
* **说明**
@@ -1118,12 +1118,12 @@ def something_else():
* `cmd: Union[str, Tuple[str, ...]]`: 指定命令内容
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `aliases: Optional[Set[Union[str, Tuple[str, ...]]]]`: 命令别名
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
@@ -1153,12 +1153,11 @@ def something_else():
### `on_shell_command(cmd, rule=None, aliases=None, parser=None, **kwargs)`
### `on_shell_command(cmd, aliases=None, parser=None, **kwargs)`
* **说明**
注册一个支持 `shell_like` 解析参数的命令消息事件响应器。
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
@@ -1166,22 +1165,22 @@ def something_else():
并将用户输入的原始参数列表保存在 `state["argv"]`, `parser` 处理的参数保存在 `state["args"]`
* **参数**
* `cmd: Union[str, Tuple[str, ...]]`: 指定命令内容
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `aliases: Optional[Set[Union[str, Tuple[str, ...]]]]`: 命令别名
* `parser: Optional[ArgumentParser]`: `nonebot.rule.ArgumentParser` 对象
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
@@ -1203,14 +1202,15 @@ def something_else():
* `state_factory: Optional[T_StateFactory]`: 默认 state 的工厂函数
* **返回**
* **返回**
* `Type[Matcher]`
### `on_regex(pattern, flags=0, rule=None, **kwargs)`
### `on_regex(pattern, flags=0, **kwargs)`
* **说明**

View File

@@ -8,8 +8,6 @@
初次使用时可能会觉得这里的概览过于枯燥,可以先简单略读之后直接前往 [安装](./installation.md) 查看安装方法,并进行后续的基础使用教程。
:::
## 简介
NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的事件进行解析和处理,并以插件化的形式,按优先级分发给事件所对应的事件响应器,来完成具体的功能。
除了起到解析事件的作用NoneBot 还为插件提供了大量实用的预设操作和权限控制机制。对于命令处理,它更是提供了完善且易用的会话机制和内部调用机制,以分别适应命令的连续交互和插件内部功能复用等需求。

View File

@@ -2,7 +2,7 @@
到目前为止我们还在使用 NoneBot 的默认行为,在开始编写自己的插件之前,我们先尝试在配置文件上动动手脚,让 NoneBot 表现出不同的行为。
在上一章节中,我们创建了默认的项目结构,其中 `.env`, `.env.*` 均为项目的配置文件,下面将介绍几种 NoneBot 配置方式。
在上一章节中,我们创建了默认的项目结构,其中 `.env` `.env.*` 均为项目的配置文件,下面将介绍几种 NoneBot 配置方式。
:::danger 警告
请勿将敏感信息写入配置文件并提交至开源仓库!
@@ -83,4 +83,4 @@ config.custom_config4 = "new config after init"
## 优先级
`bot.py init` > `system env` > `env file`
`bot.py` 文件( `nonebot.init` ) > 系统环境变量 > `.env` `.env.*` 文件

View File

@@ -34,18 +34,18 @@ AweSome-Bot
## 启动 Bot
如果你使用 `nb-cli`
通过 `nb-cli`
```bash
nb run [--file=bot.py] [--app=app]
```
者使用
```bash
python bot.py
```
:::tip 提示
如果在 bot 入口文件内定义了 asgi server `nb-cli` 将会为你启动**冷重载模式**
如果在 bot 入口文件内定义了 asgi server `nb-cli` 将会为你启动**冷重载模式**(当文件发生变动时自动重启 NoneBot 实例)
:::

View File

@@ -1,16 +1,16 @@
# 开始使用
一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 NoneBot 实例的准备。
一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 NoneBot 实例的准备工作
## 最小实例
如果你已经按照推荐方式安装了 `nb-cli`,使用脚手架创建一个空项目:
如果你已经按照推荐方式安装了 `nb-cli`,使用创建一个空项目:
```bash
nb create
```
根据脚手架引导,将在当前目录下创建一个项目目录,项目目录内包含 `bot.py`
根据引导进行项目配置,完成后会在当前目录下创建一个项目目录,项目目录内包含 `bot.py`
如果未安装 `nb-cli`,使用你最熟悉的编辑器或 IDE创建一个名为 `bot.py` 的文件,内容如下(这里以 CQHTTP 适配器为例):

View File

@@ -7,7 +7,7 @@
:::
:::warning 注意
请在安装 nonebot2 之前卸载 nonebot 1.x
请在安装 NoneBot v2 之前卸载 NoneBot v1
```bash
pip uninstall nonebot
@@ -15,10 +15,10 @@ pip uninstall nonebot
:::
### 通过脚手架安装(推荐安装方式)
### (推荐安装方式)通过脚手架安装
1. (推荐)使用你喜欢的 Python 环境管理工具(如 `poetry`)创建新的虚拟环境
2. 使用 `pip` (或其他包管理工具) 安装 nb-clinonebot2 作为其依赖一起安装
1. (推荐)使用你喜欢的 Python 环境管理工具(如 `poetry`)创建新的虚拟环境
2. 使用 `pip` 其他包管理工具 安装 `nb-cli``nonebot2`作为其依赖一起安装
```bash
pip install nb-cli
@@ -26,7 +26,7 @@ pip uninstall nonebot
3. 点个 star 吧
nonebot2: [![nb-cli](https://img.shields.io/github/stars/nonebot/nonebot2?style=social)](https://github.com/nonebot/nonebot2)
nonebot2: [![nonebot2](https://img.shields.io/github/stars/nonebot/nonebot2?style=social)](https://github.com/nonebot/nonebot2)
nb-cli: [![nb-cli](https://img.shields.io/github/stars/nonebot/nb-cli?style=social)](https://github.com/nonebot/nb-cli)
@@ -36,7 +36,7 @@ pip uninstall nonebot
[![Telegram Chat](https://img.shields.io/badge/telegram-cqhttp-blue?style=social)](https://t.me/cqhttp)
### 不使用脚手架(纯净安装)
### (纯净安装)不使用脚手架
```bash
pip install nonebot2
@@ -84,8 +84,6 @@ nb plugin install xxx
### 官方插件
~~自用插件~~ ~~确信~~
- [NoneBot-Plugin-Docs](https://github.com/nonebot/nonebot2/tree/master/packages/nonebot-plugin-docs) 离线文档插件
- [NoneBot-Plugin-Test](https://github.com/nonebot/plugin-test) 本地机器人测试前端插件
- [NoneBot-Plugin-APScheduler](https://github.com/nonebot/plugin-apscheduler) 定时任务插件

View File

@@ -4,7 +4,7 @@
Mirai-API-HTTP 的适配现在仍然处于早期阶段, 可能没有进行过充分的测试
在生产环境中谨慎使用
在生产环境中谨慎使用
:::
@@ -34,7 +34,7 @@ Mirai-API-HTTP 的适配器以 [AGPLv3 许可](https://opensource.org/licenses/A
> 单纯运行 NoneBot 实例并不会产生任何效果,因为此刻 QQ 这边还不知道 NoneBot 的存在,也就无法把消息发送给它,因此现在需要使用一个无头 QQ 来把消息等事件上报给 NoneBot。
这次, 我们将采用在实现上有别于 onebot<sup>即 CQHTTP</sup>协议的另外一种无头 QQ API 协议, 即 MAH
这次, 我们将采用在实现上有别于 OneBotCQHTTP协议的另外一种无头 QQ API 协议, 即 MAH
为了配置 MAH 端, 我们现在需要移步到[MAH 的项目地址](https://github.com/project-mirai/mirai-api-http), 来看看它是如何配置的

View File

@@ -1 +0,0 @@
# 运行时插槽

View File

@@ -3,7 +3,7 @@ const path = require("path");
module.exports = context => ({
base: process.env.VUEPRESS_BASE || "/",
title: "NoneBot",
description: "基于 酷Q 的 Python 异步 QQ 机器人框架",
description: "跨平台 Python 异步 QQ 机器人框架",
markdown: {
lineNumbers: true
},
@@ -56,7 +56,7 @@ module.exports = context => ({
"/": {
lang: "zh-CN",
title: "NoneBot",
description: "基于 酷Q 的 Python 异步 QQ 机器人框架"
description: "跨平台 Python 异步 QQ 机器人框架"
}
},
@@ -198,6 +198,10 @@ module.exports = context => ({
title: "nonebot.drivers.fastapi 模块",
path: "drivers/fastapi"
},
{
title: "nonebot.drivers.quart 模块",
path: "drivers/quart"
},
{
title: "nonebot.adapters 模块",
path: "adapters/"

View File

@@ -3,7 +3,7 @@
"short_name": "NoneBot",
"background-color": "#ffffff",
"theme-color": "#ea5252",
"description": "An asynchronous python bot framework.",
"description": "跨平台 Python 异步 QQ 机器人框架",
"display": "standalone",
"icons": [
{

View File

@@ -88,7 +88,7 @@
"repo": "abrahum/nonebot_plugin_simdraw"
},
{
"id": "nonebot-plugin-wordbank",
"id": "nonebot_plugin_wordbank",
"link": "nonebot-plugin-wordbank",
"author": "Joenothing-lst",
"desc": "无数据库的轻量问答插件,支持模糊问答",

View File

@@ -1,5 +1,5 @@
[
"2.0.0a9",
"2.0.0a9.post1",
"2.0.0a8.post2",
"2.0.0a7"
]

View File

@@ -1,7 +1,7 @@
---
home: true
heroImage: /logo.png
tagline: An asynchronous bot framework.
tagline: 跨平台 Python 异步 QQ 机器人框架
actionText: 开始使用
actionLink: guide/
features:

View File

@@ -1 +1,117 @@
# 跨插件访问
由于 `nonebot2` 独特的插件加载机制,在使用 python 原有的 import 机制来进行插件之间的访问时,很可能会有奇怪的或者意料以外的情况发生。为了避免这种情况的发生,您可以有两种方法来实现跨插件访问:
1. 将插件间的要使用的公共代码剥离出来,作为公共文件或者文件夹,提供给插件加以调用。
2. 使用 `nonebot2` 提供的 `export``require` 机制,来实现插件间的互相调用。
第一种方法比较容易理解和实现,这里不再赘述,但需要注意的是,请不要将公共文件或者公共文件夹作为**插件**被 `nonebot2` 加载。
下面将介绍第二种方法—— `export``require` 机制:
## 使用 export and require
现在,假定有两个插件 `pluginA``pluginB`,需要在 `pluginB` 中调用 `pluginA` 中的一个变量 `varA` 和一个函数 `funcA`
在上面的条件中涉及到了两种操作:一种是在 `pluginA``导出对象` 操作;而另一种是在 `pluginB``导入对象` 操作。在 `nonebot2` 中,`导出对象` 的操作用 `export` 机制来实现,`导入对象` 的操作用 `require` 机制来实现。下面,我们将逐一进行介绍。
:::warning 警告
使用这个方法进行跨插件访问时,**需要先加载`导出对象`的插件,再加载`导入对象`的插件。**
:::
### 使用 export
`pluginA` 中,我们调用 `export` 机制 `导出对象`
`export` 机制调用前,我们需要保证导出的对象已经被定义,比如:
```python
varA = "varA"
def funcA():
return "funcA"
```
在确保定义之后,我们可以从 `nonebot.plugin` 导入 `export()` 方法, `export()` 方法会返回一个特殊的字典 `export`
```python
from nonebot.plugin import export
export=export()
```
这个字典可以用来装载导出的对象,它的 key 是对象导出后的命名value 是对象本身,我们可以直接创建新的 `key` - `value` 对导出对象:
```python
export.vA = varA
export.fA = funcA
```
除此之外,也支持 `嵌套` 导出对象:
```python
export.sub.vA = varA
export.sub.fA = funcA
```
特别地,对于 `函数对象` 而言,`export` 支持用 `装饰器` 的方法来导出,因此,我们可以这样定义 `funcA`
```python
@export.sub
def funcA():
return "funcA"
```
或者:
```python
@export
def funcA():
return "funcA"
```
通过 `装饰器` 的方法导出函数时,命名固定为函数的命名,也就是说,上面的两个例子等同于:
```python
export.sub.funcA = funcA
export.funcA = funcA
```
这样,我们就成功导出 `varA``funcA` 对象了。
下面我们将介绍如何在 `pluginB` 中导入这些对象。
### 使用 require
`pluginB` 中,我们调用 `require` 机制 `导入对象`
:::warning 警告
在导入来自其他插件的对象时, 请确保导出该对象的插件在引用该对象的插件之前加载。如果该插件并未被加载,则会尝试加载,加载失败则会返回 `None`
:::
我们可以从 `nonebot.plugin` 中导入 `require()` 方法:
```python
from nonebot.plugin import require
```
`require()` 方法的参数是插件名, 它会返回在指定插件中,用 `export()` 方法创建的字典。
```python
require_A = require('pluginA')
```
在之前,这个字典已经存入了 `'vA'` - `varA`, `'fA'` - `funcA``'funcA'` - `funcA` 这样的 `key` - `value` 对。因此在这里我们直接用 `属性` 的方法来获取导入对象:
```python
varA = require_A.vA
funcA = require_A.fA or require_A.funcA
```
这样,我们就在 `pluginB` 中成功导入了 `varA``funcA` 对象了。

View File

@@ -1 +1 @@
# 运行时插槽
# 钩子函数

View File

@@ -4,7 +4,7 @@
> Advanced Python Scheduler (APScheduler) is a Python library that lets you schedule your Python code to be executed later, either just once or periodically. You can add new jobs or remove old ones on the fly as you please. If you store your jobs in a database, they will also survive scheduler restarts and maintain their state. When the scheduler is restarted, it will then run all the jobs it should have run while it was offline.
## 从 v1 迁移
## 从 NoneBot v1 迁移
`APScheduler` 作为 `nonebot` v1 的可选依赖,为众多 bot 提供了方便的定时任务功能。`nonebot2` 已将 `APScheduler` 独立为 `nonebot_plugin_apscheduler` 插件,你可以在 [插件广场](https://v2.nonebot.dev/plugin-store.html) 中找到它。
@@ -21,7 +21,7 @@ nb plugin install nonebot_plugin_apscheduler
```
:::tip 提示
`nb-cli` 默认通过 `pypi` 安装,你可以使用 `-i [mirror]``--index [mirror]` 使用镜像源安装。
`nb-cli` 默认通过 `pypi` 安装,你可以添加命令参数 `-i [mirror]``--index [mirror]` 使用镜像源安装。
:::
### 通过 poetry
@@ -96,10 +96,14 @@ scheduler = require('nonebot_plugin_apscheduler').scheduler
对于大多数情况,我们需要在 `nonebot2` 项目被启动时启动定时任务,则此处设为 `true`
##### 在 `.env` 中添加
```bash
APSCHEDULER_AUTOSTART=true
```
##### 在 `bot.py` 中添加
```python
nonebot.init(apscheduler_autostart=True)
```
@@ -116,10 +120,14 @@ nonebot.init(apscheduler_autostart=True)
> 官方文档在绝大多数时候能提供最准确和最具时效性的指南
##### 在 `.env` 中添加
```bash
APSCHEDULER_CONFIG={"apscheduler.timezone": "Asia/Shanghai"}
```
##### 在 `bot.py` 中添加
```python
nonebot.init(apscheduler_config={
"apscheduler.timezone": "Asia/Shanghai"

View File

@@ -43,6 +43,9 @@
* [nonebot.drivers.fastapi](drivers/fastapi.html)
* [nonebot.drivers.quart](drivers/quart.html)
* [nonebot.adapters](adapters/)

View File

@@ -9,6 +9,23 @@ sidebarDepth: 0
协议详情请看: [CQHTTP](https://github.com/howmanybots/onebot/blob/master/README.md) | [OneBot](https://github.com/howmanybots/onebot/blob/master/README.md)
# NoneBot.adapters.cqhttp.config 模块
## _class_ `Config`
CQHTTP 配置类
* **配置项**
* `access_token` / `cqhttp_access_token`: CQHTTP 协议授权令牌
* `secret` / `cqhttp_secret`: CQHTTP HTTP 上报数据签名口令
# NoneBot.adapters.cqhttp.utils 模块

View File

@@ -9,6 +9,23 @@ sidebarDepth: 0
协议详情请看: [钉钉文档](https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p)
# NoneBot.adapters.ding.config 模块
## _class_ `Config`
钉钉配置类
* **配置项**
* `access_token` / `ding_access_token`: 钉钉令牌
* `secret` / `ding_secret`: 钉钉 HTTP 上报数据签名口令
# NoneBot.adapters.ding.exception 模块

View File

@@ -21,6 +21,26 @@ Mirai-API-HTTP 的适配器以 [AGPLv3许可](https://opensource.org/licenses/AG
这意味着在使用该适配器时需要 **以该许可开源您的完整程序代码**
:::
# NoneBot.adapters.mirai.config 模块
## _class_ `Config`
Mirai 配置类
* **必填**
* `auth_key` / `mirai_auth_key`: mirai-api-http 的 auth_key
* `mirai_host`: mirai-api-http 的地址
* `mirai_port`: mirai-api-http 的端口
# NoneBot.adapters.mirai.bot 模块
@@ -690,28 +710,6 @@ mirai-api-http 正向 Websocket 协议 Bot 适配。
* `qq: int`: 要使用的Bot的QQ号 **注意: 在使用正向Websocket时必须指定该值!**
# NoneBot.adapters.mirai.config 模块
## _class_ `Config`
基类:`pydantic.main.BaseModel`
Mirai 配置类
* **必填**
* `mirai_auth_key`: mirai-api-http的auth_key
* `mirai_host`: mirai-api-http的地址
* `mirai_port`: mirai-api-http的端口
# NoneBot.adapters.mirai.message 模块
@@ -967,15 +965,40 @@ CQHTTP 协议 MessageSegment 适配。具体方法参考 [mirai-api-http 消息
基类:[`nonebot.adapters.Message`](README.md#nonebot.adapters.Message)
Mirai 协议 Messaqge 适配
Mirai 协议 Message 适配
由于Mirai协议的Message实现较为特殊, 故使用MessageChain命名
### `reduce()`
* **说明**
忽略为空的消息段, 合并相邻的纯文本消息段
### `export()`
导出为可以被正常json序列化的数组
### `extract_first(*type)`
* **说明**
弹出该消息链的第一个消息
* **参数**
* \*type: MessageType: 指定的消息类型, 当指定后如类型不匹配不弹出
# NoneBot.adapters.mirai.utils 模块
@@ -1072,20 +1095,6 @@ mirai-api-http 协议事件,字段与 mirai-api-http 一致。各事件字段
> * `MEMBER`: 普通群成员
## _class_ `MessageChain`
基类:[`nonebot.adapters.Message`](README.md#nonebot.adapters.Message)
Mirai 协议 Messaqge 适配
由于Mirai协议的Message实现较为特殊, 故使用MessageChain命名
### `export()`
导出为可以被正常json序列化的数组
## _class_ `MessageEvent`
基类:`nonebot.adapters.mirai.event.base.Event`

View File

@@ -120,7 +120,7 @@ Driver 基类。将后端框架封装,以满足适配器使用。
### `register_adapter(name, adapter)`
### `register_adapter(name, adapter, **kwargs)`
* **说明**

View File

@@ -10,6 +10,58 @@ sidebarDepth: 0
后端使用方法请参考: [FastAPI 文档](https://fastapi.tiangolo.com/)
## _class_ `Config`
基类:`pydantic.env_settings.BaseSettings`
FastAPI 驱动框架设置,详情参考 FastAPI 文档
### `fastapi_openapi_url`
* **类型**
`Optional[str]`
* **说明**
openapi.json 地址,默认为 None 即关闭
### `fastapi_docs_url`
* **类型**
`Optional[str]`
* **说明**
swagger 地址,默认为 None 即关闭
### `fastapi_redoc_url`
* **类型**
`Optional[str]`
* **说明**
redoc 地址,默认为 None 即关闭
## _class_ `Driver`
基类:[`nonebot.drivers.Driver`](README.md#nonebot.drivers.Driver)

62
docs/api/drivers/quart.md Normal file
View File

@@ -0,0 +1,62 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.drivers.quart 模块
## Quart 驱动适配
后端使用方法请参考: [Quart 文档](https://pgjones.gitlab.io/quart/index.html)
## _class_ `Driver`
基类:[`nonebot.drivers.Driver`](README.md#nonebot.drivers.Driver)
Quart 驱动框架
* **上报地址**
* `/{adapter name}/http`: HTTP POST 上报
* `/{adapter name}/ws`: WebSocket 上报
### _property_ `type`
驱动名称: `quart`
### _property_ `server_app`
`Quart` 对象
### _property_ `asgi`
`Quart` 对象
### _property_ `logger`
fastapi 使用的 logger
### `on_startup(func)`
参考文档: [Startup and Shutdown](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)
### `on_shutdown(func)`
参考文档: [Startup and Shutdown](https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html)
### `run(host=None, port=None, *, app=None, **kwargs)`
使用 `uvicorn` 启动 Quart

View File

@@ -960,7 +960,7 @@ def something_else():
### `on_startswith(msg, rule=None, **kwargs)`
### `on_startswith(msg, **kwargs)`
* **说明**
@@ -1007,7 +1007,7 @@ def something_else():
### `on_endswith(msg, rule=None, **kwargs)`
### `on_endswith(msg, **kwargs)`
* **说明**
@@ -1054,7 +1054,7 @@ def something_else():
### `on_keyword(keywords, rule=None, **kwargs)`
### `on_keyword(keywords, **kwargs)`
* **说明**
@@ -1101,7 +1101,7 @@ def something_else():
### `on_command(cmd, rule=None, aliases=None, **kwargs)`
### `on_command(cmd, aliases=None, **kwargs)`
* **说明**
@@ -1118,12 +1118,12 @@ def something_else():
* `cmd: Union[str, Tuple[str, ...]]`: 指定命令内容
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `aliases: Optional[Set[Union[str, Tuple[str, ...]]]]`: 命令别名
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
@@ -1153,12 +1153,11 @@ def something_else():
### `on_shell_command(cmd, rule=None, aliases=None, parser=None, **kwargs)`
### `on_shell_command(cmd, aliases=None, parser=None, **kwargs)`
* **说明**
注册一个支持 `shell_like` 解析参数的命令消息事件响应器。
与普通的 `on_command` 不同的是,在添加 `parser` 参数时, 响应器会自动处理消息。
@@ -1166,22 +1165,22 @@ def something_else():
并将用户输入的原始参数列表保存在 `state["argv"]`, `parser` 处理的参数保存在 `state["args"]`
* **参数**
* `cmd: Union[str, Tuple[str, ...]]`: 指定命令内容
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `aliases: Optional[Set[Union[str, Tuple[str, ...]]]]`: 命令别名
* `parser: Optional[ArgumentParser]`: `nonebot.rule.ArgumentParser` 对象
* `rule: Optional[Union[Rule, T_RuleChecker]]`: 事件响应规则
* `permission: Optional[Permission]`: 事件响应权限
@@ -1203,14 +1202,15 @@ def something_else():
* `state_factory: Optional[T_StateFactory]`: 默认 state 的工厂函数
* **返回**
* **返回**
* `Type[Matcher]`
### `on_regex(pattern, flags=0, rule=None, **kwargs)`
### `on_regex(pattern, flags=0, **kwargs)`
* **说明**

View File

@@ -8,8 +8,6 @@
初次使用时可能会觉得这里的概览过于枯燥,可以先简单略读之后直接前往 [安装](./installation.md) 查看安装方法,并进行后续的基础使用教程。
:::
## 简介
NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的事件进行解析和处理,并以插件化的形式,按优先级分发给事件所对应的事件响应器,来完成具体的功能。
除了起到解析事件的作用NoneBot 还为插件提供了大量实用的预设操作和权限控制机制。对于命令处理,它更是提供了完善且易用的会话机制和内部调用机制,以分别适应命令的连续交互和插件内部功能复用等需求。

View File

@@ -2,7 +2,7 @@
到目前为止我们还在使用 NoneBot 的默认行为,在开始编写自己的插件之前,我们先尝试在配置文件上动动手脚,让 NoneBot 表现出不同的行为。
在上一章节中,我们创建了默认的项目结构,其中 `.env`, `.env.*` 均为项目的配置文件,下面将介绍几种 NoneBot 配置方式。
在上一章节中,我们创建了默认的项目结构,其中 `.env` `.env.*` 均为项目的配置文件,下面将介绍几种 NoneBot 配置方式。
:::danger 警告
请勿将敏感信息写入配置文件并提交至开源仓库!
@@ -83,4 +83,4 @@ config.custom_config4 = "new config after init"
## 优先级
`bot.py init` > `system env` > `env file`
`bot.py` 文件( `nonebot.init` ) > 系统环境变量 > `.env` `.env.*` 文件

View File

@@ -123,7 +123,7 @@ async def async_checker(bot: Bot, event: Event, state: T_State) -> bool:
def sync_checker(bot: Bot, event: Event, state: T_State) -> bool:
return True
def check(arg1, args2):
def check(arg1, arg2):
async def _checker(bot: Bot, event: Event, state: T_State) -> bool:
return bool(arg1 + arg2)

View File

@@ -34,18 +34,18 @@ AweSome-Bot
## 启动 Bot
如果你使用 `nb-cli`
通过 `nb-cli`
```bash
nb run [--file=bot.py] [--app=app]
```
者使用
```bash
python bot.py
```
:::tip 提示
如果在 bot 入口文件内定义了 asgi server `nb-cli` 将会为你启动**冷重载模式**
如果在 bot 入口文件内定义了 asgi server `nb-cli` 将会为你启动**冷重载模式**(当文件发生变动时自动重启 NoneBot 实例)
:::

View File

@@ -1,16 +1,16 @@
# 开始使用
一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 NoneBot 实例的准备。
一切都安装成功后,你就已经做好了进行简单配置以运行一个最小的 NoneBot 实例的准备工作
## 最小实例
如果你已经按照推荐方式安装了 `nb-cli`,使用脚手架创建一个空项目:
如果你已经按照推荐方式安装了 `nb-cli`,使用创建一个空项目:
```bash
nb create
```
根据脚手架引导,将在当前目录下创建一个项目目录,项目目录内包含 `bot.py`
根据引导进行项目配置,完成后会在当前目录下创建一个项目目录,项目目录内包含 `bot.py`
如果未安装 `nb-cli`,使用你最熟悉的编辑器或 IDE创建一个名为 `bot.py` 的文件,内容如下(这里以 CQHTTP 适配器为例):

View File

@@ -7,7 +7,7 @@
:::
:::warning 注意
请在安装 nonebot2 之前卸载 nonebot 1.x
请在安装 NoneBot v2 之前卸载 NoneBot v1
```bash
pip uninstall nonebot
@@ -15,10 +15,10 @@ pip uninstall nonebot
:::
### 通过脚手架安装(推荐安装方式)
### (推荐安装方式)通过脚手架安装
1. (推荐)使用你喜欢的 Python 环境管理工具(如 `poetry`)创建新的虚拟环境
2. 使用 `pip` (或其他包管理工具) 安装 nb-clinonebot2 作为其依赖一起安装
1. (推荐)使用你喜欢的 Python 环境管理工具(如 `poetry`)创建新的虚拟环境
2. 使用 `pip` 其他包管理工具 安装 `nb-cli``nonebot2`作为其依赖一起安装
```bash
pip install nb-cli
@@ -26,7 +26,7 @@ pip uninstall nonebot
3. 点个 star 吧
nonebot2: [![nb-cli](https://img.shields.io/github/stars/nonebot/nonebot2?style=social)](https://github.com/nonebot/nonebot2)
nonebot2: [![nonebot2](https://img.shields.io/github/stars/nonebot/nonebot2?style=social)](https://github.com/nonebot/nonebot2)
nb-cli: [![nb-cli](https://img.shields.io/github/stars/nonebot/nb-cli?style=social)](https://github.com/nonebot/nb-cli)
@@ -36,7 +36,7 @@ pip uninstall nonebot
[![Telegram Chat](https://img.shields.io/badge/telegram-cqhttp-blue?style=social)](https://t.me/cqhttp)
### 不使用脚手架(纯净安装)
### (纯净安装)不使用脚手架
```bash
pip install nonebot2
@@ -84,8 +84,6 @@ nb plugin install xxx
### 官方插件
~~自用插件~~ ~~确信~~
- [NoneBot-Plugin-Docs](https://github.com/nonebot/nonebot2/tree/master/packages/nonebot-plugin-docs) 离线文档插件
- [NoneBot-Plugin-Test](https://github.com/nonebot/plugin-test) 本地机器人测试前端插件
- [NoneBot-Plugin-APScheduler](https://github.com/nonebot/plugin-apscheduler) 定时任务插件

View File

@@ -6,12 +6,15 @@
`bot.py` 文件中添加以下行:
```python{5}
```python{8}
import nonebot
from nonebot.adapters.cqhttp import Bot
nonebot.init()
# 加载 nonebot 内置插件
nonebot.load_builtin_plugins()
driver = nonebot.get_driver()
driver.register_adapter("cqhttp", Bot) # 注册 CQHTTP 的 Adapter
nonebot.load_builtin_plugins() # 加载 nonebot 内置插件
app = nonebot.get_asgi()
@@ -19,6 +22,12 @@ if __name__ == "__main__":
nonebot.run()
```
::: warning
目前, 内建插件仅支持 CQHTTP 的 Adapter
如果您使用的是其他 Adapter, 请移步该 Adapter 相应的文档
:::
这将会加载 nonebot 内置的插件,它包含:
- 命令 `say`:可由**superuser**使用,可以将消息内容由特殊纯文本转为富文本

View File

@@ -4,7 +4,7 @@
Mirai-API-HTTP 的适配现在仍然处于早期阶段, 可能没有进行过充分的测试
在生产环境中谨慎使用
在生产环境中谨慎使用
:::
@@ -34,7 +34,7 @@ Mirai-API-HTTP 的适配器以 [AGPLv3 许可](https://opensource.org/licenses/A
> 单纯运行 NoneBot 实例并不会产生任何效果,因为此刻 QQ 这边还不知道 NoneBot 的存在,也就无法把消息发送给它,因此现在需要使用一个无头 QQ 来把消息等事件上报给 NoneBot。
这次, 我们将采用在实现上有别于 onebot<sup>即 CQHTTP</sup>协议的另外一种无头 QQ API 协议, 即 MAH
这次, 我们将采用在实现上有别于 OneBotCQHTTP协议的另外一种无头 QQ API 协议, 即 MAH
为了配置 MAH 端, 我们现在需要移步到[MAH 的项目地址](https://github.com/project-mirai/mirai-api-http), 来看看它是如何配置的
@@ -193,3 +193,36 @@ Mirai-API-HTTP 的适配器以 [AGPLv3 许可](https://opensource.org/licenses/A
```
恭喜你, 你的配置已经成功!
现在, 我们可以写一个简单的插件来测试一下
```python
from nonebot.plugin import on_keyword, on_command
from nonebot.rule import to_me
from nonebot.adapters.mirai import Bot, MessageEvent
message_test = on_keyword({'reply'}, rule=to_me())
@message_test.handle()
async def _message(bot: Bot, event: MessageEvent):
text = event.get_plaintext()
await bot.send(event, text, at_sender=True)
command_test = on_command('miecho')
@command_test.handle()
async def _echo(bot: Bot, event: MessageEvent):
text = event.get_plaintext()
await bot.send(event, text, at_sender=True)
```
它具有两种行为
- 在指定机器人,即私聊、群聊内@机器人、群聊内称呼机器人昵称的情况下 (即 [Rule: to_me](../api/rule.md#to-me)), 如果消息内包含 `reply` 字段, 则该消息会被机器人重复一次
- 在执行指令`miecho xxx`时, 机器人会发送回参数`xxx`
至此, 你已经初步掌握了如何使用 Mirai Adapter

View File

@@ -15,6 +15,7 @@ NoneBot Api Reference
- `nonebot.exception <exception.html>`_
- `nonebot.drivers <drivers/>`_
- `nonebot.drivers.fastapi <drivers/fastapi.html>`_
- `nonebot.drivers.quart <drivers/quart.html>`_
- `nonebot.adapters <adapters/>`_
- `nonebot.adapters.cqhttp <adapters/cqhttp.html>`_
- `nonebot.adapters.ding <adapters/ding.html>`_

View File

@@ -8,6 +8,12 @@ NoneBot.adapters.cqhttp 模块
.. automodule:: nonebot.adapters.cqhttp
NoneBot.adapters.cqhttp.config 模块
===================================
.. automodule:: nonebot.adapters.cqhttp.config
:members:
NoneBot.adapters.cqhttp.utils 模块
===================================

View File

@@ -8,6 +8,12 @@ NoneBot.adapters.ding 模块
.. automodule:: nonebot.adapters.ding
NoneBot.adapters.ding.config 模块
===================================
.. automodule:: nonebot.adapters.ding.config
:members:
NoneBot.adapters.ding.exception 模块
=====================================

View File

@@ -8,6 +8,12 @@ NoneBot.adapters.mirai 模块
.. automodule:: nonebot.adapters.mirai
NoneBot.adapters.mirai.config 模块
==================================
.. automodule:: nonebot.adapters.mirai.config
:members:
NoneBot.adapters.mirai.bot 模块
===============================
@@ -22,13 +28,6 @@ NoneBot.adapters.mirai.bot_ws 模块
:members:
:show-inheritance:
NoneBot.adapters.mirai.config 模块
==================================
.. automodule:: nonebot.adapters.mirai.config
:members:
:show-inheritance:
NoneBot.adapters.mirai.message 模块
===================================

View File

@@ -0,0 +1,12 @@
---
contentSidebar: true
sidebarDepth: 0
---
NoneBot.drivers.quart 模块
==========================
.. automodule:: nonebot.drivers.quart
:members:
:private-members:
:show-inheritance:

View File

@@ -425,7 +425,8 @@ class Bot(BaseBot):
- ``NetworkError``: 网络错误
- ``ActionFailed``: API 调用失败
"""
message = escape(message) if isinstance(message, str) else message
message = escape(message, escape_comma=False) if isinstance(
message, str) else message
msg = message if isinstance(message, Message) else Message(message)
at_sender = at_sender and getattr(event, "user_id", None)

View File

@@ -5,6 +5,14 @@ from pydantic import Field, BaseModel
# priority: alias > origin
class Config(BaseModel):
"""
CQHTTP 配置类
:配置项:
- ``access_token`` / ``cqhttp_access_token``: CQHTTP 协议授权令牌
- ``secret`` / ``cqhttp_secret``: CQHTTP HTTP 上报数据签名口令
"""
access_token: Optional[str] = Field(default=None,
alias="cqhttp_access_token")
secret: Optional[str] = Field(default=None, alias="cqhttp_secret")

View File

@@ -4,6 +4,14 @@ from pydantic import Field, BaseModel
class Config(BaseModel):
"""
钉钉配置类
:配置项:
- ``access_token`` / ``ding_access_token``: 钉钉令牌
- ``secret`` / ``ding_secret``: 钉钉 HTTP 上报数据签名口令
"""
secret: Optional[str] = Field(default=None, alias="ding_secret")
access_token: Optional[str] = Field(default=None, alias="ding_access_token")

View File

@@ -1,8 +1,7 @@
from datetime import datetime, timedelta
from functools import wraps
from io import BytesIO
from ipaddress import IPv4Address
from typing import (Any, Dict, List, NoReturn, Optional, Tuple, Union)
from typing import Any, Dict, List, NoReturn, Optional, Tuple, Union
import httpx
@@ -10,15 +9,12 @@ from nonebot.adapters import Bot as BaseBot
from nonebot.config import Config
from nonebot.drivers import Driver, WebSocket
from nonebot.exception import ApiNotAvailable, RequestDenied
from nonebot.log import logger
from nonebot.message import handle_event
from nonebot.typing import overrides
from nonebot.utils import escape_tag
from .config import Config as MiraiConfig
from .event import Event, FriendMessage, GroupMessage, TempMessage
from .message import MessageChain, MessageSegment
from .utils import catch_network_error, argument_validation, check_tome, Log
from .utils import Log, argument_validation, catch_network_error, process_event
class SessionManager:
@@ -212,20 +208,15 @@ class Bot(BaseBot):
async def handle_message(self, message: dict):
Log.debug(f'received message {message}')
try:
await handle_event(
bot=self,
event=await check_tome(
await process_event(
bot=self,
event=Event.new({
**message,
'self_id': self.self_id,
}),
),
)
except Exception as e:
logger.opt(colors=True, exception=e).exception(
'Failed to handle message '
f'<r>{escape_tag(str(message))}</r>: ')
Log.error(f'Failed to handle message: {message}', e)
@overrides(BaseBot)
async def call_api(self, api: str, **data) -> NoReturn:
@@ -262,10 +253,8 @@ class Bot(BaseBot):
* ``message: Union[MessageChain, MessageSegment, str]``: 要发送的消息
* ``at_sender: bool``: 是否 @ 事件主体
"""
if isinstance(message, MessageSegment):
if not isinstance(message, MessageChain):
message = MessageChain(message)
elif isinstance(message, str):
message = MessageChain(MessageSegment.plain(message))
if isinstance(event, FriendMessage):
return await self.send_friend_message(target=event.sender.id,
message_chain=message)

View File

@@ -10,7 +10,7 @@ class Config(BaseModel):
:必填:
- ``mirai_auth_key``: mirai-api-httpauth_key
- ``auth_key`` / ``mirai_auth_key``: mirai-api-httpauth_key
- ``mirai_host``: mirai-api-http 的地址
- ``mirai_port``: mirai-api-http 的端口
"""

View File

@@ -13,7 +13,7 @@ from .request import *
__all__ = [
'Event', 'GroupChatInfo', 'GroupInfo', 'PrivateChatInfo', 'UserPermission',
'MessageChain', 'MessageEvent', 'GroupMessage', 'FriendMessage',
'MessageSource', 'MessageEvent', 'GroupMessage', 'FriendMessage',
'TempMessage', 'NoticeEvent', 'MuteEvent', 'BotMuteEvent', 'BotUnmuteEvent',
'MemberMuteEvent', 'MemberUnmuteEvent', 'BotJoinGroupEvent',
'BotLeaveEventActive', 'BotLeaveEventKick', 'MemberJoinEvent',

View File

@@ -1,6 +1,7 @@
from typing import Any
from datetime import datetime
from typing import Any, Optional
from pydantic import Field
from pydantic import BaseModel, Field
from nonebot.typing import overrides
@@ -8,9 +9,15 @@ from ..message import MessageChain
from .base import Event, GroupChatInfo, PrivateChatInfo
class MessageSource(BaseModel):
id: int
time: datetime
class MessageEvent(Event):
"""消息事件基类"""
message_chain: MessageChain = Field(alias='messageChain')
source: Optional[MessageSource] = None
sender: Any
@overrides(Event)

View File

@@ -44,8 +44,9 @@ class MessageSegment(BaseMessageSegment):
@overrides(BaseMessageSegment)
def __str__(self) -> str:
if self.is_text():
return self.data.get('text', '')
return self.data['text'] if self.is_text() else repr(self)
def __repr__(self) -> str:
return '[mirai:%s]' % ','.join([
self.type.value,
*map(
@@ -267,18 +268,20 @@ class MessageSegment(BaseMessageSegment):
class MessageChain(BaseMessage):
"""
Mirai 协议 Messaqge 适配
Mirai 协议 Message 适配
由于Mirai协议的Message实现较为特殊, 故使用MessageChain命名
"""
@overrides(BaseMessage)
def __init__(self, message: Union[List[Dict[str, Any]],
Iterable[MessageSegment], MessageSegment],
**kwargs):
def __init__(self, message: Union[List[Dict[str,
Any]], Iterable[MessageSegment],
MessageSegment, str], **kwargs):
super().__init__(**kwargs)
if isinstance(message, MessageSegment):
self.append(message)
elif isinstance(message, str):
self.append(MessageSegment.plain(text=message))
elif isinstance(message, Iterable):
self.extend(self._construct(message))
else:
@@ -286,6 +289,19 @@ class MessageChain(BaseMessage):
f'Type {type(message).__name__} is not supported in mirai adapter.'
)
@overrides(BaseMessage)
def reduce(self):
"""
:说明:
忽略为空的消息段, 合并相邻的纯文本消息段
"""
for index, segment in enumerate(self):
segment: MessageSegment
if segment.is_text() and not str(segment).strip():
self.pop(index)
super().reduce()
@overrides(BaseMessage)
def _construct(
self, message: Union[List[Dict[str, Any]], Iterable[MessageSegment]]
@@ -306,5 +322,22 @@ class MessageChain(BaseMessage):
*map(lambda segment: segment.as_dict(), self.copy()) # type: ignore
]
def extract_first(self, *type: MessageType) -> Optional[MessageSegment]:
"""
:说明:
弹出该消息链的第一个消息
:参数:
* `*type: MessageType`: 指定的消息类型, 当指定后如类型不匹配不弹出
"""
if not len(self):
return None
first: MessageSegment = self[0]
if (not type) or (first.type in type):
return self.pop(0)
return None
def __repr__(self) -> str:
return f'<{self.__class__.__name__} {[*self.copy()]}>'

View File

@@ -7,10 +7,11 @@ from pydantic import Extra, ValidationError, validate_arguments
import nonebot.exception as exception
from nonebot.log import logger
from nonebot.message import handle_event
from nonebot.utils import escape_tag, logger_wrapper
from .event import Event, GroupMessage
from .message import MessageSegment, MessageType
from .event import Event, GroupMessage, MessageEvent, MessageSource
from .message import MessageType
if TYPE_CHECKING:
from .bot import Bot
@@ -20,23 +21,28 @@ _AnyCallable = TypeVar("_AnyCallable", bound=Callable)
class Log:
_log = logger_wrapper('MIRAI')
@staticmethod
def log(level: str, message: str, exception: Optional[Exception] = None):
logger = logger_wrapper('MIRAI')
message = '<e>' + escape_tag(message) + '</e>'
logger(level=level.upper(), message=message, exception=exception)
@classmethod
def info(cls, message: Any):
cls._log('INFO', str(message))
cls.log('INFO', str(message))
@classmethod
def debug(cls, message: Any):
cls._log('DEBUG', str(message))
cls.log('DEBUG', str(message))
@classmethod
def warn(cls, message: Any):
cls._log('WARNING', str(message))
cls.log('WARNING', str(message))
@classmethod
def error(cls, message: Any, exception: Optional[Exception] = None):
cls._log('ERROR', str(message), exception=exception)
cls.log('ERROR', str(message), exception=exception)
class ActionFailed(exception.ActionFailed):
@@ -118,39 +124,55 @@ def argument_validation(function: _AnyCallable) -> _AnyCallable:
return wrapper # type: ignore
async def check_tome(bot: "Bot", event: "Event") -> "Event":
if not isinstance(event, GroupMessage):
def process_source(bot: "Bot", event: MessageEvent) -> MessageEvent:
source = event.message_chain.extract_first(MessageType.SOURCE)
if source is not None:
event.source = MessageSource.parse_obj(source.data)
return event
def _is_at(event: GroupMessage) -> bool:
for segment in event.message_chain:
segment: MessageSegment
if segment.type != MessageType.AT:
continue
if segment.data['target'] == event.self_id:
return True
return False
def _is_nick(event: GroupMessage) -> bool:
text = event.get_plaintext()
if not text:
return False
nick_regex = '|'.join(
{i.strip() for i in bot.config.nickname if i.strip()})
def process_at(bot: "Bot", event: GroupMessage) -> GroupMessage:
at = event.message_chain.extract_first(MessageType.AT)
if at is not None:
if at.data['target'] == event.self_id:
event.to_me = True
else:
event.message_chain.insert(0, at)
return event
def process_nick(bot: "Bot", event: GroupMessage) -> GroupMessage:
plain = event.message_chain.extract_first(MessageType.PLAIN)
if plain is not None:
text = str(plain)
nick_regex = '|'.join(filter(lambda x: x, bot.config.nickname))
matched = re.search(rf"^({nick_regex})([\s,]*|$)", text, re.IGNORECASE)
if matched is None:
return False
Log.info(f'User is calling me {matched.group(1)}')
return True
def _is_reply(event: GroupMessage) -> bool:
for segment in event.message_chain:
segment: MessageSegment
if segment.type != MessageType.QUOTE:
continue
if segment.data['senderId'] == event.self_id:
return True
return False
event.to_me = any([_is_at(event), _is_reply(event), _is_nick(event)])
if matched is not None:
event.to_me = True
nickname = matched.group(1)
Log.info(f'User is calling me {nickname}')
plain.data['text'] = text[matched.end():]
event.message_chain.insert(0, plain)
return event
def process_reply(bot: "Bot", event: GroupMessage) -> GroupMessage:
reply = event.message_chain.extract_first(MessageType.QUOTE)
if reply is not None:
if reply.data['senderId'] == event.self_id:
event.to_me = True
else:
event.message_chain.insert(0, reply)
return event
async def process_event(bot: "Bot", event: Event) -> None:
if isinstance(event, MessageEvent):
event.message_chain.reduce()
Log.debug(event.message_chain)
event = process_source(bot, event)
if isinstance(event, GroupMessage):
event = process_nick(bot, event)
event = process_at(bot, event)
event = process_reply(bot, event)
await handle_event(bot, event)

View File

@@ -62,7 +62,7 @@ class Driver(abc.ABC):
:说明: 已连接的 Bot
"""
def register_adapter(self, name: str, adapter: Type["Bot"]):
def register_adapter(self, name: str, adapter: Type["Bot"], **kwargs):
"""
:说明:
@@ -74,7 +74,7 @@ class Driver(abc.ABC):
* ``adapter: Type[Bot]``: 适配器 Class
"""
self._adapters[name] = adapter
adapter.register(self, self.config)
adapter.register(self, self.config, **kwargs)
logger.opt(
colors=True).debug(f'Succeeded to load adapter "<y>{name}</y>"')

View File

@@ -28,9 +28,39 @@ from nonebot.drivers import Driver as BaseDriver, WebSocket as BaseWebSocket
class Config(BaseSettings):
"""
FastAPI 驱动框架设置,详情参考 FastAPI 文档
"""
fastapi_openapi_url: Optional[str] = None
"""
:类型:
``Optional[str]``
:说明:
`openapi.json` 地址,默认为 `None` 即关闭
"""
fastapi_docs_url: Optional[str] = None
"""
:类型:
``Optional[str]``
:说明:
`swagger` 地址,默认为 `None` 即关闭
"""
fastapi_redoc_url: Optional[str] = None
"""
:类型:
``Optional[str]``
:说明:
`redoc` 地址,默认为 `None` 即关闭
"""
class Config:
extra = "ignore"

240
nonebot/drivers/quart.py Normal file
View File

@@ -0,0 +1,240 @@
"""
Quart 驱动适配
================
后端使用方法请参考: `Quart 文档`_
.. _Quart 文档:
https://pgjones.gitlab.io/quart/index.html
"""
import asyncio
from json.decoder import JSONDecodeError
from typing import Any, Callable, Coroutine, Dict, Optional, Type, TypeVar
import uvicorn
from nonebot.config import Config as NoneBotConfig
from nonebot.config import Env
from nonebot.drivers import Driver as BaseDriver
from nonebot.drivers import WebSocket as BaseWebSocket
from nonebot.exception import RequestDenied
from nonebot.log import logger
from nonebot.typing import overrides
try:
from quart import Quart, Request, Response
from quart import Websocket as QuartWebSocket
from quart import exceptions
from quart import request as _request
from quart import websocket as _websocket
except ImportError:
raise ValueError(
'Please install Quart by using `pip install nonebot2[quart]`')
_AsyncCallable = TypeVar("_AsyncCallable", bound=Callable[..., Coroutine])
class Driver(BaseDriver):
"""
Quart 驱动框架
:上报地址:
* ``/{adapter name}/http``: HTTP POST 上报
* ``/{adapter name}/ws``: WebSocket 上报
"""
@overrides(BaseDriver)
def __init__(self, env: Env, config: NoneBotConfig):
super().__init__(env, config)
self._server_app = Quart(self.__class__.__qualname__)
self._server_app.add_url_rule('/<adapter>/http',
methods=['POST'],
view_func=self._handle_http)
self._server_app.add_websocket('/<adapter>/ws',
view_func=self._handle_ws_reverse)
@property
@overrides(BaseDriver)
def type(self) -> str:
"""驱动名称: ``quart``"""
return 'quart'
@property
@overrides(BaseDriver)
def server_app(self) -> Quart:
"""``Quart`` 对象"""
return self._server_app
@property
@overrides(BaseDriver)
def asgi(self):
"""``Quart`` 对象"""
return self._server_app
@property
@overrides(BaseDriver)
def logger(self):
"""fastapi 使用的 logger"""
return self._server_app.logger
@overrides(BaseDriver)
def on_startup(self, func: _AsyncCallable) -> _AsyncCallable:
"""参考文档: `Startup and Shutdown <https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html>`_"""
return self.server_app.before_serving(func) # type: ignore
@overrides(BaseDriver)
def on_shutdown(self, func: _AsyncCallable) -> _AsyncCallable:
"""参考文档: `Startup and Shutdown <https://pgjones.gitlab.io/quart/how_to_guides/startup_shutdown.html>`_"""
return self.server_app.after_serving(func) # type: ignore
@overrides(BaseDriver)
def run(self,
host: Optional[str] = None,
port: Optional[int] = None,
*,
app: Optional[str] = None,
**kwargs):
"""使用 ``uvicorn`` 启动 Quart"""
super().run(host, port, app, **kwargs)
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {
"default": {
"class": "nonebot.log.LoguruHandler",
},
},
"loggers": {
"uvicorn.error": {
"handlers": ["default"],
"level": "INFO"
},
"uvicorn.access": {
"handlers": ["default"],
"level": "INFO",
},
},
}
uvicorn.run(app or self.server_app,
host=host or str(self.config.host),
port=port or self.config.port,
reload=bool(app) and self.config.debug,
debug=self.config.debug,
log_config=LOGGING_CONFIG,
**kwargs)
@overrides(BaseDriver)
async def _handle_http(self, adapter: str):
request: Request = _request
try:
data: Dict[str, Any] = await request.get_json()
except Exception as e:
raise exceptions.BadRequest()
if adapter not in self._adapters:
logger.warning(f'Unknown adapter {adapter}. '
'Please register the adapter before use.')
raise exceptions.NotFound()
BotClass = self._adapters[adapter]
headers = {k: v for k, v in request.headers.items(lower=True)}
try:
self_id = await BotClass.check_permission(self, 'http', headers,
data)
except RequestDenied as e:
raise exceptions.HTTPException(status_code=e.status_code,
description=e.reason,
name='Request Denied')
if self_id in self._clients:
logger.warning("There's already a reverse websocket connection,"
"so the event may be handled twice.")
bot = BotClass('http', self_id)
asyncio.create_task(bot.handle_message(data))
return Response('', 204)
@overrides(BaseDriver)
async def _handle_ws_reverse(self, adapter: str):
websocket: QuartWebSocket = _websocket
if adapter not in self._adapters:
logger.warning(
f'Unknown adapter {adapter}. Please register the adapter before use.'
)
raise exceptions.NotFound()
BotClass = self._adapters[adapter]
headers = {k: v for k, v in websocket.headers.items(lower=True)}
try:
self_id = await BotClass.check_permission(self, 'websocket',
headers, None)
except RequestDenied as e:
print(e.reason)
raise exceptions.HTTPException(status_code=e.status_code,
description=e.reason,
name='Request Denied')
if self_id in self._clients:
logger.warning("There's already a reverse websocket connection,"
"so the event may be handled twice.")
ws = WebSocket(websocket)
bot = BotClass('websocket', self_id, websocket=ws)
await ws.accept()
logger.opt(colors=True).info(
f"WebSocket Connection from <y>{adapter.upper()} "
f"Bot {self_id}</y> Accepted!")
self._bot_connect(bot)
try:
while not ws.closed:
data = await ws.receive()
if data is None:
continue
asyncio.create_task(bot.handle_message(data))
finally:
self._bot_disconnect(bot)
class WebSocket(BaseWebSocket):
@overrides(BaseWebSocket)
def __init__(self, websocket: QuartWebSocket):
super().__init__(websocket)
self._closed = False
@property
@overrides(BaseWebSocket)
def websocket(self) -> QuartWebSocket:
return self._websocket
@property
@overrides(BaseWebSocket)
def closed(self):
return self._closed
@overrides(BaseWebSocket)
async def accept(self):
await self.websocket.accept()
self._closed = False
@overrides(BaseWebSocket)
async def close(self):
self._closed = True
@overrides(BaseWebSocket)
async def receive(self) -> Optional[Dict[str, Any]]:
data: Optional[Dict[str, Any]] = None
try:
data = await self.websocket.receive_json()
except JSONDecodeError:
logger.warning('Received an invalid json message.')
except asyncio.CancelledError:
self._closed = True
logger.warning('WebSocket disconnected by peer.')
return data
@overrides(BaseWebSocket)
async def send(self, data: dict):
await self.websocket.send_json(data)

View File

@@ -7,7 +7,7 @@ NoneBot 内部处理并按优先级分发事件给所有事件响应器,提供
import asyncio
from datetime import datetime
from typing import Set, Type, Optional, Iterable, TYPE_CHECKING
from typing import Set, Type, TYPE_CHECKING
from nonebot.log import logger
from nonebot.rule import TrieRule
@@ -115,6 +115,7 @@ async def _check_matcher(priority: int, Matcher: Type[Matcher], bot: "Bot",
except Exception as e:
logger.opt(colors=True, exception=e).error(
f"<r><bg #f8bbd0>Rule check failed for {Matcher}.</bg #f8bbd0></r>")
return
if Matcher.temp:
try:

View File

@@ -22,7 +22,7 @@ from nonebot.typing import T_State, T_StateFactory, T_Handler, T_RuleChecker
from nonebot.rule import Rule, startswith, endswith, keyword, command, shell_command, ArgumentParser, regex
if TYPE_CHECKING:
from nonebot.adapters import Bot, Event
from nonebot.adapters import Bot, Event, MessageSegment
plugins: Dict[str, "Plugin"] = {}
"""
@@ -745,11 +745,7 @@ class MatcherGroup:
self.matchers.append(matcher)
return matcher
def on_startswith(self,
msg: str,
rule: Optional[Optional[Union[Rule,
T_RuleChecker]]] = None,
**kwargs) -> Type[Matcher]:
def on_startswith(self, msg: str, **kwargs) -> Type[Matcher]:
"""
:说明:
@@ -771,12 +767,14 @@ class MatcherGroup:
- ``Type[Matcher]``
"""
return self.on_message(rule=startswith(msg) & rule, **kwargs)
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_startswith(msg, **final_kwargs)
self.matchers.append(matcher)
return matcher
def on_endswith(self,
msg: str,
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = None,
**kwargs) -> Type[Matcher]:
def on_endswith(self, msg: str, **kwargs) -> Type[Matcher]:
"""
:说明:
@@ -798,12 +796,14 @@ class MatcherGroup:
- ``Type[Matcher]``
"""
return self.on_message(rule=endswith(msg) & rule, **kwargs)
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_endswith(msg, **final_kwargs)
self.matchers.append(matcher)
return matcher
def on_keyword(self,
keywords: Set[str],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
**kwargs) -> Type[Matcher]:
def on_keyword(self, keywords: Set[str], **kwargs) -> Type[Matcher]:
"""
:说明:
@@ -825,11 +825,15 @@ class MatcherGroup:
- ``Type[Matcher]``
"""
return self.on_message(rule=keyword(*keywords) & rule, **kwargs)
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_keyword(keywords, **final_kwargs)
self.matchers.append(matcher)
return matcher
def on_command(self,
cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
**kwargs) -> Type[Matcher]:
"""
@@ -842,8 +846,8 @@ class MatcherGroup:
:参数:
* ``cmd: Union[str, Tuple[str, ...]]``: 指定命令内容
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``aliases: Optional[Set[Union[str, Tuple[str, ...]]]]``: 命令别名
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
@@ -856,27 +860,15 @@ class MatcherGroup:
- ``Type[Matcher]``
"""
async def _strip_cmd(bot: "Bot", event: "Event", state: T_State):
message = event.get_message()
segment = message.pop(0)
new_message = message.__class__(
str(segment)
[len(state["_prefix"]["raw_command"]):].strip()) # type: ignore
for new_segment in reversed(new_message):
message.insert(0, new_segment)
handlers = kwargs.pop("handlers", [])
handlers.insert(0, _strip_cmd)
commands = set([cmd]) | (aliases or set())
return self.on_message(rule=command(*commands) & rule,
handlers=handlers,
**kwargs)
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_command(cmd, aliases=aliases, **final_kwargs)
self.matchers.append(matcher)
return matcher
def on_shell_command(self,
cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = None,
aliases: Optional[Set[Union[str, Tuple[str,
...]]]] = None,
parser: Optional[ArgumentParser] = None,
@@ -893,9 +885,9 @@ class MatcherGroup:
:参数:
* ``cmd: Union[str, Tuple[str, ...]]``: 指定命令内容
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``aliases: Optional[Set[Union[str, Tuple[str, ...]]]]``: 命令别名
* ``parser: Optional[ArgumentParser]``: ``nonebot.rule.ArgumentParser`` 对象
* ``rule: Optional[Union[Rule, T_RuleChecker]]``: 事件响应规则
* ``permission: Optional[Permission]``: 事件响应权限
* ``handlers: Optional[List[T_Handler]]``: 事件处理函数列表
* ``temp: bool``: 是否为临时事件响应器(仅执行一次)
@@ -908,29 +900,19 @@ class MatcherGroup:
- ``Type[Matcher]``
"""
async def _strip_cmd(bot: "Bot", event: "Event", state: T_State):
message = event.get_message()
segment = message.pop(0)
new_message = message.__class__(
str(segment)
[len(state["_prefix"]["raw_command"]):].strip()) # type: ignore
for new_segment in reversed(new_message):
message.insert(0, new_segment)
handlers = kwargs.pop("handlers", [])
handlers.insert(0, _strip_cmd)
commands = set([cmd]) | (aliases or set())
return self.on_message(rule=shell_command(*commands, parser=parser) &
rule,
handlers=handlers,
**kwargs)
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_shell_command(cmd,
aliases=aliases,
parser=parser,
**final_kwargs)
self.matchers.append(matcher)
return matcher
def on_regex(self,
pattern: str,
flags: Union[int, re.RegexFlag] = 0,
rule: Optional[Rule] = None,
**kwargs) -> Type[Matcher]:
"""
:说明:
@@ -956,7 +938,12 @@ class MatcherGroup:
- ``Type[Matcher]``
"""
return self.on_message(rule=regex(pattern, flags) & rule, **kwargs)
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
final_kwargs.pop("type", None)
matcher = on_regex(pattern, flags=flags, **final_kwargs)
self.matchers.append(matcher)
return matcher
def load_plugin(module_path: str) -> Optional[Plugin]:

View File

@@ -320,8 +320,8 @@ class MatcherGroup:
def on_startswith(
self,
*,
msg: str,
*,
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
@@ -334,8 +334,8 @@ class MatcherGroup:
def on_endswith(
self,
*,
msg: str,
*,
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
@@ -348,8 +348,8 @@ class MatcherGroup:
def on_keyword(
self,
*,
keywords: Set[str],
*,
rule: Optional[Optional[Union[Rule, T_RuleChecker]]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
@@ -362,10 +362,10 @@ class MatcherGroup:
def on_command(
self,
*,
cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
temp: bool = ...,
@@ -377,11 +377,11 @@ class MatcherGroup:
def on_shell_command(
self,
*,
cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = ...,
parser: Optional[ArgumentParser] = ...,
*,
rule: Optional[Union[Rule, T_RuleChecker]] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,
temp: bool = ...,
@@ -393,9 +393,9 @@ class MatcherGroup:
def on_regex(
self,
*,
pattern: str,
flags: Union[int, re.RegexFlag] = 0,
*,
rule: Optional[Rule] = ...,
permission: Optional[Permission] = ...,
handlers: Optional[List[T_Handler]] = ...,

View File

@@ -25,7 +25,7 @@ from nonebot.exception import ParserExit
from nonebot.typing import T_State, T_RuleChecker
if TYPE_CHECKING:
from nonebot.adapters import Bot, Event
from nonebot.adapters import Bot, Event, MessageSegment
class Rule:

264
poetry.lock generated
View File

@@ -1,3 +1,11 @@
[[package]]
name = "aiofiles"
version = "0.6.0"
description = "File support for asyncio."
category = "main"
optional = true
python-versions = "*"
[[package]]
name = "alabaster"
version = "0.7.12"
@@ -17,6 +25,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
pytz = ">=2015.7"
[[package]]
name = "blinker"
version = "1.4"
description = "Fast, simple object-to-object and broadcast signaling"
category = "main"
optional = true
python-versions = "*"
[[package]]
name = "certifi"
version = "2020.12.5"
@@ -25,14 +41,6 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "chardet"
version = "4.0.0"
description = "Universal encoding detector for Python 2 and 3"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "click"
version = "7.1.2"
@@ -83,6 +91,26 @@ category = "main"
optional = false
python-versions = "*"
[[package]]
name = "h2"
version = "4.0.0"
description = "HTTP/2 State-Machine based protocol implementation"
category = "main"
optional = true
python-versions = ">=3.6.1"
[package.dependencies]
hpack = ">=4.0,<5"
hyperframe = ">=6.0,<7"
[[package]]
name = "hpack"
version = "4.0.0"
description = "Pure-Python HPACK header compression"
category = "main"
optional = true
python-versions = ">=3.6.1"
[[package]]
name = "html2text"
version = "2020.1.16"
@@ -135,13 +163,43 @@ sniffio = "*"
brotli = ["brotlipy (>=0.7.0,<0.8.0)"]
http2 = ["h2 (>=3.0.0,<4.0.0)"]
[[package]]
name = "hypercorn"
version = "0.11.2"
description = "A ASGI Server based on Hyper libraries and inspired by Gunicorn."
category = "main"
optional = true
python-versions = ">=3.7"
[package.dependencies]
h11 = "*"
h2 = ">=3.1.0"
priority = "*"
toml = "*"
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
wsproto = ">=0.14.0"
[package.extras]
h3 = ["aioquic (>=0.9.0,<1.0)"]
tests = ["hypothesis", "mock", "pytest", "pytest-asyncio", "pytest-cov", "pytest-trio", "trio"]
trio = ["trio (>=0.11.0)"]
uvloop = ["uvloop"]
[[package]]
name = "hyperframe"
version = "6.0.0"
description = "HTTP/2 framing layer for Python"
category = "main"
optional = true
python-versions = ">=3.6.1"
[[package]]
name = "idna"
version = "2.10"
version = "3.1"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
python-versions = ">=3.4"
[[package]]
name = "imagesize"
@@ -151,11 +209,19 @@ category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "itsdangerous"
version = "1.1.0"
description = "Various helpers to pass data to untrusted environments and back."
category = "main"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "jinja2"
version = "2.11.3"
description = "A very fast and expressive template engine."
category = "dev"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
@@ -184,7 +250,7 @@ dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3
name = "markupsafe"
version = "1.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "dev"
category = "main"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
@@ -199,6 +265,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies]
pyparsing = ">=2.0.2"
[[package]]
name = "priority"
version = "1.3.0"
description = "A pure-Python implementation of the HTTP/2 priority tree"
category = "main"
optional = true
python-versions = "*"
[[package]]
name = "pydantic"
version = "1.7.3"
@@ -270,22 +344,38 @@ category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "quart"
version = "0.14.1"
description = "A Python ASGI web microframework with the same API as Flask"
category = "main"
optional = true
python-versions = ">=3.7.0"
[package.dependencies]
aiofiles = "*"
blinker = "*"
click = "*"
hypercorn = ">=0.7.0"
itsdangerous = "*"
jinja2 = "*"
toml = "*"
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
werkzeug = ">=1.0.0"
[package.extras]
dotenv = ["python-dotenv"]
[[package]]
name = "requests"
version = "2.25.1"
version = "2.15.1"
description = "Python HTTP for Humans."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
certifi = ">=2017.4.17"
chardet = ">=3.0.2,<5"
idna = ">=2.5,<3"
urllib3 = ">=1.21.1,<1.27"
python-versions = "*"
[package.extras]
security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
security = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)"]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
[[package]]
@@ -453,6 +543,14 @@ python-versions = ">=3.6"
[package.extras]
full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"]
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "main"
optional = true
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "typing-extensions"
version = "3.7.4.3"
@@ -480,19 +578,6 @@ category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "urllib3"
version = "1.26.3"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "uvicorn"
version = "0.11.8"
@@ -527,6 +612,18 @@ category = "main"
optional = false
python-versions = ">=3.6.1"
[[package]]
name = "werkzeug"
version = "1.0.1"
description = "The comprehensive WSGI web application library."
category = "main"
optional = true
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
dev = ["pytest", "pytest-timeout", "coverage", "tox", "sphinx", "pallets-sphinx-themes", "sphinx-issues"]
watchdog = ["watchdog"]
[[package]]
name = "win32-setctime"
version = "1.0.3"
@@ -538,6 +635,17 @@ python-versions = ">=3.5"
[package.extras]
dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"]
[[package]]
name = "wsproto"
version = "1.0.0"
description = "WebSockets state-machine based protocol implementation"
category = "main"
optional = true
python-versions = ">=3.6.1"
[package.dependencies]
h11 = ">=0.9.0,<1"
[[package]]
name = "yapf"
version = "0.30.0"
@@ -546,12 +654,20 @@ category = "dev"
optional = false
python-versions = "*"
[extras]
all = ["Quart"]
quart = ["Quart"]
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
content-hash = "9aa4fde8078788e6a12866ba4eb5d17ec6237355c663d6ea74040b6e165cdcf1"
content-hash = "11273401518ba0c93c5e381c6f0c1be02d60106bcda715c7ee7a06a78a8871d5"
[metadata.files]
aiofiles = [
{file = "aiofiles-0.6.0-py3-none-any.whl", hash = "sha256:bd3019af67f83b739f8e4053c6c0512a7f545b9a8d91aaeab55e6e0f9d123c27"},
{file = "aiofiles-0.6.0.tar.gz", hash = "sha256:e0281b157d3d5d59d803e3f4557dcc9a3dff28a4dd4829a9ff478adae50ca092"},
]
alabaster = [
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"},
@@ -560,14 +676,13 @@ babel = [
{file = "Babel-2.9.0-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"},
{file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"},
]
blinker = [
{file = "blinker-1.4.tar.gz", hash = "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"},
]
certifi = [
{file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
{file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"},
]
chardet = [
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
]
click = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"},
@@ -588,6 +703,14 @@ h11 = [
{file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"},
{file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"},
]
h2 = [
{file = "h2-4.0.0-py3-none-any.whl", hash = "sha256:ac9e293a1990b339d5d71b19c5fe630e3dd4d768c620d1730d355485323f1b25"},
{file = "h2-4.0.0.tar.gz", hash = "sha256:bb7ac7099dd67a857ed52c815a6192b6b1f5ba6b516237fc24a085341340593d"},
]
hpack = [
{file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"},
{file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"},
]
html2text = [
{file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"},
{file = "html2text-2020.1.16.tar.gz", hash = "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb"},
@@ -614,14 +737,26 @@ httpx = [
{file = "httpx-0.16.1-py3-none-any.whl", hash = "sha256:9cffb8ba31fac6536f2c8cde30df859013f59e4bcc5b8d43901cb3654a8e0a5b"},
{file = "httpx-0.16.1.tar.gz", hash = "sha256:126424c279c842738805974687e0518a94c7ae8d140cd65b9c4f77ac46ffa537"},
]
hypercorn = [
{file = "Hypercorn-0.11.2-py3-none-any.whl", hash = "sha256:8007c10f81566920f8ae12c0e26e146f94ca70506da964b5a727ad610aa1d821"},
{file = "Hypercorn-0.11.2.tar.gz", hash = "sha256:5ba1e719c521080abd698ff5781a2331e34ef50fc1c89a50960538115a896a9a"},
]
hyperframe = [
{file = "hyperframe-6.0.0-py3-none-any.whl", hash = "sha256:a51026b1591cac726fc3d0b7994fbc7dc5efab861ef38503face2930fd7b2d34"},
{file = "hyperframe-6.0.0.tar.gz", hash = "sha256:742d2a4bc3152a340a49d59f32e33ec420aa8e7054c1444ef5c7efff255842f1"},
]
idna = [
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
{file = "idna-3.1-py3-none-any.whl", hash = "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16"},
{file = "idna-3.1.tar.gz", hash = "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"},
]
imagesize = [
{file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
{file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"},
]
itsdangerous = [
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
{file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"},
]
jinja2 = [
{file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"},
{file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"},
@@ -649,45 +784,30 @@ markupsafe = [
{file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"},
{file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"},
{file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
{file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"},
{file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"},
{file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"},
{file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"},
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
]
packaging = [
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"},
]
priority = [
{file = "priority-1.3.0-py2.py3-none-any.whl", hash = "sha256:be4fcb94b5e37cdeb40af5533afe6dd603bd665fe9c8b3052610fc1001d5d1eb"},
{file = "priority-1.3.0.tar.gz", hash = "sha256:6bc1961a6d7fcacbfc337769f1a382c8e746566aaa365e78047abe9f66b2ffbe"},
]
pydantic = [
{file = "pydantic-1.7.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd"},
{file = "pydantic-1.7.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a4143c8d0c456a093387b96e0f5ee941a950992904d88bc816b4f0e72c9a0009"},
@@ -735,9 +855,13 @@ pytz = [
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
]
quart = [
{file = "Quart-0.14.1-py3-none-any.whl", hash = "sha256:7b13786e07541cc9ce1466fdc6a6ccd5f36eb39118edd25a42d617593cd17707"},
{file = "Quart-0.14.1.tar.gz", hash = "sha256:429c5b4ff27e1d2f9ca0aacc38f6aba0ff49b38b815448bf24b613d3de12ea02"},
]
requests = [
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
{file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
{file = "requests-2.15.1-py2.py3-none-any.whl", hash = "sha256:ff753b2196cd18b1bbeddc9dcd5c864056599f7a7d9a4fb5677e723efa2b7fb9"},
{file = "requests-2.15.1.tar.gz", hash = "sha256:e5659b9315a0610505e050bb7190bf6fa2ccee1ac295f2b760ef9d8a03ebbb2e"},
]
rfc3986 = [
{file = "rfc3986-1.4.0-py2.py3-none-any.whl", hash = "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50"},
@@ -784,6 +908,10 @@ starlette = [
{file = "starlette-0.13.6-py3-none-any.whl", hash = "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9"},
{file = "starlette-0.13.6.tar.gz", hash = "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc"},
]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
typing-extensions = [
{file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"},
{file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
@@ -795,10 +923,6 @@ unify = [
untokenize = [
{file = "untokenize-0.1.1.tar.gz", hash = "sha256:3865dbbbb8efb4bb5eaa72f1be7f3e0be00ea8b7f125c69cbd1f5fda926f37a2"},
]
urllib3 = [
{file = "urllib3-1.26.3-py2.py3-none-any.whl", hash = "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80"},
{file = "urllib3-1.26.3.tar.gz", hash = "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73"},
]
uvicorn = [
{file = "uvicorn-0.11.8-py3-none-any.whl", hash = "sha256:4b70ddb4c1946e39db9f3082d53e323dfd50634b95fd83625d778729ef1730ef"},
{file = "uvicorn-0.11.8.tar.gz", hash = "sha256:46a83e371f37ea7ff29577d00015f02c942410288fb57def6440f2653fff1d26"},
@@ -838,10 +962,18 @@ websockets = [
{file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"},
{file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"},
]
werkzeug = [
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
{file = "Werkzeug-1.0.1.tar.gz", hash = "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c"},
]
win32-setctime = [
{file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"},
{file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"},
]
wsproto = [
{file = "wsproto-1.0.0-py3-none-any.whl", hash = "sha256:d8345d1808dd599b5ffb352c25a367adb6157e664e140dbecba3f9bc007edb9f"},
{file = "wsproto-1.0.0.tar.gz", hash = "sha256:868776f8456997ad0d9720f7322b746bbe9193751b5b290b7f924659377c8c38"},
]
yapf = [
{file = "yapf-0.30.0-py2.py3-none-any.whl", hash = "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9"},
{file = "yapf-0.30.0.tar.gz", hash = "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427"},

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "nonebot2"
version = "2.0.0-alpha.9"
version = "2.0.0a10"
description = "An asynchronous python bot framework."
authors = ["yanyongyu <yanyongyu_1@126.com>"]
license = "MIT"
@@ -31,12 +31,17 @@ fastapi = "^0.63.0"
uvicorn = "^0.11.5"
websockets = "^8.1"
pydantic = {extras = ["dotenv", "typing_extensions"], version = "^1.7.3"}
Quart = {version = "^0.14.1", optional = true}
[tool.poetry.dev-dependencies]
yapf = "^0.30.0"
sphinx = "^3.4.1"
sphinx-markdown-builder = { git = "https://github.com/nonebot/sphinx-markdown-builder.git" }
[tool.poetry.extras]
quart = ["quart"]
all = ["quart"]
# [[tool.poetry.source]]
# name = "aliyun"
# url = "https://mirrors.aliyun.com/pypi/simple/"

View File

@@ -1,13 +1,20 @@
from nonebot.plugin import on_message
from nonebot.plugin import on_keyword, on_command
from nonebot.rule import to_me
from nonebot.adapters.mirai import Bot, MessageEvent
message_test = on_message()
message_test = on_keyword({'reply'}, rule=to_me())
@message_test.handle()
async def _message(bot: Bot, event: MessageEvent):
text = event.get_plaintext()
if not text:
return
reversed_text = ''.join(reversed(text))
await bot.send(event, reversed_text, at_sender=True)
await bot.send(event, text, at_sender=True)
command_test = on_command('miecho')
@command_test.handle()
async def _echo(bot: Bot, event: MessageEvent):
text = event.get_plaintext()
await bot.send(event, text, at_sender=True)