🚧 add event parser

This commit is contained in:
yanyongyu 2020-12-09 19:57:49 +08:00
parent 2c97902f4e
commit 783efeaf0e
7 changed files with 202 additions and 118 deletions

View File

@ -6,10 +6,10 @@
""" """
import abc import abc
from typing_extensions import Literal
from functools import reduce, partial from functools import reduce, partial
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Any, Dict, Union, TypeVar, Optional, Callable, Iterable, Awaitable, Generic, TYPE_CHECKING from typing import Any, Dict, Union, Optional, Callable, Iterable, Awaitable, TYPE_CHECKING
from typing_extensions import Literal
from pydantic import BaseModel from pydantic import BaseModel
@ -162,6 +162,14 @@ class Event(abc.ABC, BaseModel):
def get_session_id(self) -> str: def get_session_id(self) -> str:
raise NotImplementedError raise NotImplementedError
@abc.abstractmethod
def get_message(self) -> "Message":
raise NotImplementedError
@abc.abstractmethod
def get_plaintext(self) -> str:
raise NotImplementedError
# T = TypeVar("T", bound=BaseModel) # T = TypeVar("T", bound=BaseModel)

View File

@ -10,7 +10,7 @@ CQHTTP (OneBot) v11 协议适配
https://github.com/howmanybots/onebot/blob/master/README.md https://github.com/howmanybots/onebot/blob/master/README.md
""" """
from .event import Event from .event import CQHTTPEvent
from .message import Message, MessageSegment from .message import Message, MessageSegment
from .utils import log, escape, unescape, _b2s from .utils import log, escape, unescape, _b2s
from .bot import Bot, _check_at_me, _check_nickname, _check_reply, _handle_api_result from .bot import Bot, _check_at_me, _check_nickname, _check_reply, _handle_api_result

View File

@ -15,9 +15,9 @@ from nonebot.adapters import Bot as BaseBot
from nonebot.exception import RequestDenied from nonebot.exception import RequestDenied
from .utils import log from .utils import log
from .event import Reply, CQHTTPEvent, MessageEvent
from .message import Message, MessageSegment from .message import Message, MessageSegment
from .exception import NetworkError, ApiNotAvailable, ActionFailed from .exception import NetworkError, ApiNotAvailable, ActionFailed
from .event import Reply, CQHTTPEvent, MessageEvent, get_event_model
if TYPE_CHECKING: if TYPE_CHECKING:
from nonebot.drivers import Driver, WebSocket from nonebot.drivers import Driver, WebSocket
@ -297,7 +297,20 @@ class Bot(BaseBot):
return return
try: try:
event = CQHTTPEvent.parse_obj(message) post_type = message['post_type']
detail_type = message.get(f"{post_type}_type")
detail_type = f".{detail_type}" if detail_type else ""
sub_type = message.get("sub_type")
sub_type = f".{sub_type}" if sub_type else ""
models = get_event_model(f".{post_type}{detail_type}{sub_type}")
for model in models:
try:
event = model.parse_obj(message)
break
except Exception as e:
log("DEBUG", "Event Parser Error", e)
else:
event = CQHTTPEvent.parse_obj(message)
# Check whether user is calling me # Check whether user is calling me
await _check_reply(self, event) await _check_reply(self, event)

View File

