change Matcher class

This commit is contained in:
yanyongyu 2020-05-05 16:11:05 +08:00
parent 06b7ef2a45
commit 761d725aed
4 changed files with 265 additions and 73 deletions

124
nonebot/event.py Normal file
View File

@ -0,0 +1,124 @@
from typing import Dict, Any, Optional
class Event(dict):
"""
封装从 CQHTTP 收到的事件数据对象字典提供属性以获取其中的字段
`type` `detail_type` 属性对于任何事件都有效外其它属性存在与否不存在则返回
`None`依事件不同而不同
"""
@staticmethod
def from_payload(payload: Dict[str, Any]) -> Optional["Event"]:
"""
CQHTTP 事件数据构造 `Event` 对象
"""
try:
e = Event(payload)
_ = e.type, e.detail_type
return e
except KeyError:
return None
@property
def type(self) -> str:
"""
事件类型 ``message````notice````request````meta_event``
"""
return self['post_type']
@property
def detail_type(self) -> str:
"""
事件具体类型 `type` 的不同而不同 ``message`` 类型为例
``private````group````discuss``
"""
return self[f'{self.type}_type']
@property
def sub_type(self) -> Optional[str]:
"""
事件子类型 `detail_type` 不同而不同 ``message.private`` 为例
``friend````group````discuss````other``
"""
return self.get('sub_type')
@property
def name(self):
"""
事件名对于有 `sub_type` 的事件 ``{type}.{detail_type}.{sub_type}``否则为
``{type}.{detail_type}``
"""
n = self.type + '.' + self.detail_type
if self.sub_type:
n += '.' + self.sub_type
return n
@property
def self_id(self) -> int:
"""机器人自身 ID。"""
return self['self_id']
@property
def user_id(self) -> Optional[int]:
"""用户 ID。"""
return self.get('user_id')
@property
def operator_id(self) -> Optional[int]:
"""操作者 ID。"""
return self.get('operator_id')
@property
def group_id(self) -> Optional[int]:
"""群 ID。"""
return self.get('group_id')
@property
def discuss_id(self) -> Optional[int]:
"""讨论组 ID。"""
return self.get('discuss_id')
@property
def message_id(self) -> Optional[int]:
"""消息 ID。"""
return self.get('message_id')
@property
def message(self) -> Optional[Any]:
"""消息。"""
return self.get('message')
@property
def raw_message(self) -> Optional[str]:
"""未经 CQHTTP 处理的原始消息。"""
return self.get('raw_message')
@property
def sender(self) -> Optional[Dict[str, Any]]:
"""消息发送者信息。"""
return self.get('sender')
@property
def anonymous(self) -> Optional[Dict[str, Any]]:
"""匿名信息。"""
return self.get('anonymous')
@property
def file(self) -> Optional[Dict[str, Any]]:
"""文件信息。"""
return self.get('file')
@property
def comment(self) -> Optional[str]:
"""请求验证消息。"""
return self.get('comment')
@property
def flag(self) -> Optional[str]:
"""请求标识。"""
return self.get('flag')
def __repr__(self) -> str:
return f'<Event, {super().__repr__()}>'

View File

@ -1,8 +1,13 @@
class BlockedException(Exception):
"""Block a message from further handling"""
class PausedException(Exception):
"""Block a message from further handling and try to receive a new message"""
pass
class RejectedException(Exception):
"""Reject a message and return current handler back"""
pass
class FinishedException(Exception):
"""Finish handling a message"""
pass

View File

