🔀 Merge pull request #202

Pre Release 2.0.0a10
This commit is contained in:
Ju4tCode 2021-02-07 23:42:08 +08:00 committed by GitHub
commit 8821218bfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 857 additions and 170 deletions

View File

@ -2,7 +2,7 @@ name: Build API Doc
on: on:
pull_request: pull_request:
types: [ opened, synchronize, reopened ] types: [opened, synchronize, reopened]
jobs: jobs:
build: build:
@ -30,7 +30,7 @@ jobs:
- name: Set up dependencies - name: Set up dependencies
run: | run: |
poetry install poetry install -E all
- name: Build Doc - name: Build Doc
run: poetry run sphinx-build -M markdown ./docs_build ./build run: poetry run sphinx-build -M markdown ./docs_build ./build

View File

@ -198,6 +198,10 @@ module.exports = context => ({
title: "nonebot.drivers.fastapi 模块", title: "nonebot.drivers.fastapi 模块",
path: "drivers/fastapi" path: "drivers/fastapi"
}, },
{
title: "nonebot.drivers.quart 模块",
path: "drivers/quart"
},
{ {
title: "nonebot.adapters 模块", title: "nonebot.adapters 模块",
path: "adapters/" path: "adapters/"

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

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

View File

@ -965,15 +965,40 @@ CQHTTP 协议 MessageSegment 适配。具体方法参考 [mirai-api-http 消息
基类:[`nonebot.adapters.Message`](README.md#nonebot.adapters.Message) 基类:[`nonebot.adapters.Message`](README.md#nonebot.adapters.Message)
Mirai 协议 Messaqge 适配 Mirai 协议 Message 适配
由于Mirai协议的Message实现较为特殊, 故使用MessageChain命名 由于Mirai协议的Message实现较为特殊, 故使用MessageChain命名
### `reduce()`
* **说明**
忽略为空的消息段, 合并相邻的纯文本消息段
### `export()` ### `export()`
导出为可以被正常json序列化的数组 导出为可以被正常json序列化的数组
### `extract_first(*type)`
* **说明**
弹出该消息链的第一个消息
* **参数**
* \*type: MessageType: 指定的消息类型, 当指定后如类型不匹配不弹出
# NoneBot.adapters.mirai.utils 模块 # NoneBot.adapters.mirai.utils 模块
@ -1070,20 +1095,6 @@ mirai-api-http 协议事件,字段与 mirai-api-http 一致。各事件字段
> * `MEMBER`: 普通群成员 > * `MEMBER`: 普通群成员
## _class_ `MessageChain`
基类:[`nonebot.adapters.Message`](README.md#nonebot.adapters.Message)
Mirai 协议 Messaqge 适配
由于Mirai协议的Message实现较为特殊, 故使用MessageChain命名
### `export()`
导出为可以被正常json序列化的数组
## _class_ `MessageEvent` ## _class_ `MessageEvent`
基类:`nonebot.adapters.mirai.event.base.Event` 基类:`nonebot.adapters.mirai.event.base.Event`

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

@ -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: def sync_checker(bot: Bot, event: Event, state: T_State) -> bool:
return True return True
def check(arg1, args2): def check(arg1, arg2):
async def _checker(bot: Bot, event: Event, state: T_State) -> bool: async def _checker(bot: Bot, event: Event, state: T_State) -> bool:
return bool(arg1 + arg2) return bool(arg1 + arg2)

View File

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

View File

@ -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.exception <exception.html>`_
- `nonebot.drivers <drivers/>`_ - `nonebot.drivers <drivers/>`_
- `nonebot.drivers.fastapi <drivers/fastapi.html>`_ - `nonebot.drivers.fastapi <drivers/fastapi.html>`_
- `nonebot.drivers.quart <drivers/quart.html>`_
- `nonebot.adapters <adapters/>`_ - `nonebot.adapters <adapters/>`_
- `nonebot.adapters.cqhttp <adapters/cqhttp.html>`_ - `nonebot.adapters.cqhttp <adapters/cqhttp.html>`_
- `nonebot.adapters.ding <adapters/ding.html>`_ - `nonebot.adapters.ding <adapters/ding.html>`_

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``: 网络错误 - ``NetworkError``: 网络错误
- ``ActionFailed``: API 调用失败 - ``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) msg = message if isinstance(message, Message) else Message(message)
at_sender = at_sender and getattr(event, "user_id", None) at_sender = at_sender and getattr(event, "user_id", None)

View File

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

View File

@ -13,7 +13,7 @@ from .request import *
__all__ = [ __all__ = [
'Event', 'GroupChatInfo', 'GroupInfo', 'PrivateChatInfo', 'UserPermission', 'Event', 'GroupChatInfo', 'GroupInfo', 'PrivateChatInfo', 'UserPermission',
'MessageChain', 'MessageEvent', 'GroupMessage', 'FriendMessage', 'MessageSource', 'MessageEvent', 'GroupMessage', 'FriendMessage',
'TempMessage', 'NoticeEvent', 'MuteEvent', 'BotMuteEvent', 'BotUnmuteEvent', 'TempMessage', 'NoticeEvent', 'MuteEvent', 'BotMuteEvent', 'BotUnmuteEvent',
'MemberMuteEvent', 'MemberUnmuteEvent', 'BotJoinGroupEvent', 'MemberMuteEvent', 'MemberUnmuteEvent', 'BotJoinGroupEvent',
'BotLeaveEventActive', 'BotLeaveEventKick', 'MemberJoinEvent', '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 from nonebot.typing import overrides
@ -8,9 +9,15 @@ from ..message import MessageChain
from .base import Event, GroupChatInfo, PrivateChatInfo from .base import Event, GroupChatInfo, PrivateChatInfo
class MessageSource(BaseModel):
id: int
time: datetime
class MessageEvent(Event): class MessageEvent(Event):
"""消息事件基类""" """消息事件基类"""
message_chain: MessageChain = Field(alias='messageChain') message_chain: MessageChain = Field(alias='messageChain')
source: Optional[MessageSource] = None
sender: Any sender: Any
@overrides(Event) @overrides(Event)

View File

@ -44,8 +44,9 @@ class MessageSegment(BaseMessageSegment):
@overrides(BaseMessageSegment) @overrides(BaseMessageSegment)
def __str__(self) -> str: def __str__(self) -> str:
if self.is_text(): return self.data['text'] if self.is_text() else repr(self)
return self.data.get('text', '')
def __repr__(self) -> str:
return '[mirai:%s]' % ','.join([ return '[mirai:%s]' % ','.join([
self.type.value, self.type.value,
*map( *map(
@ -267,18 +268,20 @@ class MessageSegment(BaseMessageSegment):
class MessageChain(BaseMessage): class MessageChain(BaseMessage):
""" """
Mirai 协议 Messaqge 适配 Mirai 协议 Message 适配
由于Mirai协议的Message实现较为特殊, 故使用MessageChain命名 由于Mirai协议的Message实现较为特殊, 故使用MessageChain命名
""" """
@overrides(BaseMessage) @overrides(BaseMessage)
def __init__(self, message: Union[List[Dict[str, Any]], def __init__(self, message: Union[List[Dict[str,
Iterable[MessageSegment], MessageSegment], Any]], Iterable[MessageSegment],
**kwargs): MessageSegment, str], **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
if isinstance(message, MessageSegment): if isinstance(message, MessageSegment):
self.append(message) self.append(message)
elif isinstance(message, str):
self.append(MessageSegment.plain(text=message))
elif isinstance(message, Iterable): elif isinstance(message, Iterable):
self.extend(self._construct(message)) self.extend(self._construct(message))
else: else:
@ -286,6 +289,19 @@ class MessageChain(BaseMessage):
f'Type {type(message).__name__} is not supported in mirai adapter.' 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) @overrides(BaseMessage)
def _construct( def _construct(
self, message: Union[List[Dict[str, Any]], Iterable[MessageSegment]] 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 *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: def __repr__(self) -> str:
return f'<{self.__class__.__name__} {[*self.copy()]}>' 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 import nonebot.exception as exception
from nonebot.log import logger from nonebot.log import logger
from nonebot.message import handle_event
from nonebot.utils import escape_tag, logger_wrapper from nonebot.utils import escape_tag, logger_wrapper
from .event import Event, GroupMessage from .event import Event, GroupMessage, MessageEvent, MessageSource
from .message import MessageSegment, MessageType from .message import MessageType
if TYPE_CHECKING: if TYPE_CHECKING:
from .bot import Bot from .bot import Bot
@ -20,23 +21,28 @@ _AnyCallable = TypeVar("_AnyCallable", bound=Callable)
class Log: 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 @classmethod
def info(cls, message: Any): def info(cls, message: Any):
cls._log('INFO', str(message)) cls.log('INFO', str(message))
@classmethod @classmethod
def debug(cls, message: Any): def debug(cls, message: Any):
cls._log('DEBUG', str(message)) cls.log('DEBUG', str(message))
@classmethod @classmethod
def warn(cls, message: Any): def warn(cls, message: Any):
cls._log('WARNING', str(message)) cls.log('WARNING', str(message))
@classmethod @classmethod
def error(cls, message: Any, exception: Optional[Exception] = None): 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): class ActionFailed(exception.ActionFailed):
@ -118,39 +124,55 @@ def argument_validation(function: _AnyCallable) -> _AnyCallable:
return wrapper # type: ignore return wrapper # type: ignore
async def check_tome(bot: "Bot", event: "Event") -> "Event": def process_source(bot: "Bot", event: MessageEvent) -> MessageEvent:
if not isinstance(event, GroupMessage): source = event.message_chain.extract_first(MessageType.SOURCE)
return event if source is not None:
event.source = MessageSource.parse_obj(source.data)
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)])
return event 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
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 import asyncio
from datetime import datetime 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.log import logger
from nonebot.rule import TrieRule from nonebot.rule import TrieRule

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 from nonebot.rule import Rule, startswith, endswith, keyword, command, shell_command, ArgumentParser, regex
if TYPE_CHECKING: if TYPE_CHECKING:
from nonebot.adapters import Bot, Event from nonebot.adapters import Bot, Event, MessageSegment
plugins: Dict[str, "Plugin"] = {} plugins: Dict[str, "Plugin"] = {}
""" """

View File

@ -25,7 +25,7 @@ from nonebot.exception import ParserExit
from nonebot.typing import T_State, T_RuleChecker from nonebot.typing import T_State, T_RuleChecker
if TYPE_CHECKING: if TYPE_CHECKING:
from nonebot.adapters import Bot, Event from nonebot.adapters import Bot, Event, MessageSegment
class Rule: 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]] [[package]]
name = "alabaster" name = "alabaster"
version = "0.7.12" version = "0.7.12"
@ -17,6 +25,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[package.dependencies] [package.dependencies]
pytz = ">=2015.7" 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]] [[package]]
name = "certifi" name = "certifi"
version = "2020.12.5" version = "2020.12.5"
@ -25,14 +41,6 @@ category = "main"
optional = false optional = false
python-versions = "*" 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]] [[package]]
name = "click" name = "click"
version = "7.1.2" version = "7.1.2"
@ -83,6 +91,26 @@ category = "main"
optional = false optional = false
python-versions = "*" 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]] [[package]]
name = "html2text" name = "html2text"
version = "2020.1.16" version = "2020.1.16"
@ -135,13 +163,43 @@ sniffio = "*"
brotli = ["brotlipy (>=0.7.0,<0.8.0)"] brotli = ["brotlipy (>=0.7.0,<0.8.0)"]
http2 = ["h2 (>=3.0.0,<4.0.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]] [[package]]
name = "idna" name = "idna"
version = "2.10" version = "3.1"
description = "Internationalized Domain Names in Applications (IDNA)" description = "Internationalized Domain Names in Applications (IDNA)"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=3.4"
[[package]] [[package]]
name = "imagesize" name = "imagesize"
@ -151,11 +209,19 @@ category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 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]] [[package]]
name = "jinja2" name = "jinja2"
version = "2.11.3" version = "2.11.3"
description = "A very fast and expressive template engine." description = "A very fast and expressive template engine."
category = "dev" category = "main"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 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" name = "markupsafe"
version = "1.1.1" version = "1.1.1"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
category = "dev" category = "main"
optional = false optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 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] [package.dependencies]
pyparsing = ">=2.0.2" 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]] [[package]]
name = "pydantic" name = "pydantic"
version = "1.7.3" version = "1.7.3"
@ -270,22 +344,38 @@ category = "dev"
optional = false optional = false
python-versions = "*" 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]] [[package]]
name = "requests" name = "requests"
version = "2.25.1" version = "2.15.1"
description = "Python HTTP for Humans." description = "Python HTTP for Humans."
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" python-versions = "*"
[package.dependencies]
certifi = ">=2017.4.17"
chardet = ">=3.0.2,<5"
idna = ">=2.5,<3"
urllib3 = ">=1.21.1,<1.27"
[package.extras] [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"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
[[package]] [[package]]
@ -453,6 +543,14 @@ python-versions = ">=3.6"
[package.extras] [package.extras]
full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"] 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]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "3.7.4.3" version = "3.7.4.3"
@ -480,19 +578,6 @@ category = "dev"
optional = false optional = false
python-versions = "*" 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]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.11.8" version = "0.11.8"
@ -527,6 +612,18 @@ category = "main"
optional = false optional = false
python-versions = ">=3.6.1" 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]] [[package]]
name = "win32-setctime" name = "win32-setctime"
version = "1.0.3" version = "1.0.3"
@ -538,6 +635,17 @@ python-versions = ">=3.5"
[package.extras] [package.extras]
dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] 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]] [[package]]
name = "yapf" name = "yapf"
version = "0.30.0" version = "0.30.0"
@ -546,12 +654,20 @@ category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
[extras]
all = ["Quart"]
quart = ["Quart"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7" python-versions = "^3.7"
content-hash = "9aa4fde8078788e6a12866ba4eb5d17ec6237355c663d6ea74040b6e165cdcf1" content-hash = "11273401518ba0c93c5e381c6f0c1be02d60106bcda715c7ee7a06a78a8871d5"
[metadata.files] [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 = [ alabaster = [
{file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"},
{file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, {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-py2.py3-none-any.whl", hash = "sha256:9d35c22fcc79893c3ecc85ac4a56cde1ecf3f19c540bba0922308a6c06ca6fa5"},
{file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"}, {file = "Babel-2.9.0.tar.gz", hash = "sha256:da031ab54472314f210b0adcff1588ee5d1d1d0ba4dbd07b94dba82bde791e05"},
] ]
blinker = [
{file = "blinker-1.4.tar.gz", hash = "sha256:471aee25f3992bd325afa3772f1063dbdbbca947a041b8b89466dc00d606f8b6"},
]
certifi = [ certifi = [
{file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"},
{file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, {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 = [ click = [
{file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"},
{file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, {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-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"},
{file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"}, {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 = [ html2text = [
{file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"}, {file = "html2text-2020.1.16-py3-none-any.whl", hash = "sha256:c7c629882da0cf377d66f073329ccf34a12ed2adf0169b9285ae4e63ef54c82b"},
{file = "html2text-2020.1.16.tar.gz", hash = "sha256:e296318e16b059ddb97f7a8a1d6a5c1d7af4544049a01e261731d2d5cc277bbb"}, {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-py3-none-any.whl", hash = "sha256:9cffb8ba31fac6536f2c8cde30df859013f59e4bcc5b8d43901cb3654a8e0a5b"},
{file = "httpx-0.16.1.tar.gz", hash = "sha256:126424c279c842738805974687e0518a94c7ae8d140cd65b9c4f77ac46ffa537"}, {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 = [ idna = [
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, {file = "idna-3.1-py3-none-any.whl", hash = "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16"},
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, {file = "idna-3.1.tar.gz", hash = "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1"},
] ]
imagesize = [ imagesize = [
{file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"}, {file = "imagesize-1.2.0-py2.py3-none-any.whl", hash = "sha256:6965f19a6a2039c7d48bca7dba2473069ff854c36ae6f19d2cde309d998228a1"},
{file = "imagesize-1.2.0.tar.gz", hash = "sha256:b1f6b5a4eab1f73479a50fb79fcf729514a900c341d8503d62a62dbc4127a2b1"}, {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 = [ jinja2 = [
{file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"},
{file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, {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-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"},
{file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, {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_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_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-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-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"},
{file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, {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_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_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-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-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"},
{file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, {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-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_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-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-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"},
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, {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"}, {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
] ]
packaging = [ packaging = [
{file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"}, {file = "packaging-20.9-py2.py3-none-any.whl", hash = "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"},
{file = "packaging-20.9.tar.gz", hash = "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5"}, {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 = [ pydantic = [
{file = "pydantic-1.7.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd"}, {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"}, {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-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, {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 = [ requests = [
{file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"}, {file = "requests-2.15.1-py2.py3-none-any.whl", hash = "sha256:ff753b2196cd18b1bbeddc9dcd5c864056599f7a7d9a4fb5677e723efa2b7fb9"},
{file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, {file = "requests-2.15.1.tar.gz", hash = "sha256:e5659b9315a0610505e050bb7190bf6fa2ccee1ac295f2b760ef9d8a03ebbb2e"},
] ]
rfc3986 = [ rfc3986 = [
{file = "rfc3986-1.4.0-py2.py3-none-any.whl", hash = "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50"}, {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-py3-none-any.whl", hash = "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9"},
{file = "starlette-0.13.6.tar.gz", hash = "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc"}, {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 = [ typing-extensions = [
{file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, {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"}, {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"},
@ -795,10 +923,6 @@ unify = [
untokenize = [ untokenize = [
{file = "untokenize-0.1.1.tar.gz", hash = "sha256:3865dbbbb8efb4bb5eaa72f1be7f3e0be00ea8b7f125c69cbd1f5fda926f37a2"}, {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 = [ uvicorn = [
{file = "uvicorn-0.11.8-py3-none-any.whl", hash = "sha256:4b70ddb4c1946e39db9f3082d53e323dfd50634b95fd83625d778729ef1730ef"}, {file = "uvicorn-0.11.8-py3-none-any.whl", hash = "sha256:4b70ddb4c1946e39db9f3082d53e323dfd50634b95fd83625d778729ef1730ef"},
{file = "uvicorn-0.11.8.tar.gz", hash = "sha256:46a83e371f37ea7ff29577d00015f02c942410288fb57def6440f2653fff1d26"}, {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-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"},
{file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"}, {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 = [ win32-setctime = [
{file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"}, {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"},
{file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"}, {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 = [ yapf = [
{file = "yapf-0.30.0-py2.py3-none-any.whl", hash = "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9"}, {file = "yapf-0.30.0-py2.py3-none-any.whl", hash = "sha256:3abf61ba67cf603069710d30acbc88cfe565d907e16ad81429ae90ce9651e0c9"},
{file = "yapf-0.30.0.tar.gz", hash = "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427"}, {file = "yapf-0.30.0.tar.gz", hash = "sha256:3000abee4c28daebad55da6c85f3cd07b8062ce48e2e9943c8da1b9667d48427"},

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "nonebot2" name = "nonebot2"
version = "2.0.0a9.post1" version = "2.0.0a10"
description = "An asynchronous python bot framework." description = "An asynchronous python bot framework."
authors = ["yanyongyu <yanyongyu_1@126.com>"] authors = ["yanyongyu <yanyongyu_1@126.com>"]
license = "MIT" license = "MIT"
@ -31,12 +31,17 @@ fastapi = "^0.63.0"
uvicorn = "^0.11.5" uvicorn = "^0.11.5"
websockets = "^8.1" websockets = "^8.1"
pydantic = {extras = ["dotenv", "typing_extensions"], version = "^1.7.3"} pydantic = {extras = ["dotenv", "typing_extensions"], version = "^1.7.3"}
Quart = {version = "^0.14.1", optional = true}
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
yapf = "^0.30.0" yapf = "^0.30.0"
sphinx = "^3.4.1" sphinx = "^3.4.1"
sphinx-markdown-builder = { git = "https://github.com/nonebot/sphinx-markdown-builder.git" } sphinx-markdown-builder = { git = "https://github.com/nonebot/sphinx-markdown-builder.git" }
[tool.poetry.extras]
quart = ["quart"]
all = ["quart"]
# [[tool.poetry.source]] # [[tool.poetry.source]]
# name = "aliyun" # name = "aliyun"
# url = "https://mirrors.aliyun.com/pypi/simple/" # 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 from nonebot.adapters.mirai import Bot, MessageEvent
message_test = on_message() message_test = on_keyword({'reply'}, rule=to_me())
@message_test.handle() @message_test.handle()
async def _message(bot: Bot, event: MessageEvent): async def _message(bot: Bot, event: MessageEvent):
text = event.get_plaintext() text = event.get_plaintext()
if not text: await bot.send(event, text, at_sender=True)
return
reversed_text = ''.join(reversed(text))
await bot.send(event, reversed_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)