@ -1,7 +1,9 @@
from typing import Optional import inspect
from typing_extensions import Literal from typing_extensions import Literal
from typing import Type, List, Optional
from pydantic import BaseModel from pydantic import BaseModel
from pygtrie import StringTrie
from nonebot.adapters import Event from nonebot.adapters import Event
from nonebot.utils import escape_tag from nonebot.utils import escape_tag
from nonebot.typing import overrides from nonebot.typing import overrides
@ -210,6 +212,7 @@ from .message import Message
class CQHTTPEvent(Event): class CQHTTPEvent(Event):
__event__ = ""
time: int time: int
self_id: int self_id: int
post_type: Literal["message", "notice", "request", "meta_event"] post_type: Literal["message", "notice", "request", "meta_event"]
@ -226,6 +229,14 @@ class CQHTTPEvent(Event):
def get_event_description(self) -> str: def get_event_description(self) -> str:
return str(self.dict()) return str(self.dict())
@overrides(Event)
def get_message(self) -> Message:
raise ValueError("Event has no message!")
@overrides(Event)
def get_plaintext(self) -> str:
raise ValueError("Event has no message!")
# Models # Models
class Sender(BaseModel): class Sender(BaseModel):
@ -284,6 +295,7 @@ class Status(BaseModel):
# Message Events # Message Events
class MessageEvent(CQHTTPEvent): class MessageEvent(CQHTTPEvent):
__event__ = "message"
post_type: Literal["message"] post_type: Literal["message"]
sub_type: str sub_type: str
user_id: int user_id: int
@ -302,8 +314,17 @@ class MessageEvent(CQHTTPEvent):
return f"{self.post_type}.{self.message_type}" + (f".{sub_type}" return f"{self.post_type}.{self.message_type}" + (f".{sub_type}"
if sub_type else "") if sub_type else "")
@overrides(CQHTTPEvent)
def get_message(self) -> Message:
return self.message
@overrides(CQHTTPEvent)
def get_plaintext(self) -> str:
return self.message.extract_plain_text()
class PrivateMessageEvent(MessageEvent): class PrivateMessageEvent(MessageEvent):
__event__ = "message.private"
message_type: Literal["private"] message_type: Literal["private"]
@overrides(CQHTTPEvent) @overrides(CQHTTPEvent)
@ -316,6 +337,7 @@ class PrivateMessageEvent(MessageEvent):
class GroupMessageEvent(MessageEvent): class GroupMessageEvent(MessageEvent):
__event__ = "message.group"
message_type: Literal["group"] message_type: Literal["group"]
group_id: int group_id: int
anonymous: Anonymous anonymous: Anonymous
@ -333,6 +355,7 @@ class GroupMessageEvent(MessageEvent):
# Notice Events # Notice Events
class NoticeEvent(CQHTTPEvent): class NoticeEvent(CQHTTPEvent):
__event__ = "notice"
post_type: Literal["notice"] post_type: Literal["notice"]
notice_type: str notice_type: str
@ -344,6 +367,7 @@ class NoticeEvent(CQHTTPEvent):
class GroupUploadNoticeEvent(NoticeEvent): class GroupUploadNoticeEvent(NoticeEvent):
__event__ = "notice.group_upload"
notice_type: Literal["group_upload"] notice_type: Literal["group_upload"]
user_id: int user_id: int
group_id: int group_id: int
@ -351,6 +375,7 @@ class GroupUploadNoticeEvent(NoticeEvent):
class GroupAdminNoticeEvent(NoticeEvent): class GroupAdminNoticeEvent(NoticeEvent):
__event__ = "notice.group_admin"
notice_type: Literal["group_admin"] notice_type: Literal["group_admin"]
sub_type: str sub_type: str
user_id: int user_id: int
@ -358,6 +383,7 @@ class GroupAdminNoticeEvent(NoticeEvent):
class GroupDecreaseNoticeEvent(NoticeEvent): class GroupDecreaseNoticeEvent(NoticeEvent):
__event__ = "notice.group_decrease"
notice_type: Literal["group_decrease"] notice_type: Literal["group_decrease"]
sub_type: str sub_type: str
user_id: int user_id: int
@ -366,6 +392,7 @@ class GroupDecreaseNoticeEvent(NoticeEvent):
class GroupIncreaseNoticeEvent(NoticeEvent): class GroupIncreaseNoticeEvent(NoticeEvent):
__event__ = "notice.group_increase"
notice_type: Literal["group_increase"] notice_type: Literal["group_increase"]
sub_type: str sub_type: str
user_id: int user_id: int
@ -374,6 +401,7 @@ class GroupIncreaseNoticeEvent(NoticeEvent):
class GroupBanNoticeEvent(NoticeEvent): class GroupBanNoticeEvent(NoticeEvent):
__event__ = "notice.group_ban"
notice_type: Literal["group_ban"] notice_type: Literal["group_ban"]
sub_type: str sub_type: str
user_id: int user_id: int
@ -383,11 +411,13 @@ class GroupBanNoticeEvent(NoticeEvent):
class FriendAddNoticeEvent(NoticeEvent): class FriendAddNoticeEvent(NoticeEvent):
__event__ = "notice.friend_add"
notice_type: Literal["friend_add"] notice_type: Literal["friend_add"]
user_id: int user_id: int
class GroupRecallNoticeEvent(NoticeEvent): class GroupRecallNoticeEvent(NoticeEvent):
__event__ = "notice.group_recall"
notice_type: Literal["group_recall"] notice_type: Literal["group_recall"]
user_id: int user_id: int
group_id: int group_id: int
@ -396,12 +426,14 @@ class GroupRecallNoticeEvent(NoticeEvent):
class FriendRecallNoticeEvent(NoticeEvent): class FriendRecallNoticeEvent(NoticeEvent):
__event__ = "notice.friend_recall"
notice_type: Literal["friend_recall"] notice_type: Literal["friend_recall"]
user_id: int user_id: int
message_id: int message_id: int
class NotifyEvent(NoticeEvent): class NotifyEvent(NoticeEvent):
__event__ = "notice.notify"
notice_type: Literal["notify"] notice_type: Literal["notify"]
sub_type: str sub_type: str
user_id: int user_id: int
@ -409,19 +441,26 @@ class NotifyEvent(NoticeEvent):
class PokeNotifyEvent(NotifyEvent): class PokeNotifyEvent(NotifyEvent):
__event__ = "notice.notify.poke"
sub_type: Literal["poke"]
target_id: int target_id: int
class LuckyKingNotifyEvent(NotifyEvent): class LuckyKingNotifyEvent(NotifyEvent):
__event__ = "notice.notify.lucky_king"
sub_type: Literal["lucky_king"]
target_id: int target_id: int
class HonorNotifyEvent(NotifyEvent): class HonorNotifyEvent(NotifyEvent):
__event__ = "notice.notify.honor"
sub_type: Literal["honor"]
honor_type: str honor_type: str
# Request Events # Request Events
class RequestEvent(CQHTTPEvent): class RequestEvent(CQHTTPEvent):
__event__ = "request"
post_type: Literal["request"] post_type: Literal["request"]
request_type: str request_type: str
@ -433,6 +472,7 @@ class RequestEvent(CQHTTPEvent):
class FriendRequestEvent(RequestEvent): class FriendRequestEvent(RequestEvent):
__event__ = "request.friend"
request_type: Literal["friend"] request_type: Literal["friend"]
user_id: int user_id: int
comment: str comment: str
@ -440,6 +480,7 @@ class FriendRequestEvent(RequestEvent):
class GroupRequestEvent(RequestEvent): class GroupRequestEvent(RequestEvent):
__event__ = "request.group"
request_type: Literal["group"] request_type: Literal["group"]
sub_type: str sub_type: str
group_id: int group_id: int
@ -450,6 +491,7 @@ class GroupRequestEvent(RequestEvent):
# Meta Events # Meta Events
class MetaEvent(CQHTTPEvent): class MetaEvent(CQHTTPEvent):
__event__ = "meta_event"
post_type: Literal["meta_event"] post_type: Literal["meta_event"]
meta_event_type: str meta_event_type: str
@ -465,11 +507,26 @@ class MetaEvent(CQHTTPEvent):
class LifecycleMetaEvent(MetaEvent): class LifecycleMetaEvent(MetaEvent):
__event__ = "meta_event.lifecycle"
meta_event_type: Literal["lifecycle"] meta_event_type: Literal["lifecycle"]
sub_type: str sub_type: str
class HeartbeatMetaEvent(MetaEvent): class HeartbeatMetaEvent(MetaEvent):
__event__ = "meta_event.heartbeat"
meta_event_type: Literal["heartbeat"] meta_event_type: Literal["heartbeat"]
status: Status status: Status
interval: int interval: int
_t = StringTrie(separator=".")
model = None
for model in globals().values():
if not inspect.isclass(model) or not issubclass(model, CQHTTPEvent):
continue
_t["." + model.__event__] = model
def get_event_model(event_name) -> List[Type[CQHTTPEvent]]:
return [model.value for model in _t.prefixes("." + event_name)][::-1]