@ -1,102 +1,163 @@
import re
import copy
from functools import wraps
from typing import Union, Optional
from typing import Type, Union, Optional, Callable
from .rule import Rule, startswith, regex, user
from .event import Event
from .typing import Scope, Handler
from .exception import BlockedException, RejectedException
from .rule import Rule, startswith, regex, user
from .exception import PausedException, RejectedException, FinishedException
class Matcher:
def __init__(self,
rule: Rule,
rule: Rule = Rule()
scope: Scope = "ALL"
permission: str = "ALL"
block: bool = True
handlers: list = []
temp: bool = False
_default_state: dict = {}
_default_parser: Optional[Callable[[Event, dict], None]] = None
_args_parser: Optional[Callable[[Event, dict], None]] = None
def __init__(self):
self.handlers = self.handlers.copy()
self.state = self._default_state.copy()
self.parser = self._args_parser or self._default_parser
@classmethod
def new(cls,
rule: Rule = Rule(),
scope: Scope = "ALL",
permission: str = "ALL",
block: bool = True,
*,
handlers: list = [],
state: dict = {},
temp: bool = False):
self.rule = rule
self.scope = scope
self.permission = permission
self.block = block
self.handlers = handlers
self.state = state
self.temp = temp
temp: bool = False,
*,
default_state: dict = {},
default_parser: Optional[Callable[[Event, dict], None]] = None,
args_parser: Optional[Callable[[Event, dict], None]] = None):
def _default_parser(event: "Event", state: dict):
state[state.pop("_current_arg")] = event.message
# class NewMatcher(cls):
# rule: Rule = rule
# scope: Scope = scope
# permission: str = permission
# block: bool = block
# handlers: list = handlers
# temp: bool = temp
self._args_parser = _default_parser
# _default_state = default_state
def __call__(self, func: Handler) -> Handler:
self.handlers.append(func)
NewMatcher = type(
"Matcher", (cls,), {
"rule": rule,
"scope": scope,
"permission": permission,
"block": block,
"handlers": handlers,
"temp": temp,
"_default_state": default_state,
"_default_parser": default_parser,
"_args_parser": args_parser,
})
# TODO: export some functions
func.args_parser = self.args_parser
func.receive = self.receive
func.got = self.got
return NewMatcher
@classmethod
def args_parser(cls, func: Callable[[Event, dict], None]):
cls._default_parser = func
return func
def args_parser(self, func):
self._args_parser = func
return func
def receive(self):
@classmethod
def receive(cls):
def _decorator(func: Handler) -> Handler:
@wraps(func)
def _handler(event: "Event", state: dict):
# TODO: add tmp matcher to matcher tree
matcher = Matcher(user(event.user_id) & self.rule,
scope=self.scope,
permission=self.permission,
block=self.block,
handlers=self.handlers,
state=state,
temp=True)
matcher.args_parser(self._args_parser)
raise BlockedException
def _handler(event: Event, state: dict):
raise PausedException
self.handlers.append(_handler)
cls.handlers.append(_handler)
return func
return _decorator
def got(self, key, args_parser=None):
@classmethod
def got(cls,
key: str,
args_parser: Optional[Callable[[Event, dict], None]] = None):
def _decorator(func: Handler) -> Handler:
@wraps(func)
def _handler(event: "Event", state: dict):
def _handler(event: Event, state: dict):
if key not in state:
state["_current_arg"] = key
# TODO: add tmp matcher to matcher tree
matcher = copy.copy(self)
if state.get("__current_arg__", None) == key:
state[key] = event.message
del state["__current_arg__"]
return func(event, state)
state["__current_arg__"] = key
cls._args_parser = args_parser
raise RejectedException
return func(event, state)
self.handlers.append(_handler)
cls.handlers.append(_handler)
return func
return _decorator
def finish(self):
# BlockedException用于阻止后续handler继续执行
raise BlockedException
@classmethod
def finish(cls):
raise FinishedException
def reject(self):
# RejectedException用于阻止后续handler继续执行并将当前handler放回队列
@classmethod
def reject(cls):
raise RejectedException
async def run(self, event):
if not self.rule(event):
return
try:
if self.parser:
await self.parser(event, state) # type: ignore
for _ in range(len(self.handlers)):
handler = self.handlers.pop(0)
await handler(event, self.state)
except RejectedException:
# TODO: add tmp matcher to matcher tree
self.handlers.insert(handler, 0)
matcher = Matcher.new(self.rule,
self.scope,
self.permission,
self.block,
self.handlers,
temp=True,
default_state=self.state,
default_parser=self._default_parser,
args_parser=self._args_parser)
return
except PausedException:
# TODO: add tmp matcher to matcher tree
matcher = Matcher.new(self.rule,
self.scope,
self.permission,
self.block,
self.handlers,
temp=True,
default_state=self.state,
default_parser=self._default_parser,
args_parser=self._args_parser)
return
except FinishedException:
return
def on_message(rule: Rule,
scope="ALL",
@ -104,23 +165,23 @@ def on_message(rule: Rule,
block=True,
*,
handlers=[],
state={},
temp=False) -> Matcher:
temp=False,
state={}) -> Type[Matcher]:
# TODO: add matcher to matcher tree
return Matcher(rule,
return Matcher.new(rule,
scope,
permission,
block,
handlers=handlers,
state=state,
temp=temp)
temp=temp,
default_state=state)
def on_startswith(msg,
start: int = None,
end: int = None,
rule: Optional[Rule] = None,
**kwargs) -> Matcher:
**kwargs) -> Type[Matcher]:
return on_message(startswith(msg, start, end) &
rule, **kwargs) if rule else on_message(
startswith(msg, start, end), **kwargs)
@ -129,7 +190,7 @@ def on_startswith(msg,
def on_regex(pattern,
flags: Union[int, re.RegexFlag] = 0,
rule: Optional[Rule] = None,
**kwargs) -> Matcher:
**kwargs) -> Type[Matcher]:
return on_message(regex(pattern, flags) &
rule, **kwargs) if rule else on_message(
regex(pattern, flags), **kwargs)

View File

@ -1,11 +1,13 @@
import re
from typing import Union, Callable
from typing import Union, Callable, Optional
from .event import Event
class Rule:
def __init__(self, checker: Callable[["Event"], bool]):
self.checker = checker
def __init__(self, checker: Optional[Callable[[Event], bool]] = None):
self.checker = checker or (lambda event: True)
def __call__(self, event):
return self.checker(event)