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): class PausedException(Exception):
"""Block a message from further handling""" """Block a message from further handling and try to receive a new message"""
pass pass
class RejectedException(Exception): class RejectedException(Exception):
"""Reject a message and return current handler back""" """Reject a message and return current handler back"""
pass pass
class FinishedException(Exception):
"""Finish handling a message"""
pass

View File

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

View File

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