mirror of
https://github.com/nonebot/nonebot2.git
synced 2024-11-28 07:39:45 +08:00
commit
8821218bfd
4
.github/workflows/build_docs.yml
vendored
4
.github/workflows/build_docs.yml
vendored
@ -2,7 +2,7 @@ name: Build API Doc
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize, reopened ]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@ -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
|
||||
|
@ -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/"
|
||||
|
@ -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` 对象了。
|
||||
|
@ -43,6 +43,9 @@
|
||||
* [nonebot.drivers.fastapi](drivers/fastapi.html)
|
||||
|
||||
|
||||
* [nonebot.drivers.quart](drivers/quart.html)
|
||||
|
||||
|
||||
* [nonebot.adapters](adapters/)
|
||||
|
||||
|
||||
|
@ -965,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 模块
|
||||
|
||||
|
||||
@ -1070,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`
|
||||
|
62
docs/api/drivers/quart.md
Normal file
62
docs/api/drivers/quart.md
Normal 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
|
@ -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)
|
||||
|
@ -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**使用,可以将消息内容由特殊纯文本转为富文本
|
||||
|
@ -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
|
||||
|
@ -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>`_
|
||||
|
12
docs_build/drivers/quart.rst
Normal file
12
docs_build/drivers/quart.rst
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
contentSidebar: true
|
||||
sidebarDepth: 0
|
||||
---
|
||||
|
||||
NoneBot.drivers.quart 模块
|
||||
==========================
|
||||
|
||||
.. automodule:: nonebot.drivers.quart
|
||||
:members:
|
||||
:private-members:
|
||||
:show-inheritance:
|
@ -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)
|
||||
|
@ -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(
|
||||
await process_event(
|
||||
bot=self,
|
||||
event=await check_tome(
|
||||
bot=self,
|
||||
event=Event.new({
|
||||
**message,
|
||||
'self_id': self.self_id,
|
||||
}),
|
||||
),
|
||||
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)
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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()]}>'
|
||||
|
@ -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):
|
||||
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()})
|
||||
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)])
|
||||
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 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 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)
|
240
nonebot/drivers/quart.py
Normal file
240
nonebot/drivers/quart.py
Normal 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)
|
@ -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
|
||||
|
@ -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"] = {}
|
||||
"""
|
||||
|
@ -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
264
poetry.lock
generated
@ -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"},
|
||||
|
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "nonebot2"
|
||||
version = "2.0.0a9.post1"
|
||||
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/"
|
||||
|
@ -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)
|
Loading…
Reference in New Issue
Block a user