🔀 Merge pull request #16 from nonebot/dev

Pre Release 2.0.0a2
This commit is contained in:
Ju4tCode 2020-10-01 01:31:40 +08:00 committed by GitHub
commit a308f4d4ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 2993 additions and 190 deletions

View File

@ -1,10 +1,10 @@
<div align=center>
<img src="docs/.vuepress/public/logo.png" width="200" height="200">
<img src="./docs/.vuepress/public/logo.png" width="200" height="200">
# NoneBot
[![License](https://img.shields.io/github/license/richardchien/nonebot.svg)](LICENSE)
[![PyPI](https://img.shields.io/pypi/v/nonebot.svg)](https://pypi.python.org/pypi/nonebot)
[![License](https://img.shields.io/github/license/nonebot/nonebot2.svg)](LICENSE)
[![PyPI](https://img.shields.io/pypi/v/nonebot2.svg)](https://pypi.python.org/pypi/nonebot2)
![Python Version](https://img.shields.io/badge/python-3.7+-blue.svg)
![CQHTTP Version](https://img.shields.io/badge/cqhttp-11+-black.svg)
[![QQ 群](https://img.shields.io/badge/qq%E7%BE%A4-768887710-orange.svg)](https://jq.qq.com/?_wv=1027&k=5OFifDh)
@ -16,8 +16,6 @@
## 简介
**NoneBot2 尚在开发中**
NoneBot2 是一个可扩展的 Python 异步机器人框架,它会对机器人收到的消息进行解析和处理,并以插件化的形式,分发给消息所对应的命令处理器和自然语言处理器,来完成具体的功能。
除了起到解析消息的作用NoneBot 还为插件提供了大量实用的预设操作和权限控制机制,尤其对于命令处理器,它更是提供了完善且易用的会话机制和内部调用机制,以分别适应命令的连续交互和插件内部功能复用等需求。

View File

@ -29,3 +29,6 @@
* [nonebot.exception](exception.html)
* [nonebot.adapters.cqhttp](adapters/cqhttp.html)

View File

@ -0,0 +1,41 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.adapters 模块
## _class_ `BaseBot`
基类:`abc.ABC`
## _class_ `BaseEvent`
基类:`abc.ABC`
### `_raw_event`
原始 event
## _class_ `BaseMessageSegment`
基类:`abc.ABC`
## _class_ `BaseMessage`
基类:`list`, `abc.ABC`
### `append(obj)`
Append object to the end of the list.
### `extend(obj)`
Extend list by appending elements from the iterable.

View File

@ -0,0 +1,411 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.adapters.cqhttp 模块
## CQHTTP (OneBot) v11 协议适配
协议详情请看: [CQHTTP](http://cqhttp.cc/) | [OneBot](https://github.com/howmanybots/onebot)
## `log(level, message)`
* **说明**
用于打印 CQHTTP 日志。
* **参数**
* `level: str`: 日志等级
* `message: str`: 日志信息
## `escape(s, *, escape_comma=True)`
* **说明**
对字符串进行 CQ 码转义。
* **参数**
* `s: str`: 需要转义的字符串
* `escape_comma: bool`: 是否转义逗号(`,`)。
## `unescape(s)`
* **说明**
对字符串进行 CQ 码去转义。
* **参数**
* `s: str`: 需要转义的字符串
## `_b2s(b)`
转换布尔值为字符串。
## _async_ `_check_reply(bot, event)`
* **说明**
检查消息中存在的回复,去除并赋值 `event.reply`, `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_check_at_me(bot, event)`
* **说明**
检查消息开头或结尾是否存在 @机器人,去除并赋值 `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_check_nickname(bot, event)`
* **说明**
检查消息开头是否存在,去除并赋值 `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_handle_api_result(result)`
* **说明**
处理 API 请求返回值。
* **参数**
* `result: Optional[Dict[str, Any]]`: API 返回数据
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `ActionFailed`: API 调用失败
## _class_ `Bot`
基类:[`nonebot.adapters.BaseBot`](#None)
CQHTTP 协议 Bot 适配。继承属性参考 [BaseBot](./#class-basebot) 。
### _property_ `type`
* 返回: `"cqhttp"`
### _async_ `handle_message(message)`
* **说明**
调用 [_check_reply](#async-check-reply-bot-event), [_check_at_me](#check-at-me-bot-event), [_check_nickname](#check-nickname-bot-event) 处理事件并转换为 [Event](#class-event)
### _async_ `call_api(api, **data)`
* **说明**
调用 CQHTTP 协议 API
* **参数**
* `api: str`: API 名称
* `**data: Any`: API 参数
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `NetworkError`: 网络错误
* `ActionFailed`: API 调用失败
### _async_ `send(event, message, at_sender=False, **kwargs)`
* **说明**
根据 `event` 向触发事件的主体发送消息。
* **参数**
* `event: Event`: Event 对象
* `message: Union[str, Message, MessageSegment]`: 要发送的消息
* `at_sender: bool`: 是否 @ 事件主体
* `**kwargs`: 覆盖默认参数
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `ValueError`: 缺少 `user_id`, `group_id`
* `NetworkError`: 网络错误
* `ActionFailed`: API 调用失败
## _class_ `Event`
基类:[`nonebot.adapters.BaseEvent`](#None)
CQHTTP 协议 Event 适配。继承属性参考 [BaseEvent](./#class-baseevent) 。
### _property_ `id`
* 类型: `Optional[int]`
* 说明: 事件/消息 ID
### _property_ `name`
* 类型: `str`
* 说明: 事件名称,由类型与 `.` 组合而成
### _property_ `self_id`
* 类型: `str`
* 说明: 机器人自身 ID
### _property_ `time`
* 类型: `int`
* 说明: 事件发生时间
### _property_ `type`
* 类型: `str`
* 说明: 事件类型
### _property_ `detail_type`
* 类型: `str`
* 说明: 事件详细类型
### _property_ `sub_type`
* 类型: `str`
* 说明: 事件类型
### _property_ `user_id`
* 类型: `Optional[int]`
* 说明: 事件主体 ID
### _property_ `group_id`
* 类型: `Optional[int]`
* 说明: 事件主体群 ID
### _property_ `to_me`
* 类型: `Optional[bool]`
* 说明: 消息是否与机器人相关
### _property_ `message`
* 类型: `Optional[Message]`
* 说明: 消息内容
### _property_ `reply`
* 类型: `Optional[dict]`
* 说明: 回复消息详情
### _property_ `raw_message`
* 类型: `Optional[str]`
* 说明: 原始消息
### _property_ `plain_text`
* 类型: `Optional[str]`
* 说明: 纯文本消息内容
### _property_ `sender`
* 类型: `Optional[dict]`
* 说明: 消息发送者信息
## _class_ `MessageSegment`
基类:[`nonebot.adapters.BaseMessageSegment`](#None)
## _class_ `Message`
基类:[`nonebot.adapters.BaseMessage`](#None)

View File

@ -142,6 +142,22 @@ sidebarDepth: 0
## `MatcherGroup`
* **类型**
`MatcherGroup`
* **说明**
MatcherGroup 为 Matcher 的集合。可以共享 Handler。
## `Rule`

View File

@ -6,6 +6,29 @@ sidebarDepth: 0
# NoneBot.utils 模块
## `escape_tag(s)`
* **说明**
用于记录带颜色日志时转义 `<tag>` 类型特殊标签
* **参数**
* `s: str`: 需要转义的字符串
* **返回**
* `str`
## `run_sync(func)`

View File

@ -159,7 +159,7 @@ weather = on_command("天气", rule=to_me(), permission=Permission(), priority=5
- `on_startswith(str)` ~ `on("message", startswith(str))`: 消息开头匹配处理器
- `on_endswith(str)` ~ `on("message", endswith(str))`: 消息结尾匹配处理器
- `on_command(str|tuple)` ~ `on("message", command(str|tuple))`: 命令处理器
- `on_regax(pattern_str)` ~ `on("message", regax(pattern_str))`: 正则匹配处理器
- `on_regex(pattern_str)` ~ `on("message", regex(pattern_str))`: 正则匹配处理器
#### 匹配规则 rule

View File

@ -78,6 +78,14 @@
{
"title": "nonebot.exception 模块",
"path": "exception"
},
{
"title": "nonebot.adapters 模块",
"path": "adapters/"
},
{
"title": "nonebot.adapters.cqhttp 模块",
"path": "adapters/cqhttp"
}
]
}

View File

@ -1,7 +1,7 @@
<template>
<div class="qq-chat">
<v-app
><v-main>
<v-app>
<v-main>
<v-card class="elevation-6">
<v-toolbar color="primary" dark dense flat>
<v-row no-gutters>
@ -139,19 +139,15 @@ export default {
default: () => []
}
},
data: () => ({
wow: null
}),
methods: {
initWOW: function() {
this.wow = new WOW({
new WOW({
noxClass: "wow",
animateClass: "animate__animated",
offset: 0,
mobile: true,
live: true
});
this.wow.init();
}).init();
}
},
mounted() {
@ -167,6 +163,7 @@ export default {
.chat {
min-height: 150px;
overflow-x: hidden;
}
.chat-bg {
background-color: #f3f6f9;
@ -214,3 +211,9 @@ export default {
font-size: 12px;
}
</style>
<style>
.v-application--wrap {
min-height: 0 !important;
}
</style>

View File

@ -119,6 +119,14 @@ module.exports = context => ({
{
title: "nonebot.exception 模块",
path: "exception"
},
{
title: "nonebot.adapters 模块",
path: "adapters/"
},
{
title: "nonebot.adapters.cqhttp 模块",
path: "adapters/cqhttp"
}
]
}

View File

@ -1,3 +1,3 @@
[
"2.0.0a1"
"2.0.0a2"
]

View File

@ -29,3 +29,6 @@
* [nonebot.exception](exception.html)
* [nonebot.adapters.cqhttp](adapters/cqhttp.html)

View File

@ -0,0 +1,41 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.adapters 模块
## _class_ `BaseBot`
基类:`abc.ABC`
## _class_ `BaseEvent`
基类:`abc.ABC`
### `_raw_event`
原始 event
## _class_ `BaseMessageSegment`
基类:`abc.ABC`
## _class_ `BaseMessage`
基类:`list`, `abc.ABC`
### `append(obj)`
Append object to the end of the list.
### `extend(obj)`
Extend list by appending elements from the iterable.

411
docs/api/adapters/cqhttp.md Normal file
View File

@ -0,0 +1,411 @@
---
contentSidebar: true
sidebarDepth: 0
---
# NoneBot.adapters.cqhttp 模块
## CQHTTP (OneBot) v11 协议适配
协议详情请看: [CQHTTP](http://cqhttp.cc/) | [OneBot](https://github.com/howmanybots/onebot)
## `log(level, message)`
* **说明**
用于打印 CQHTTP 日志。
* **参数**
* `level: str`: 日志等级
* `message: str`: 日志信息
## `escape(s, *, escape_comma=True)`
* **说明**
对字符串进行 CQ 码转义。
* **参数**
* `s: str`: 需要转义的字符串
* `escape_comma: bool`: 是否转义逗号(`,`)。
## `unescape(s)`
* **说明**
对字符串进行 CQ 码去转义。
* **参数**
* `s: str`: 需要转义的字符串
## `_b2s(b)`
转换布尔值为字符串。
## _async_ `_check_reply(bot, event)`
* **说明**
检查消息中存在的回复,去除并赋值 `event.reply`, `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_check_at_me(bot, event)`
* **说明**
检查消息开头或结尾是否存在 @机器人,去除并赋值 `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_check_nickname(bot, event)`
* **说明**
检查消息开头是否存在,去除并赋值 `event.to_me`
* **参数**
* `bot: Bot`: Bot 对象
* `event: Event`: Event 对象
## `_handle_api_result(result)`
* **说明**
处理 API 请求返回值。
* **参数**
* `result: Optional[Dict[str, Any]]`: API 返回数据
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `ActionFailed`: API 调用失败
## _class_ `Bot`
基类:[`nonebot.adapters.BaseBot`](#None)
CQHTTP 协议 Bot 适配。继承属性参考 [BaseBot](./#class-basebot) 。
### _property_ `type`
* 返回: `"cqhttp"`
### _async_ `handle_message(message)`
* **说明**
调用 [_check_reply](#async-check-reply-bot-event), [_check_at_me](#check-at-me-bot-event), [_check_nickname](#check-nickname-bot-event) 处理事件并转换为 [Event](#class-event)
### _async_ `call_api(api, **data)`
* **说明**
调用 CQHTTP 协议 API
* **参数**
* `api: str`: API 名称
* `**data: Any`: API 参数
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `NetworkError`: 网络错误
* `ActionFailed`: API 调用失败
### _async_ `send(event, message, at_sender=False, **kwargs)`
* **说明**
根据 `event` 向触发事件的主体发送消息。
* **参数**
* `event: Event`: Event 对象
* `message: Union[str, Message, MessageSegment]`: 要发送的消息
* `at_sender: bool`: 是否 @ 事件主体
* `**kwargs`: 覆盖默认参数
* **返回**
* `Any`: API 调用返回数据
* **异常**
* `ValueError`: 缺少 `user_id`, `group_id`
* `NetworkError`: 网络错误
* `ActionFailed`: API 调用失败
## _class_ `Event`
基类:[`nonebot.adapters.BaseEvent`](#None)
CQHTTP 协议 Event 适配。继承属性参考 [BaseEvent](./#class-baseevent) 。
### _property_ `id`
* 类型: `Optional[int]`
* 说明: 事件/消息 ID
### _property_ `name`
* 类型: `str`
* 说明: 事件名称,由类型与 `.` 组合而成
### _property_ `self_id`
* 类型: `str`
* 说明: 机器人自身 ID
### _property_ `time`
* 类型: `int`
* 说明: 事件发生时间
### _property_ `type`
* 类型: `str`
* 说明: 事件类型
### _property_ `detail_type`
* 类型: `str`
* 说明: 事件详细类型
### _property_ `sub_type`
* 类型: `str`
* 说明: 事件类型
### _property_ `user_id`
* 类型: `Optional[int]`
* 说明: 事件主体 ID
### _property_ `group_id`
* 类型: `Optional[int]`
* 说明: 事件主体群 ID
### _property_ `to_me`
* 类型: `Optional[bool]`
* 说明: 消息是否与机器人相关
### _property_ `message`
* 类型: `Optional[Message]`
* 说明: 消息内容
### _property_ `reply`
* 类型: `Optional[dict]`
* 说明: 回复消息详情
### _property_ `raw_message`
* 类型: `Optional[str]`
* 说明: 原始消息
### _property_ `plain_text`
* 类型: `Optional[str]`
* 说明: 纯文本消息内容
### _property_ `sender`
* 类型: `Optional[dict]`
* 说明: 消息发送者信息
## _class_ `MessageSegment`
基类:[`nonebot.adapters.BaseMessageSegment`](#None)
## _class_ `Message`
基类:[`nonebot.adapters.BaseMessage`](#None)

View File

@ -142,6 +142,22 @@ sidebarDepth: 0
## `MatcherGroup`
* **类型**
`MatcherGroup`
* **说明**
MatcherGroup 为 Matcher 的集合。可以共享 Handler。
## `Rule`

View File

@ -6,6 +6,29 @@ sidebarDepth: 0
# NoneBot.utils 模块
## `escape_tag(s)`
* **说明**
用于记录带颜色日志时转义 `<tag>` 类型特殊标签
* **参数**
* `s: str`: 需要转义的字符串
* **返回**
* `str`
## `run_sync(func)`

View File

@ -159,7 +159,7 @@ weather = on_command("天气", rule=to_me(), permission=Permission(), priority=5
- `on_startswith(str)` ~ `on("message", startswith(str))`: 消息开头匹配处理器
- `on_endswith(str)` ~ `on("message", endswith(str))`: 消息结尾匹配处理器
- `on_command(str|tuple)` ~ `on("message", command(str|tuple))`: 命令处理器
- `on_regax(pattern_str)` ~ `on("message", regax(pattern_str))`: 正则匹配处理器
- `on_regex(pattern_str)` ~ `on("message", regex(pattern_str))`: 正则匹配处理器
#### 匹配规则 rule

View File

@ -11,3 +11,4 @@ NoneBot Api Reference
- `nonebot.permission <permission.html>`_
- `nonebot.utils <utils.html>`_
- `nonebot.exception <exception.html>`_
- `nonebot.adapters.cqhttp <adapters/cqhttp.html>`_

View File

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

View File

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

View File

@ -7,6 +7,7 @@ NoneBot.utils 模块
==================
.. autofunction:: nonebot.utils.escape_tag
.. autodecorator:: nonebot.utils.run_sync
.. autoclass:: nonebot.utils.DataclassEncoder
:show-inheritance:

View File

@ -109,6 +109,7 @@ def get_bots() -> Union[NoReturn, Dict[str, Bot]]:
from nonebot.sched import scheduler
from nonebot.utils import escape_tag
from nonebot.config import Env, Config
from nonebot.log import logger, default_filter
from nonebot.adapters.cqhttp import Bot as CQBot
@ -155,8 +156,8 @@ def init(*, _env_file: Optional[str] = None, **kwargs):
_env_file=_env_file or f".env.{env.environment}")
default_filter.level = "DEBUG" if config.debug else "INFO"
logger.opt(
colors=True).debug(f"Loaded <y><b>Config</b></y>: {config.dict()}")
logger.opt(colors=True).debug(
f"Loaded <y><b>Config</b></y>: {escape_tag(str(config.dict()))}")
DriverClass: Type[Driver] = getattr(
importlib.import_module(config.driver), "Driver")
@ -213,5 +214,5 @@ async def _start_scheduler():
from nonebot.plugin import on_message, on_notice, on_request, on_metaevent
from nonebot.plugin import on_startswith, on_endswith, on_command, on_regex
from nonebot.plugin import on_startswith, on_endswith, on_command, on_regex, CommandGroup
from nonebot.plugin import load_plugin, load_plugins, load_builtin_plugins, get_loaded_plugins

View File

@ -52,6 +52,9 @@ class BaseEvent(abc.ABC):
def __init__(self, raw_event: dict):
self._raw_event = raw_event
"""
原始 event
"""
def __repr__(self) -> str:
return f"<Event {self.self_id}: {self.name} {self.time}>"

View File

@ -28,14 +28,29 @@ from nonebot.adapters import BaseBot, BaseEvent, BaseMessage, BaseMessageSegment
def log(level: str, message: str):
"""
:说明:
用于打印 CQHTTP 日志
:参数:
* ``level: str``: 日志等级
* ``message: str``: 日志信息
"""
return logger.opt(colors=True).log(level, "<m>CQHTTP</m> | " + message)
def escape(s: str, *, escape_comma: bool = True) -> str:
"""
:说明:
对字符串进行 CQ 码转义
``escape_comma`` 参数控制是否转义逗号``,``
:参数:
* ``s: str``: 需要转义的字符串
* ``escape_comma: bool``: 是否转义逗号``,``
"""
s = s.replace("&", "&amp;") \
.replace("[", "&#91;") \
@ -46,7 +61,15 @@ def escape(s: str, *, escape_comma: bool = True) -> str:
def unescape(s: str) -> str:
"""对字符串进行 CQ 码去转义。"""
"""
:说明:
对字符串进行 CQ 码去转义
:参数:
* ``s: str``: 需要转义的字符串
"""
return s.replace("&#44;", ",") \
.replace("&#91;", "[") \
.replace("&#93;", "]") \
@ -54,10 +77,21 @@ def unescape(s: str) -> str:
def _b2s(b: Optional[bool]) -> Optional[str]:
"""转换布尔值为字符串。"""
return b if b is None else str(b).lower()
async def _check_reply(bot: "Bot", event: "Event"):
"""
:说明:
检查消息中存在的回复去除并赋值 ``event.reply``, ``event.to_me``
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
"""
if event.type != "message":
return
@ -74,6 +108,16 @@ async def _check_reply(bot: "Bot", event: "Event"):
def _check_at_me(bot: "Bot", event: "Event"):
"""
:说明:
检查消息开头或结尾是否存在 @机器人去除并赋值 ``event.to_me``
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
"""
if event.type != "message":
return
@ -119,6 +163,16 @@ def _check_at_me(bot: "Bot", event: "Event"):
def _check_nickname(bot: "Bot", event: "Event"):
"""
:说明:
检查消息开头是否存在去除并赋值 ``event.to_me``
:参数:
* ``bot: Bot``: Bot 对象
* ``event: Event``: Event 对象
"""
if event.type != "message":
return
@ -145,7 +199,25 @@ def _check_nickname(bot: "Bot", event: "Event"):
first_msg_seg.data["text"] = first_text[m.end():]
def _handle_api_result(result: Optional[Dict[str, Any]]) -> Any:
def _handle_api_result(
result: Optional[Dict[str, Any]]) -> Union[Any, NoReturn]:
"""
:说明:
处理 API 请求返回值
:参数:
* ``result: Optional[Dict[str, Any]]``: API 返回数据
:返回:
- ``Any``: API 调用返回数据
:异常:
- ``ActionFailed``: API 调用失败
"""
if isinstance(result, dict):
if result.get("status") == "failed":
raise ActionFailed(retcode=result.get("retcode"))
@ -183,6 +255,9 @@ class ResultStore:
class Bot(BaseBot):
"""
CQHTTP 协议 Bot 适配继承属性参考 `BaseBot <./#class-basebot>`_ 。
"""
def __init__(self,
driver: Driver,
@ -203,10 +278,18 @@ class Bot(BaseBot):
@property
@overrides(BaseBot)
def type(self) -> str:
"""
- 返回: ``"cqhttp"``
"""
return "cqhttp"
@overrides(BaseBot)
async def handle_message(self, message: dict):
"""
:说明:
调用 `_check_reply <#async-check-reply-bot-event>`_, `_check_at_me <#check-at-me-bot-event>`_, `_check_nickname <#check-nickname-bot-event>`_ 处理事件并转换为 `Event <#class-event>`_
"""
if not message:
return
@ -230,6 +313,25 @@ class Bot(BaseBot):
@overrides(BaseBot)
async def call_api(self, api: str, **data) -> Union[Any, NoReturn]:
"""
:说明:
调用 CQHTTP 协议 API
:参数:
* ``api: str``: API 名称
* ``**data: Any``: API 参数
:返回:
- ``Any``: API 调用返回数据
:异常:
- ``NetworkError``: 网络错误
- ``ActionFailed``: API 调用失败
"""
if "self_id" in data:
self_id = data.pop("self_id")
if self_id:
@ -278,12 +380,36 @@ class Bot(BaseBot):
raise NetworkError("HTTP request failed")
@overrides(BaseBot)
async def send(self, event: "Event", message: Union[str, "Message",
"MessageSegment"],
async def send(self,
event: "Event",
message: Union[str, "Message", "MessageSegment"],
at_sender: bool = False,
**kwargs) -> Union[Any, NoReturn]:
"""
:说明:
根据 ``event`` 向触发事件的主体发送消息
:参数:
* ``event: Event``: Event 对象
* ``message: Union[str, Message, MessageSegment]``: 要发送的消息
* ``at_sender: bool``: 是否 @ 事件主体
* ``**kwargs``: 覆盖默认参数
:返回:
- ``Any``: API 调用返回数据
:异常:
- ``ValueError``: 缺少 ``user_id``, ``group_id``
- ``NetworkError``: 网络错误
- ``ActionFailed``: API 调用失败
"""
msg = message if isinstance(message, Message) else Message(message)
at_sender = kwargs.pop("at_sender", False) and bool(event.user_id)
at_sender = at_sender and bool(event.user_id)
params = {}
if event.user_id:
@ -309,6 +435,9 @@ class Bot(BaseBot):
class Event(BaseEvent):
"""
CQHTTP 协议 Event 适配继承属性参考 `BaseEvent <./#class-baseevent>`_ 。
"""
def __init__(self, raw_event: dict):
if "message" in raw_event:
@ -319,11 +448,19 @@ class Event(BaseEvent):
@property
@overrides(BaseEvent)
def id(self) -> Optional[int]:
"""
- 类型: ``Optional[int]``
- 说明: 事件/消息 ID
"""
return self._raw_event.get("message_id") or self._raw_event.get("flag")
@property
@overrides(BaseEvent)
def name(self) -> str:
"""
- 类型: ``str``
- 说明: 事件名称由类型与 ``.`` 组合而成
"""
n = self.type + "." + self.detail_type
if self.sub_type:
n += "." + self.sub_type
@ -332,16 +469,28 @@ class Event(BaseEvent):
@property
@overrides(BaseEvent)
def self_id(self) -> str:
"""
- 类型: ``str``
- 说明: 机器人自身 ID
"""
return str(self._raw_event["self_id"])
@property
@overrides(BaseEvent)
def time(self) -> int:
"""
- 类型: ``int``
- 说明: 事件发生时间
"""
return self._raw_event["time"]
@property
@overrides(BaseEvent)
def type(self) -> str:
"""
- 类型: ``str``
- 说明: 事件类型
"""
return self._raw_event["post_type"]
@type.setter
@ -352,6 +501,10 @@ class Event(BaseEvent):
@property
@overrides(BaseEvent)
def detail_type(self) -> str:
"""
- 类型: ``str``
- 说明: 事件详细类型
"""
return self._raw_event[f"{self.type}_type"]
@detail_type.setter
@ -362,6 +515,10 @@ class Event(BaseEvent):
@property
@overrides(BaseEvent)
def sub_type(self) -> Optional[str]:
"""
- 类型: ``Optional[str]``
- 说明: 事件子类型
"""
return self._raw_event.get("sub_type")
@type.setter
@ -372,6 +529,10 @@ class Event(BaseEvent):
@property
@overrides(BaseEvent)
def user_id(self) -> Optional[int]:
"""
- 类型: ``Optional[int]``
- 说明: 事件主体 ID
"""
return self._raw_event.get("user_id")
@user_id.setter
@ -382,6 +543,10 @@ class Event(BaseEvent):
@property
@overrides(BaseEvent)
def group_id(self) -> Optional[int]:
"""
- 类型: ``Optional[int]``
- 说明: 事件主体群 ID
"""
return self._raw_event.get("group_id")
@group_id.setter
@ -392,6 +557,10 @@ class Event(BaseEvent):
@property
@overrides(BaseEvent)
def to_me(self) -> Optional[bool]:
"""
- 类型: ``Optional[bool]``
- 说明: 消息是否与机器人相关
"""
return self._raw_event.get("to_me")
@to_me.setter
@ -402,6 +571,10 @@ class Event(BaseEvent):
@property
@overrides(BaseEvent)
def message(self) -> Optional["Message"]:
"""
- 类型: ``Optional[Message]``
- 说明: 消息内容
"""
return self._raw_event.get("message")
@message.setter
@ -412,6 +585,10 @@ class Event(BaseEvent):
@property
@overrides(BaseEvent)
def reply(self) -> Optional[dict]:
"""
- 类型: ``Optional[dict]``
- 说明: 回复消息详情
"""
return self._raw_event.get("reply")
@reply.setter
@ -422,6 +599,10 @@ class Event(BaseEvent):
@property
@overrides(BaseEvent)
def raw_message(self) -> Optional[str]:
"""
- 类型: ``Optional[str]``
- 说明: 原始消息
"""
return self._raw_event.get("raw_message")
@raw_message.setter
@ -432,11 +613,19 @@ class Event(BaseEvent):
@property
@overrides(BaseEvent)
def plain_text(self) -> Optional[str]:
"""
- 类型: ``Optional[str]``
- 说明: 纯文本消息内容
"""
return self.message and self.message.extract_plain_text()
@property
@overrides(BaseEvent)
def sender(self) -> Optional[dict]:
"""
- 类型: ``Optional[dict]``
- 说明: 消息发送者信息
"""
return self._raw_event.get("sender")
@sender.setter

View File

@ -1,15 +1,91 @@
import asyncio
from nonebot.config import Config
from nonebot.adapters import BaseBot
from nonebot.typing import Any, Dict, List, Union, Message, Optional
from nonebot.typing import Any, Dict, List, Union, Driver, Optional, NoReturn, WebSocket, Iterable
def log(level: str, message: str):
...
def escape(s: str, *, escape_comma: bool = ...) -> str:
...
def unescape(s: str) -> str:
...
def _b2s(b: Optional[bool]) -> Optional[str]:
...
async def _check_reply(bot: "Bot", event: "Event"):
...
def _check_at_me(bot: "Bot", event: "Event"):
...
def _check_nickname(bot: "Bot", event: "Event"):
...
def _handle_api_result(
result: Optional[Dict[str, Any]]) -> Union[Any, NoReturn]:
...
class ResultStore:
_seq: int = ...
_futures: Dict[int, asyncio.Future] = ...
@classmethod
def get_seq(cls) -> int:
...
@classmethod
def add_result(cls, result: Dict[str, Any]):
...
@classmethod
async def fetch(cls, seq: int, timeout: Optional[float]) -> Dict[str, Any]:
...
class Bot(BaseBot):
def __init__(self,
driver: Driver,
connection_type: str,
config: Config,
self_id: str,
*,
websocket: WebSocket = None):
...
def type(self) -> str:
...
async def handle_message(self, message: dict):
...
async def call_api(self, api: str, **data) -> Union[Any, NoReturn]:
...
async def send(self, event: "Event", message: Union[str, "Message",
"MessageSegment"],
**kwargs) -> Union[Any, NoReturn]:
...
async def send_private_msg(self,
*,
user_id: int,
message: Union[str, Message],
auto_escape: bool = False,
self_id: Optional[int] = None) -> Dict[str, Any]:
auto_escape: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -28,8 +104,8 @@ class Bot(BaseBot):
*,
group_id: int,
message: Union[str, Message],
auto_escape: bool = False,
self_id: Optional[int] = None) -> Dict[str, Any]:
auto_escape: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -46,12 +122,12 @@ class Bot(BaseBot):
async def send_msg(self,
*,
message_type: Optional[str] = None,
user_id: Optional[int] = None,
group_id: Optional[int] = None,
message_type: Optional[str] = ...,
user_id: Optional[int] = ...,
group_id: Optional[int] = ...,
message: Union[str, Message],
auto_escape: bool = False,
self_id: Optional[int] = None) -> Dict[str, Any]:
auto_escape: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -71,7 +147,7 @@ class Bot(BaseBot):
async def delete_msg(self,
*,
message_id: int,
self_id: Optional[int] = None) -> None:
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -87,7 +163,7 @@ class Bot(BaseBot):
async def get_msg(self,
*,
message_id: int,
self_id: Optional[int] = None) -> Dict[str, Any]:
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -103,7 +179,7 @@ class Bot(BaseBot):
async def get_forward_msg(self,
*,
id: int,
self_id: Optional[int] = None) -> None:
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -119,8 +195,8 @@ class Bot(BaseBot):
async def send_like(self,
*,
user_id: int,
times: int = 1,
self_id: Optional[int] = None) -> None:
times: int = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -138,8 +214,8 @@ class Bot(BaseBot):
*,
group_id: int,
user_id: int,
reject_add_request: bool = False,
self_id: Optional[int] = None) -> None:
reject_add_request: bool = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -158,8 +234,8 @@ class Bot(BaseBot):
*,
group_id: int,
user_id: int,
duration: int = 30 * 60,
self_id: Optional[int] = None) -> None:
duration: int = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -177,11 +253,10 @@ class Bot(BaseBot):
async def set_group_anonymous_ban(self,
*,
group_id: int,
anonymous: Optional[Dict[str,
Any]] = None,
anonymous_flag: Optional[str] = None,
duration: int = 30 * 60,
self_id: Optional[int] = None) -> None:
anonymous: Optional[Dict[str, Any]] = ...,
anonymous_flag: Optional[str] = ...,
duration: int = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -200,8 +275,8 @@ class Bot(BaseBot):
async def set_group_whole_ban(self,
*,
group_id: int,
enable: bool = True,
self_id: Optional[int] = None) -> None:
enable: bool = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -219,8 +294,8 @@ class Bot(BaseBot):
*,
group_id: int,
user_id: int,
enable: bool = True,
self_id: Optional[int] = None) -> None:
enable: bool = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -238,8 +313,8 @@ class Bot(BaseBot):
async def set_group_anonymous(self,
*,
group_id: int,
enable: bool = True,
self_id: Optional[int] = None) -> None:
enable: bool = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -257,8 +332,8 @@ class Bot(BaseBot):
*,
group_id: int,
user_id: int,
card: str = "",
self_id: Optional[int] = None) -> None:
card: str = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -277,7 +352,7 @@ class Bot(BaseBot):
*,
group_id: int,
group_name: str,
self_id: Optional[int] = None) -> None:
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -294,8 +369,8 @@ class Bot(BaseBot):
async def set_group_leave(self,
*,
group_id: int,
is_dismiss: bool = False,
self_id: Optional[int] = None) -> None:
is_dismiss: bool = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -313,9 +388,9 @@ class Bot(BaseBot):
*,
group_id: int,
user_id: int,
special_title: str = "",
duration: int = -1,
self_id: Optional[int] = None) -> None:
special_title: str = ...,
duration: int = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -334,9 +409,9 @@ class Bot(BaseBot):
async def set_friend_add_request(self,
*,
flag: str,
approve: bool = True,
remark: str = "",
self_id: Optional[int] = None) -> None:
approve: bool = ...,
remark: str = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -355,9 +430,9 @@ class Bot(BaseBot):
*,
flag: str,
sub_type: str,
approve: bool = True,
reason: str = "",
self_id: Optional[int] = None) -> None:
approve: bool = ...,
reason: str = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -375,7 +450,7 @@ class Bot(BaseBot):
async def get_login_info(self,
*,
self_id: Optional[int] = None) -> Dict[str, Any]:
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -387,12 +462,11 @@ class Bot(BaseBot):
"""
...
async def get_stranger_info(
self,
async def get_stranger_info(self,
*,
user_id: int,
no_cache: bool = False,
self_id: Optional[int] = None) -> Dict[str, Any]:
no_cache: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -408,7 +482,7 @@ class Bot(BaseBot):
async def get_friend_list(self,
*,
self_id: Optional[int] = None
self_id: Optional[int] = ...
) -> List[Dict[str, Any]]:
"""
:说明:
@ -424,8 +498,8 @@ class Bot(BaseBot):
async def get_group_info(self,
*,
group_id: int,
no_cache: bool = False,
self_id: Optional[int] = None) -> Dict[str, Any]:
no_cache: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -441,7 +515,7 @@ class Bot(BaseBot):
async def get_group_list(self,
*,
self_id: Optional[int] = None
self_id: Optional[int] = ...
) -> List[Dict[str, Any]]:
"""
:说明:
@ -459,8 +533,8 @@ class Bot(BaseBot):
*,
group_id: int,
user_id: int,
no_cache: bool = False,
self_id: Optional[int] = None) -> Dict[str, Any]:
no_cache: bool = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -479,7 +553,7 @@ class Bot(BaseBot):
self,
*,
group_id: int,
self_id: Optional[int] = None) -> List[Dict[str, Any]]:
self_id: Optional[int] = ...) -> List[Dict[str, Any]]:
"""
:说明:
@ -492,12 +566,12 @@ class Bot(BaseBot):
"""
...
async def get_group_honor_info(
self,
async def get_group_honor_info(self,
*,
group_id: int,
type: str = "all",
self_id: Optional[int] = None) -> Dict[str, Any]:
type: str = ...,
self_id: Optional[int] = ...
) -> Dict[str, Any]:
"""
:说明:
@ -513,8 +587,8 @@ class Bot(BaseBot):
async def get_cookies(self,
*,
domain: str = "",
self_id: Optional[int] = None) -> Dict[str, Any]:
domain: str = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -529,7 +603,7 @@ class Bot(BaseBot):
async def get_csrf_token(self,
*,
self_id: Optional[int] = None) -> Dict[str, Any]:
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -543,8 +617,8 @@ class Bot(BaseBot):
async def get_credentials(self,
*,
domain: str = "",
self_id: Optional[int] = None) -> Dict[str, Any]:
domain: str = ...,
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -561,7 +635,7 @@ class Bot(BaseBot):
*,
file: str,
out_format: str,
self_id: Optional[int] = None) -> Dict[str, Any]:
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -578,7 +652,7 @@ class Bot(BaseBot):
async def get_image(self,
*,
file: str,
self_id: Optional[int] = None) -> Dict[str, Any]:
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -593,7 +667,7 @@ class Bot(BaseBot):
async def can_send_image(self,
*,
self_id: Optional[int] = None) -> Dict[str, Any]:
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -607,7 +681,7 @@ class Bot(BaseBot):
async def can_send_record(self,
*,
self_id: Optional[int] = None) -> Dict[str, Any]:
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -621,7 +695,7 @@ class Bot(BaseBot):
async def get_status(self,
*,
self_id: Optional[int] = None) -> Dict[str, Any]:
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -635,7 +709,7 @@ class Bot(BaseBot):
async def get_version_info(self,
*,
self_id: Optional[int] = None) -> Dict[str, Any]:
self_id: Optional[int] = ...) -> Dict[str, Any]:
"""
:说明:
@ -649,8 +723,8 @@ class Bot(BaseBot):
async def set_restart(self,
*,
delay: int = 0,
self_id: Optional[int] = None) -> None:
delay: int = ...,
self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -663,7 +737,7 @@ class Bot(BaseBot):
"""
...
async def clean_cache(self, *, self_id: Optional[int] = None) -> None:
async def clean_cache(self, *, self_id: Optional[int] = ...) -> None:
"""
:说明:
@ -674,3 +748,242 @@ class Bot(BaseBot):
* ``self_id``: 机器人 QQ
"""
...
class Event:
def __init__(self, raw_event: dict):
...
@property
def id(self) -> Optional[int]:
...
@property
def name(self) -> str:
...
@property
def self_id(self) -> str:
...
@property
def time(self) -> int:
...
@property
def type(self) -> str:
...
@type.setter
def type(self, value) -> None:
...
@property
def detail_type(self) -> str:
...
@detail_type.setter
def detail_type(self, value) -> None:
...
@property
def sub_type(self) -> Optional[str]:
...
@type.setter
def sub_type(self, value) -> None:
...
@property
def user_id(self) -> Optional[int]:
...
@user_id.setter
def user_id(self, value) -> None:
...
@property
def group_id(self) -> Optional[int]:
...
@group_id.setter
def group_id(self, value) -> None:
...
@property
def to_me(self) -> Optional[bool]:
...
@to_me.setter
def to_me(self, value) -> None:
...
@property
def message(self) -> Optional["Message"]:
...
@message.setter
def message(self, value) -> None:
...
@property
def reply(self) -> Optional[dict]:
...
@reply.setter
def reply(self, value) -> None:
...
@property
def raw_message(self) -> Optional[str]:
...
@raw_message.setter
def raw_message(self, value) -> None:
...
@property
def plain_text(self) -> Optional[str]:
...
@property
def sender(self) -> Optional[dict]:
...
@sender.setter
def sender(self, value) -> None:
...
class MessageSegment:
def __init__(self, type: str, data: Dict[str, Union[str, list]]) -> None:
...
def __str__(self):
...
def __add__(self, other) -> "Message":
...
@staticmethod
def anonymous(ignore_failure: Optional[bool] = ...) -> "MessageSegment":
...
@staticmethod
def at(user_id: Union[int, str]) -> "MessageSegment":
...
@staticmethod
def contact_group(group_id: int) -> "MessageSegment":
...
@staticmethod
def contact_user(user_id: int) -> "MessageSegment":
...
@staticmethod
def dice() -> "MessageSegment":
...
@staticmethod
def face(id_: int) -> "MessageSegment":
...
@staticmethod
def forward(id_: str) -> "MessageSegment":
...
@staticmethod
def image(file: str,
type_: Optional[str] = ...,
cache: bool = ...,
proxy: bool = ...,
timeout: Optional[int] = ...) -> "MessageSegment":
...
@staticmethod
def json(data: str) -> "MessageSegment":
...
@staticmethod
def location(latitude: float,
longitude: float,
title: Optional[str] = ...,
content: Optional[str] = ...) -> "MessageSegment":
...
@staticmethod
def music(type_: str, id_: int) -> "MessageSegment":
...
@staticmethod
def music_custom(url: str,
audio: str,
title: str,
content: Optional[str] = ...,
img_url: Optional[str] = ...) -> "MessageSegment":
...
@staticmethod
def node(id_: int) -> "MessageSegment":
...
@staticmethod
def node_custom(user_id: int, nickname: str,
content: Union[str, "Message"]) -> "MessageSegment":
...
@staticmethod
def poke(type_: str, id_: str) -> "MessageSegment":
...
@staticmethod
def record(file: str,
magic: Optional[bool] = ...,
cache: Optional[bool] = ...,
proxy: Optional[bool] = ...,
timeout: Optional[int] = ...) -> "MessageSegment":
...
@staticmethod
def reply(id_: int) -> "MessageSegment":
...
@staticmethod
def rps() -> "MessageSegment":
...
@staticmethod
def shake() -> "MessageSegment":
...
@staticmethod
def share(url: str = ...,
title: str = ...,
content: Optional[str] = ...,
img_url: Optional[str] = ...) -> "MessageSegment":
...
@staticmethod
def text(text: str) -> "MessageSegment":
...
@staticmethod
def video(file: str,
cache: Optional[bool] = ...,
proxy: Optional[bool] = ...,
timeout: Optional[int] = ...) -> "MessageSegment":
...
@staticmethod
def xml(data: str) -> "MessageSegment":
...
class Message:
@staticmethod
def _construct(msg: Union[str, dict, list]) -> Iterable[MessageSegment]:
...

View File

@ -200,8 +200,8 @@ class Driver(BaseDriver):
websocket=ws)
else:
logger.warning("Unknown adapter")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="adapter not found")
await ws.close(code=status.WS_1008_POLICY_VIOLATION)
return
await ws.accept()
self._clients[x_self_id] = bot

View File

@ -159,15 +159,18 @@ class Matcher(metaclass=MatcherMeta):
) -> Callable[[Handler], Handler]:
async def _key_getter(bot: Bot, event: Event, state: dict):
if key not in state:
state["_current_key"] = key
if key not in state:
if prompt:
await bot.send(event=event, message=prompt)
raise PausedException
else:
state["_skip_key"] = True
async def _key_parser(bot: Bot, event: Event, state: dict):
# if key in state:
# return
if key in state and state.get("_skip_key"):
del state["_skip_key"]
return
parser = args_parser or cls._default_parser
if parser:
await parser(bot, event, state)
@ -185,6 +188,8 @@ class Matcher(metaclass=MatcherMeta):
async def wrapper(bot: Bot, event: Event, state: dict):
await parser(bot, event, state)
await func(bot, event, state)
if "_current_key" in state:
del state["_current_key"]
cls.handlers.append(wrapper)
@ -273,3 +278,172 @@ class Matcher(metaclass=MatcherMeta):
logger.info(f"Matcher {self} running complete")
current_bot.reset(b_t)
current_event.reset(e_t)
class MatcherGroup:
def __init__(self,
type_: str = "",
rule: Rule = Rule(),
permission: Permission = Permission(),
handlers: Optional[list] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
*,
module: Optional[str] = None,
default_state: Optional[dict] = None,
expire_time: Optional[datetime] = None):
self.matchers: List[Type[Matcher]] = []
self.type = type_
self.rule = rule
self.permission = permission
self.handlers = handlers
self.temp = temp
self.priority = priority
self.block = block
self.module = module
self.default_state = default_state
self.expire_time = expire_time
def __repr__(self) -> str:
return (
f"<MatcherGroup from {self.module or 'unknow'}, type={self.type}, "
f"priority={self.priority}, temp={self.temp}>")
def __str__(self) -> str:
return self.__repr__()
def new(self,
type_: str = "",
rule: Rule = Rule(),
permission: Permission = Permission(),
handlers: Optional[list] = None,
temp: bool = False,
priority: int = 1,
block: bool = False,
*,
module: Optional[str] = None,
default_state: Optional[dict] = None,
expire_time: Optional[datetime] = None) -> Type[Matcher]:
matcher = Matcher.new(type_=type_ or self.type,
rule=self.rule & rule,
permission=permission or self.permission,
handlers=handlers or self.handlers,
temp=temp or self.temp,
priority=priority or self.priority,
block=block or self.block,
module=module or self.module,
default_state=default_state or self.default_state,
expire_time=expire_time or self.expire_time)
self.matchers.append(matcher)
return matcher
def args_parser(self, func: ArgsParser) -> ArgsParser:
for matcher in self.matchers:
matcher.args_parser(func)
return func
def handle(self) -> Callable[[Handler], Handler]:
"""直接处理消息事件"""
def _decorator(func: Handler) -> Handler:
self.handlers.append(func)
return func
return _decorator
def receive(self) -> Callable[[Handler], Handler]:
"""接收一条新消息并处理"""
async def _receive(bot: Bot, event: Event, state: dict) -> NoReturn:
raise PausedException
if self.handlers:
# 已有前置handlers则接受一条新的消息否则视为接收初始消息
self.handlers.append(_receive)
def _decorator(func: Handler) -> Handler:
if not self.handlers or self.handlers[-1] is not func:
self.handlers.append(func)
return func
return _decorator
def got(
self,
key: str,
prompt: Optional[str] = None,
args_parser: Optional[ArgsParser] = None
) -> Callable[[Handler], Handler]:
async def _key_getter(bot: Bot, event: Event, state: dict):
state["_current_key"] = key
if key not in state:
if prompt:
await bot.send(event=event, message=prompt)
raise PausedException
else:
state["_skip_key"] = True
async def _key_parser(bot: Bot, event: Event, state: dict):
if key in state and state.get("_skip_key"):
del state["_skip_key"]
return
parser = args_parser or self._default_parser
if parser:
await parser(bot, event, state)
else:
state[state["_current_key"]] = str(event.message)
self.handlers.append(_key_getter)
self.handlers.append(_key_parser)
def _decorator(func: Handler) -> Handler:
if not hasattr(self.handlers[-1], "__wrapped__"):
parser = self.handlers.pop()
@wraps(func)
async def wrapper(bot: Bot, event: Event, state: dict):
await parser(bot, event, state)
await func(bot, event, state)
if "_current_key" in state:
del state["_current_key"]
self.handlers.append(wrapper)
return func
return _decorator
async def finish(
self,
prompt: Optional[Union[str, Message,
MessageSegment]] = None) -> NoReturn:
bot: Bot = current_bot.get()
event: Event = current_event.get()
if prompt:
await bot.send(event=event, message=prompt)
raise FinishedException
async def pause(
self,
prompt: Optional[Union[str, Message,
MessageSegment]] = None) -> NoReturn:
bot: Bot = current_bot.get()
event: Event = current_event.get()
if prompt:
await bot.send(event=event, message=prompt)
raise PausedException
async def reject(
self,
prompt: Optional[Union[str, Message,
MessageSegment]] = None) -> NoReturn:
bot: Bot = current_bot.get()
event: Event = current_event.get()
if prompt:
await bot.send(event=event, message=prompt)
raise RejectedException

View File

@ -6,6 +6,7 @@ from datetime import datetime
from nonebot.log import logger
from nonebot.rule import TrieRule
from nonebot.utils import escape_tag
from nonebot.matcher import matchers
from nonebot.typing import Set, Type, Union, NoReturn
from nonebot.typing import Bot, Event, Matcher, PreProcessor
@ -64,7 +65,9 @@ async def handle_event(bot: Bot, event: Event):
log_msg += f"@[群:{event.group_id}]:"
log_msg += ' "' + "".join(
map(lambda x: str(x) if x.type == "text" else f"<le>{x!s}</le>",
map(
lambda x: escape_tag(str(x))
if x.type == "text" else f"<le>{escape_tag(str(x))}</le>",
event.message)) + '"' # type: ignore
elif event.type == "notice":
log_msg += f"Notice {event.raw_event}"

View File

@ -14,7 +14,7 @@
import asyncio
from nonebot.utils import run_sync
from nonebot.typing import Bot, Event, Union, NoReturn, Callable, Awaitable, PermissionChecker
from nonebot.typing import Bot, Event, Union, NoReturn, Optional, Callable, Awaitable, PermissionChecker
class Permission:
@ -53,10 +53,13 @@ class Permission:
def __and__(self, other) -> NoReturn:
raise RuntimeError("And operation between Permissions is not allowed.")
def __or__(self, other: Union["Permission",
PermissionChecker]) -> "Permission":
def __or__(
self, other: Optional[Union["Permission",
PermissionChecker]]) -> "Permission":
checkers = self.checkers.copy()
if isinstance(other, Permission):
if other is None:
return self
elif isinstance(other, Permission):
checkers |= other.checkers
elif asyncio.iscoroutinefunction(other):
checkers.add(other) # type: ignore

View File

@ -9,9 +9,9 @@ from dataclasses import dataclass
from importlib._bootstrap import _load
from nonebot.log import logger
from nonebot.matcher import Matcher
from nonebot.permission import Permission
from nonebot.typing import Handler, RuleChecker
from nonebot.matcher import Matcher, MatcherGroup
from nonebot.rule import Rule, startswith, endswith, command, regex
from nonebot.typing import Set, List, Dict, Type, Tuple, Union, Optional, ModuleType
@ -27,8 +27,8 @@ class Plugin(object):
matcher: Set[Type[Matcher]]
def on(rule: Union[Rule, RuleChecker] = Rule(),
permission: Permission = Permission(),
def on(rule: Optional[Union[Rule, RuleChecker]] = None,
permission: Optional[Permission] = None,
*,
handlers: Optional[List[Handler]] = None,
temp: bool = False,
@ -37,7 +37,7 @@ def on(rule: Union[Rule, RuleChecker] = Rule(),
state: Optional[dict] = None) -> Type[Matcher]:
matcher = Matcher.new("",
Rule() & rule,
permission,
permission or Permission(),
temp=temp,
priority=priority,
block=block,
@ -47,7 +47,7 @@ def on(rule: Union[Rule, RuleChecker] = Rule(),
return matcher
def on_metaevent(rule: Union[Rule, RuleChecker] = Rule(),
def on_metaevent(rule: Optional[Union[Rule, RuleChecker]] = None,
*,
handlers: Optional[List[Handler]] = None,
temp: bool = False,
@ -66,8 +66,8 @@ def on_metaevent(rule: Union[Rule, RuleChecker] = Rule(),
return matcher
def on_message(rule: Union[Rule, RuleChecker] = Rule(),
permission: Permission = Permission(),
def on_message(rule: Optional[Union[Rule, RuleChecker]] = None,
permission: Optional[Permission] = None,
*,
handlers: Optional[List[Handler]] = None,
temp: bool = False,
@ -76,7 +76,7 @@ def on_message(rule: Union[Rule, RuleChecker] = Rule(),
state: Optional[dict] = None) -> Type[Matcher]:
matcher = Matcher.new("message",
Rule() & rule,
permission,
permission or Permission(),
temp=temp,
priority=priority,
block=block,
@ -86,7 +86,7 @@ def on_message(rule: Union[Rule, RuleChecker] = Rule(),
return matcher
def on_notice(rule: Union[Rule, RuleChecker] = Rule(),
def on_notice(rule: Optional[Union[Rule, RuleChecker]] = None,
*,
handlers: Optional[List[Handler]] = None,
temp: bool = False,
@ -105,7 +105,7 @@ def on_notice(rule: Union[Rule, RuleChecker] = Rule(),
return matcher
def on_request(rule: Union[Rule, RuleChecker] = Rule(),
def on_request(rule: Optional[Union[Rule, RuleChecker]] = None,
*,
handlers: Optional[List[Handler]] = None,
temp: bool = False,
@ -125,27 +125,23 @@ def on_request(rule: Union[Rule, RuleChecker] = Rule(),
def on_startswith(msg: str,
rule: Optional[Union[Rule, RuleChecker]] = None,
permission: Permission = Permission(),
rule: Optional[Optional[Union[Rule, RuleChecker]]] = None,
**kwargs) -> Type[Matcher]:
return on_message(startswith(msg) &
rule, permission, **kwargs) if rule else on_message(
startswith(msg), permission, **kwargs)
return on_message(startswith(msg) & rule, **kwargs) if rule else on_message(
startswith(msg), **kwargs)
def on_endswith(msg: str,
rule: Optional[Union[Rule, RuleChecker]] = None,
permission: Permission = Permission(),
rule: Optional[Optional[Union[Rule, RuleChecker]]] = None,
**kwargs) -> Type[Matcher]:
return on_message(endswith(msg) &
rule, permission, **kwargs) if rule else on_message(
startswith(msg), permission, **kwargs)
return on_message(endswith(msg) & rule, **kwargs) if rule else on_message(
startswith(msg), **kwargs)
def on_command(cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, RuleChecker]] = None,
permission: Permission = Permission(),
**kwargs) -> Type[Matcher]:
aliases: Optional[Set[Union[str, Tuple[str, ...]]]] = None,
**kwargs) -> Union[Type[Matcher], MatcherGroup]:
if isinstance(cmd, str):
cmd = (cmd,)
@ -157,20 +153,28 @@ def on_command(cmd: Union[str, Tuple[str, ...]],
handlers = kwargs.pop("handlers", [])
handlers.insert(0, _strip_cmd)
return on_message(
command(cmd) &
rule, permission, handlers=handlers, **kwargs) if rule else on_message(
command(cmd), permission, handlers=handlers, **kwargs)
if aliases:
aliases = set(map(lambda x: (x,) if isinstance(x, str) else x, aliases))
group = MatcherGroup("message",
Rule() & rule,
handlers=handlers,
**kwargs)
for cmd_ in [cmd, *aliases]:
_tmp_matchers.add(group.new(rule=command(cmd_)))
return group
else:
return on_message(command(cmd) & rule, handlers=handlers, **
kwargs) if rule else on_message(
command(cmd), handlers=handlers, **kwargs)
def on_regex(pattern: str,
flags: Union[int, re.RegexFlag] = 0,
rule: Optional[Rule] = None,
permission: Permission = Permission(),
**kwargs) -> Type[Matcher]:
return on_message(regex(pattern, flags) &
rule, permission, **kwargs) if rule else on_message(
regex(pattern, flags), permission, **kwargs)
rule, **kwargs) if rule else on_message(
regex(pattern, flags), **kwargs)
def load_plugin(module_path: str) -> Optional[Plugin]:
@ -234,3 +238,21 @@ def load_builtin_plugins():
def get_loaded_plugins() -> Set[Plugin]:
return set(plugins.values())
class CommandGroup:
def __init__(self, cmd: Union[str, Tuple[str, ...]], **kwargs):
self.basecmd = (cmd,) if isinstance(cmd, str) else cmd
if "aliases" in kwargs:
del kwargs["aliases"]
self.base_kwargs = kwargs
def command(self, cmd: Union[str, Tuple[str, ...]],
**kwargs) -> Union[Type[Matcher], MatcherGroup]:
sub_cmd = (cmd,) if isinstance(cmd, str) else cmd
cmd = self.basecmd + sub_cmd
final_kwargs = self.base_kwargs.copy()
final_kwargs.update(kwargs)
return on_command(cmd, **final_kwargs)

180
nonebot/plugin.pyi Normal file
View File

@ -0,0 +1,180 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
from typing import overload
from nonebot.typing import Rule, Matcher, Handler, Permission, RuleChecker, MatcherGroup
from nonebot.typing import Set, List, Dict, Type, Tuple, Union, Optional, ModuleType
plugins: Dict[str, "Plugin"] = ...
_tmp_matchers: Set[Type[Matcher]] = ...
class Plugin(object):
name: str
module: ModuleType
matcher: Set[Type[Matcher]]
def on(rule: Optional[Union[Rule, RuleChecker]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_metaevent(rule: Optional[Union[Rule, RuleChecker]] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_message(rule: Optional[Union[Rule, RuleChecker]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_notice(rule: Optional[Union[Rule, RuleChecker]] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_request(rule: Optional[Union[Rule, RuleChecker]] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_startswith(msg: str,
rule: Optional[Optional[Union[Rule, RuleChecker]]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def on_endswith(msg: str,
rule: Optional[Optional[Union[Rule, RuleChecker]]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
@overload
def on_command(cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, RuleChecker]] = ...,
aliases: None = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
@overload
def on_command(cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, RuleChecker]] = ...,
aliases: Set[Union[str, Tuple[str, ...]]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> MatcherGroup:
...
def on_regex(pattern: str,
flags: Union[int, re.RegexFlag] = 0,
rule: Optional[Rule] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Type[Matcher]:
...
def load_plugin(module_path: str) -> Optional[Plugin]:
...
def load_plugins(*plugin_dir: str) -> Set[Plugin]:
...
def load_builtin_plugins():
...
def get_loaded_plugins() -> Set[Plugin]:
...
class CommandGroup:
def __init__(self,
cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, RuleChecker]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...):
...
def command(
self,
cmd: Union[str, Tuple[str, ...]],
rule: Optional[Union[Rule, RuleChecker]] = ...,
aliases: Set[Union[str, Tuple[str, ...]]] = ...,
permission: Optional[Permission] = ...,
*,
handlers: Optional[List[Handler]] = ...,
temp: bool = ...,
priority: int = ...,
block: bool = ...,
state: Optional[dict] = ...) -> Union[Type[Matcher], MatcherGroup]:
...

View File

@ -20,7 +20,7 @@ from pygtrie import CharTrie
from nonebot import get_driver
from nonebot.log import logger
from nonebot.utils import run_sync
from nonebot.typing import Bot, Any, Dict, Event, Union, Tuple, NoReturn, Callable, Awaitable, RuleChecker
from nonebot.typing import Bot, Any, Dict, Event, Union, Tuple, NoReturn, Optional, Callable, Awaitable, RuleChecker
class Rule:
@ -68,9 +68,11 @@ class Rule:
*map(lambda c: c(bot, event, state), self.checkers))
return all(results)
def __and__(self, other: Union["Rule", RuleChecker]) -> "Rule":
def __and__(self, other: Optional[Union["Rule", RuleChecker]]) -> "Rule":
checkers = self.checkers.copy()
if isinstance(other, Rule):
if other is None:
return self
elif isinstance(other, Rule):
checkers |= other.checkers
elif asyncio.iscoroutinefunction(other):
checkers.add(other) # type: ignore

View File

@ -28,10 +28,10 @@ from typing import Union, TypeVar, Optional, Iterable, Callable, Awaitable
# import some modules needed when checking types
if TYPE_CHECKING:
from nonebot.rule import Rule as RuleClass
from nonebot.matcher import Matcher as MatcherClass
from nonebot.drivers import BaseDriver, BaseWebSocket
from nonebot.permission import Permission as PermissionClass
from nonebot.adapters import BaseBot, BaseEvent, BaseMessage, BaseMessageSegment
from nonebot.matcher import Matcher as MatcherClass, MatcherGroup as MatcherGroupClass
def overrides(InterfaceClass: object):
@ -112,6 +112,14 @@ Matcher = TypeVar("Matcher", bound="MatcherClass")
Matcher 即响应事件的处理类通过 Rule 判断是否响应事件运行 Handler
"""
MatcherGroup = TypeVar("MatcherGroup", bound="MatcherGroupClass")
"""
:类型: ``MatcherGroup``
:说明:
MatcherGroup Matcher 的集合可以共享 Handler
"""
Rule = TypeVar("Rule", bound="RuleClass")
"""
:类型: ``Rule``

View File

@ -1,6 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re
import json
import asyncio
import dataclasses
@ -9,6 +10,18 @@ from functools import wraps, partial
from nonebot.typing import Any, Callable, Awaitable, overrides
def escape_tag(s: str) -> str:
"""
:说明:
用于记录带颜色日志时转义 ``<tag>`` 类型特殊标签
:参数:
* ``s: str``: 需要转义的字符串
:返回:
- ``str``
"""
return re.sub(r"</?((?:[fb]g\s)?[^<>\s]*)>", r"\\\g<0>", s)
def run_sync(func: Callable[..., Any]) -> Callable[..., Awaitable[Any]]:
"""
:说明:

896
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "nonebot2"
version = "2.0.0a1"
version = "2.0.0a2"
description = "An asynchronous python bot framework."
authors = ["yanyongyu <yanyongyu_1@126.com>"]
license = "MIT"
@ -32,7 +32,7 @@ uvicorn = "^0.11.5"
pydantic = { extras = ["dotenv"], version = "^1.6.1" }
apscheduler = { version = "^3.6.3", optional = true }
# nonebot-test = { version = "^0.1.0", optional = true }
# nb-cli = { version="^0.1.0", optional = true }
nb-cli = { version="^0.1.0", optional = true }
[tool.poetry.dev-dependencies]
yapf = "^0.30.0"
@ -40,7 +40,7 @@ sphinx = "^3.1.1"
sphinx-markdown-builder = { git = "https://github.com/nonebot/sphinx-markdown-builder.git" }
[tool.poetry.extras]
# cli = ["nb-cli"]
cli = ["nb-cli"]
# test = ["nonebot-test"]
scheduler = ["apscheduler"]
full = ["nb-cli", "nonebot-test", "scheduler"]

View File

@ -0,0 +1,6 @@
from nonebot.rule import to_me
from nonebot import CommandGroup
test = CommandGroup("test", rule=to_me())
from . import commands

View File

@ -0,0 +1,11 @@
from nonebot.typing import Bot, Event
from nonebot.permission import GROUP_OWNER
from . import test
test_1 = test.command("1", aliases={"test"}, permission=GROUP_OWNER)
@test_1.handle()
async def test1(bot: Bot, event: Event, state: dict):
await test_1.finish(event.raw_message)

View File

@ -1,22 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from nonebot.rule import to_me
from nonebot.typing import Event
from nonebot.plugin import on_message
from nonebot.adapters.cqhttp import Bot
test_message = on_message(to_me(), state={"default": 1})
@test_message.handle()
async def test_handler(bot: Bot, event: Event, state: dict):
print("[*] Test Matcher Received:", event)
state["event"] = event
await bot.send(message="Received", event=event)
@test_message.receive()
async def test_receive(bot: Bot, event: Event, state: dict):
print("[*] Test Matcher Received next time:", event)
print("[*] Current State:", state)