View File

@ -203,7 +203,8 @@ class Matcher(metaclass=MatcherMeta):
- ``bool``: 是否满足匹配规则 - ``bool``: 是否满足匹配规则
""" """
return (event.type == (cls.type or event.type) and event_type = event.get_type()
return (event_type == (cls.type or event_type) and
await cls.rule(bot, event, state)) await cls.rule(bot, event, state))
@classmethod @classmethod
@ -303,7 +304,7 @@ class Matcher(metaclass=MatcherMeta):
if parser: if parser:
await parser(bot, event, state) await parser(bot, event, state)
else: else:
state[state["_current_key"]] = str(event.message) state[state["_current_key"]] = str(event.get_message())
cls.handlers.append(_key_getter) cls.handlers.append(_key_getter)
cls.handlers.append(_key_parser) cls.handlers.append(_key_parser)
@ -427,7 +428,8 @@ class Matcher(metaclass=MatcherMeta):
Matcher.new( Matcher.new(
self.type, self.type,
Rule(), Rule(),
USER(event.user_id, perm=self.permission), # type:ignore USER(event.get_session_id(),
perm=self.permission), # type:ignore
self.handlers, self.handlers,
temp=True, temp=True,
priority=0, priority=0,
@ -439,7 +441,8 @@ class Matcher(metaclass=MatcherMeta):
Matcher.new( Matcher.new(
self.type, self.type,
Rule(), Rule(),
USER(event.user_id, perm=self.permission), # type:ignore USER(event.get_session_id(),
perm=self.permission), # type:ignore
self.handlers, self.handlers,
temp=True, temp=True,
priority=0, priority=0,

View File

@ -81,19 +81,19 @@ class Permission:
async def _message(bot: "Bot", event: "Event") -> bool: async def _message(bot: "Bot", event: "Event") -> bool:
return event.type == "message" return event.get_type() == "message"
async def _notice(bot: "Bot", event: "Event") -> bool: async def _notice(bot: "Bot", event: "Event") -> bool:
return event.type == "notice" return event.get_type() == "notice"
async def _request(bot: "Bot", event: "Event") -> bool: async def _request(bot: "Bot", event: "Event") -> bool:
return event.type == "request" return event.get_type() == "request"
async def _metaevent(bot: "Bot", event: "Event") -> bool: async def _metaevent(bot: "Bot", event: "Event") -> bool:
return event.type == "meta_event" return event.get_type() == "meta_event"
MESSAGE = Permission(_message) MESSAGE = Permission(_message)
@ -114,7 +114,7 @@ METAEVENT = Permission(_metaevent)
""" """
def USER(*user: int, perm: Permission = Permission()): def USER(*user: str, perm: Permission = Permission()):
""" """
:说明: :说明:
@ -122,104 +122,94 @@ def USER(*user: int, perm: Permission = Permission()):
:参数: :参数:
* ``*user: int``: 白名单 * ``*user: str``: 白名单
* ``perm: Permission``: 需要同时满足的权限 * ``perm: Permission``: 需要同时满足的权限
""" """
async def _user(bot: "Bot", event: "Event") -> bool: async def _user(bot: "Bot", event: "Event") -> bool:
return event.type == "message" and event.user_id in user and await perm( return event.get_type() == "message" and event.get_session_id(
bot, event) ) in user and await perm(bot, event)
return Permission(_user) return Permission(_user)
async def _private(bot: "Bot", event: "Event") -> bool: # async def _private(bot: "Bot", event: "Event") -> bool:
return event.type == "message" and event.detail_type == "private" # return event.get_type() == "message" and event.detail_type == "private"
# async def _private_friend(bot: "Bot", event: "Event") -> bool:
# return (event.get_type() == "message" and event.detail_type == "private" and
# event.sub_type == "friend")
async def _private_friend(bot: "Bot", event: "Event") -> bool: # async def _private_group(bot: "Bot", event: "Event") -> bool:
return (event.type == "message" and event.detail_type == "private" and # return (event.get_type() == "message" and event.detail_type == "private" and
event.sub_type == "friend") # event.sub_type == "group")
# async def _private_other(bot: "Bot", event: "Event") -> bool:
# return (event.get_type() == "message" and event.detail_type == "private" and
# event.sub_type == "other")
async def _private_group(bot: "Bot", event: "Event") -> bool: # PRIVATE = Permission(_private)
return (event.type == "message" and event.detail_type == "private" and # """
event.sub_type == "group") # - **说明**: 匹配任意私聊消息类型事件
# """
# PRIVATE_FRIEND = Permission(_private_friend)
# """
# - **说明**: 匹配任意好友私聊消息类型事件
# """
# PRIVATE_GROUP = Permission(_private_group)
# """
# - **说明**: 匹配任意群临时私聊消息类型事件
# """
# PRIVATE_OTHER = Permission(_private_other)
# """
# - **说明**: 匹配任意其他私聊消息类型事件
# """
# async def _group(bot: "Bot", event: "Event") -> bool:
# return event.get_type() == "message" and event.detail_type == "group"
async def _private_other(bot: "Bot", event: "Event") -> bool: # async def _group_member(bot: "Bot", event: "Event") -> bool:
return (event.type == "message" and event.detail_type == "private" and # return (event.get_type() == "message" and event.detail_type == "group" and
event.sub_type == "other") # event.sender.get("role") == "member")
# async def _group_admin(bot: "Bot", event: "Event") -> bool:
# return (event.get_type() == "message" and event.detail_type == "group" and
# event.sender.get("role") == "admin")
PRIVATE = Permission(_private) # async def _group_owner(bot: "Bot", event: "Event") -> bool:
""" # return (event.get_type() == "message" and event.detail_type == "group" and
- **说明**: 匹配任意私聊消息类型事件 # event.sender.get("role") == "owner")
"""
PRIVATE_FRIEND = Permission(_private_friend)
"""
- **说明**: 匹配任意好友私聊消息类型事件
"""
PRIVATE_GROUP = Permission(_private_group)
"""
- **说明**: 匹配任意群临时私聊消息类型事件
"""
PRIVATE_OTHER = Permission(_private_other)
"""
- **说明**: 匹配任意其他私聊消息类型事件
"""
# GROUP = Permission(_group)
# """
# - **说明**: 匹配任意群聊消息类型事件
# """
# GROUP_MEMBER = Permission(_group_member)
# """
# - **说明**: 匹配任意群员群聊消息类型事件
async def _group(bot: "Bot", event: "Event") -> bool: # \:\:\:warning 警告
return event.type == "message" and event.detail_type == "group" # 该权限通过 event.sender 进行判断且不包含管理员以及群主!
# \:\:\:
# """
# GROUP_ADMIN = Permission(_group_admin)
# """
# - **说明**: 匹配任意群管理员群聊消息类型事件
# """
# GROUP_OWNER = Permission(_group_owner)
# """
# - **说明**: 匹配任意群主群聊消息类型事件
# """
# async def _superuser(bot: "Bot", event: "Event") -> bool:
# return event.get_type(
# ) == "message" and event.user_id in bot.config.superusers
async def _group_member(bot: "Bot", event: "Event") -> bool: # SUPERUSER = Permission(_superuser)
return (event.type == "message" and event.detail_type == "group" and # """
event.sender.get("role") == "member") # - **说明**: 匹配任意超级用户消息类型事件
# """
# EVERYBODY = MESSAGE
async def _group_admin(bot: "Bot", event: "Event") -> bool: # """
return (event.type == "message" and event.detail_type == "group" and # - **说明**: 匹配任意消息类型事件
event.sender.get("role") == "admin") # """
async def _group_owner(bot: "Bot", event: "Event") -> bool:
return (event.type == "message" and event.detail_type == "group" and
event.sender.get("role") == "owner")
GROUP = Permission(_group)
"""
- **说明**: 匹配任意群聊消息类型事件
"""
GROUP_MEMBER = Permission(_group_member)
"""
- **说明**: 匹配任意群员群聊消息类型事件
\:\:\:warning 警告
该权限通过 event.sender 进行判断且不包含管理员以及群主
\:\:\:
"""
GROUP_ADMIN = Permission(_group_admin)
"""
- **说明**: 匹配任意群管理员群聊消息类型事件
"""
GROUP_OWNER = Permission(_group_owner)
"""
- **说明**: 匹配任意群主群聊消息类型事件
"""
async def _superuser(bot: "Bot", event: "Event") -> bool:
return event.type == "message" and event.user_id in bot.config.superusers
SUPERUSER = Permission(_superuser)
"""
- **说明**: 匹配任意超级用户消息类型事件
"""
EVERYBODY = MESSAGE
"""
- **说明**: 匹配任意消息类型事件
"""

View File

@ -119,7 +119,7 @@ class TrieRule:
@classmethod @classmethod
def get_value(cls, bot: "Bot", event: "Event", def get_value(cls, bot: "Bot", event: "Event",
state: State) -> Tuple[Dict[str, Any], Dict[str, Any]]: state: State) -> Tuple[Dict[str, Any], Dict[str, Any]]:
if event.type != "message": if event.get_type() != "message":
state["_prefix"] = {"raw_command": None, "command": None} state["_prefix"] = {"raw_command": None, "command": None}
state["_suffix"] = {"raw_command": None, "command": None} state["_suffix"] = {"raw_command": None, "command": None}
return { return {
@ -132,12 +132,14 @@ class TrieRule:
prefix = None prefix = None
suffix = None suffix = None
message = event.message[0] message = event.get_message()
if message.type == "text": message_seg = message[0]
prefix = cls.prefix.longest_prefix(str(message).lstrip()) if message_seg.type == "text":
message_r = event.message[-1] prefix = cls.prefix.longest_prefix(str(message_seg).lstrip())
if message_r.type == "text": message_seg_r = message[-1]
suffix = cls.suffix.longest_prefix(str(message_r).rstrip()[::-1]) if message_seg_r.type == "text":
suffix = cls.suffix.longest_prefix(
str(message_seg_r).rstrip()[::-1])
state["_prefix"] = { state["_prefix"] = {
"raw_command": prefix.key, "raw_command": prefix.key,
@ -181,7 +183,10 @@ def startswith(msg: str) -> Rule:
""" """
async def _startswith(bot: "Bot", event: "Event", state: State) -> bool: async def _startswith(bot: "Bot", event: "Event", state: State) -> bool:
return event.plain_text.startswith(msg) if event.get_type() != "message":
return False
text = event.get_plaintext()
return text.startswith(msg)
return Rule(_startswith) return Rule(_startswith)
@ -198,7 +203,9 @@ def endswith(msg: str) -> Rule:
""" """
async def _endswith(bot: "Bot", event: "Event", state: State) -> bool: async def _endswith(bot: "Bot", event: "Event", state: State) -> bool:
return event.plain_text.endswith(msg) if event.get_type() != "message":
return False
return event.get_plaintext().endswith(msg)
return Rule(_endswith) return Rule(_endswith)
@ -215,8 +222,10 @@ def keyword(*keywords: str) -> Rule:
""" """
async def _keyword(bot: "Bot", event: "Event", state: State) -> bool: async def _keyword(bot: "Bot", event: "Event", state: State) -> bool:
return bool(event.plain_text and if event.get_type() != "message":
any(keyword in event.plain_text for keyword in keywords)) return False
text = event.get_plaintext()
return bool(text and any(keyword in text for keyword in keywords))
return Rule(_keyword) return Rule(_keyword)
@ -287,7 +296,9 @@ def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
pattern = re.compile(regex, flags) pattern = re.compile(regex, flags)
async def _regex(bot: "Bot", event: "Event", state: State) -> bool: async def _regex(bot: "Bot", event: "Event", state: State) -> bool:
matched = pattern.search(str(event.message)) if event.get_type() != "message":
return False
matched = pattern.search(str(event.get_message()))
if matched: if matched:
state["_matched"] = matched.group() state["_matched"] = matched.group()
return True return True
@ -298,18 +309,20 @@ def regex(regex: str, flags: Union[int, re.RegexFlag] = 0) -> Rule:
return Rule(_regex) return Rule(_regex)
def to_me() -> Rule: # def to_me() -> Rule:
""" # """
:说明: # :说明:
通过 ``event.to_me`` 判断消息是否是发送给机器人 # 通过 ``event.to_me`` 判断消息是否是发送给机器人
:参数: # :参数:
* # * 无
""" # """
async def _to_me(bot: "Bot", event: "Event", state: State) -> bool: # async def _to_me(bot: "Bot", event: "Event", state: State) -> bool:
return bool(event.to_me) # if event.get_type() != "message":
# return False
# return bool(event.to_me)
return Rule(_to_me) # return Rule(_to_me)