🚧 basically completed event serialize

This commit is contained in:
Mix 2021-01-29 21:19:13 +08:00
parent 5a9798121c
commit 0bb0d16d93
8 changed files with 233 additions and 50 deletions

View File

@ -2,9 +2,13 @@ from pprint import pprint
from typing import Optional
from nonebot.adapters import Bot as BaseBot
from nonebot.adapters import Event as BaseEvent
from nonebot.drivers import Driver, WebSocket
from nonebot.message import handle_event
from nonebot.typing import overrides
from .event import Event
class MiraiBot(BaseBot):
@ -28,12 +32,13 @@ class MiraiBot(BaseBot):
@overrides(BaseBot)
async def handle_message(self, message: dict):
pprint(message)
event = Event.new(message)
await handle_event(self, event)
@overrides(BaseBot)
async def call_api(self, api: str, **data):
return super().call_api(api, **data)
@overrides(BaseBot)
async def send(self, event: "Event", message: str, **kwargs):
async def send(self, event: "BaseEvent", message: str, **kwargs):
return super().send(event, message, **kwargs)

View File

@ -0,0 +1,4 @@
from .base import Event, SenderInfo, PrivateSenderInfo, SenderGroup
from .message import *
from .notice import *
from .request import *

View File

@ -1,13 +1,12 @@
from enum import Enum
from pydantic import BaseModel, Field
from typing import Dict, Any, Optional, Type
from pydantic import BaseModel, Field, ValidationError
from typing_extensions import Literal
from nonebot.adapters import Event as BaseEvent
from nonebot.adapters import Message as BaseMessage
from nonebot.typing import overrides
from .constants import EVENT_TYPES
from nonebot.log import logger
class SenderPermission(str, Enum):
@ -29,12 +28,54 @@ class SenderInfo(BaseModel):
group: SenderGroup
class PrivateSenderInfo(BaseModel):
id: int
nickname: str
remark: str
class Event(BaseEvent):
type: str
@classmethod
def new(cls, data: Dict[str, Any]) -> "Event":
type = data['type']
def all_subclasses(cls: Type[Event]):
return set(cls.__subclasses__()).union(
[s for c in cls.__subclasses__() for s in all_subclasses(c)])
event_class: Optional[Type[Event]] = None
for subclass in all_subclasses(cls):
if subclass.__name__ != type:
continue
event_class = subclass
if event_class is None:
return Event.parse_obj(data)
while issubclass(event_class, Event):
try:
return event_class.parse_obj(data)
except ValidationError as e:
logger.info(
f'Failed to parse {data} to class {event_class.__name__}: {e}. '
'Fallback to parent class.')
event_class = event_class.__base__
raise ValueError(f'Failed to serialize {data}.')
@overrides(BaseEvent)
def get_type(self) -> Literal["message", "notice", "request", "meta_event"]:
return EVENT_TYPES.get(self.type, 'meta_event')
from . import message, notice, request
if isinstance(self, message.MessageEvent):
return 'message'
elif isinstance(self, notice.NoticeEvent):
return 'notice'
elif isinstance(self, request.RequestEvent):
return 'request'
else:
return 'meta_event'
@overrides(BaseEvent)
def get_event_name(self) -> str:

View File

@ -1,31 +0,0 @@
from typing import List, Dict
from typing_extensions import Literal
EventType = Literal["message", "notice", "request", "meta_event"]
_EVENT_CLASSIFY: Dict[EventType, List[str]] = {
# XXX Reference: https://github.com/project-mirai/mirai-api-http/blob/v1.9.7/docs/EventType.md
'meta_event': [
'BotOnlineEvent', 'BotOfflineEventActive', 'BotOfflineEventForce',
'BotOfflineEventDropped', 'BotReloginEvent'
],
'notice': [
'BotGroupPermissionChangeEvent', 'BotMuteEvent', 'BotUnmuteEvent',
'BotJoinGroupEvent', 'BotLeaveEventActive', 'BotLeaveEventKick',
'GroupRecallEvent', 'FriendRecallEvent', 'GroupNameChangeEvent',
'GroupEntranceAnnouncementChangeEvent', 'GroupMuteAllEvent',
'GroupAllowAnonymousChatEvent', 'GroupAllowConfessTalkEvent',
'GroupAllowMemberInviteEvent', 'MemberJoinEvent',
'MemberLeaveEventKick', 'MemberLeaveEventQuit', 'MemberCardChangeEvent',
'MemberSpecialTitleChangeEvent', 'MemberPermissionChangeEvent',
'MemberMuteEvent', 'MemberUnmuteEvent'
],
'request': [
'NewFriendRequestEvent', 'MemberJoinRequestEvent',
'BotInvitedJoinGroupRequestEvent'
],
'message': ['GroupMessage', 'FriendMessage', 'TempMessage']
}
EVENT_TYPES: Dict[str, EventType] = {}
for event_type, events in _EVENT_CLASSIFY.items():
_EVENT_TYPES.update({k: event_type for k in events}) # type: ignore

View File

@ -0,0 +1,40 @@
from typing import TYPE_CHECKING
from pydantic import Field
from .base import Event, SenderInfo, PrivateSenderInfo
from ..message import MessageChain
from nonebot.typing import overrides
class MessageEvent(Event):
message_chain: MessageChain = Field(alias='messageChain')
sender: SenderInfo
@overrides(Event)
def get_message(self) -> MessageChain:
return self.message_chain
@overrides(Event)
def get_plaintext(self) -> str:
return self.message_chain.__str__()
@overrides(Event)
def get_user_id(self) -> str:
return str(self.sender.id)
@overrides(Event)
def get_session_id(self) -> str:
return self.get_user_id()
class GroupMessage(MessageEvent):
pass
class FriendMessage(MessageEvent):
sender: PrivateSenderInfo
class TempMessage(MessageEvent):
pass

View File

@ -5,34 +5,34 @@ from pydantic import Field
from .base import Event, SenderGroup, SenderInfo, SenderPermission
class BaseNoticeEvent(Event):
class NoticeEvent(Event):
pass
class BaseMuteEvent(BaseNoticeEvent):
class MuteEvent(NoticeEvent):
operator: SenderInfo
class BotMuteEvent(BaseMuteEvent):
class BotMuteEvent(MuteEvent):
pass
class BotUnmuteEvent(BaseMuteEvent):
class BotUnmuteEvent(MuteEvent):
pass
class MemberMuteEvent(BaseMuteEvent):
class MemberMuteEvent(MuteEvent):
duration_seconds: int = Field(alias='durationSeconds')
member: SenderInfo
operator: Optional[SenderInfo] = None
class MemberUnmuteEvent(BaseMuteEvent):
class MemberUnmuteEvent(MuteEvent):
member: SenderInfo
operator: Optional[SenderInfo] = None
class BotJoinGroupEvent(BaseNoticeEvent):
class BotJoinGroupEvent(NoticeEvent):
group: SenderGroup
@ -44,7 +44,7 @@ class BotLeaveEventKick(BotJoinGroupEvent):
pass
class MemberJoinEvent(BaseNoticeEvent):
class MemberJoinEvent(NoticeEvent):
member: SenderInfo
@ -56,7 +56,7 @@ class MemberLeaveEventKick(MemberJoinEvent):
operator: Optional[SenderInfo] = None
class FriendRecallEvent(BaseNoticeEvent):
class FriendRecallEvent(NoticeEvent):
author_id: int = Field(alias='authorId')
message_id: int = Field(alias='messageId')
time: int
@ -68,7 +68,7 @@ class GroupRecallEvent(FriendRecallEvent):
operator: Optional[SenderInfo] = None
class GroupStateChangeEvent(BaseNoticeEvent):
class GroupStateChangeEvent(NoticeEvent):
origin: Any
current: Any
group: SenderGroup
@ -105,7 +105,7 @@ class GroupAllowMemberInviteEvent(GroupStateChangeEvent):
current: bool
class MemberStateChangeEvent(BaseNoticeEvent):
class MemberStateChangeEvent(NoticeEvent):
member: SenderInfo
operator: Optional[SenderInfo] = None

View File

@ -1 +1,26 @@
from pydantic import Field
from .base import Event
class RequestEvent(Event):
event_id: int = Field(alias='eventId')
message: str
nick: str
class NewFriendRequestEvent(RequestEvent):
from_id: int = Field(alias='fromId')
group_id: int = Field(0, alias='groupId')
class MemberJoinRequestEvent(RequestEvent):
from_id: int = Field(alias='fromId')
group_id: int = Field(alias='groupId')
group_name: str = Field(alias='groupName')
class BotInvitedJoinGroupRequestEvent(RequestEvent):
from_id: int = Field(alias='fromId')
group_id: int = Field(alias='groupId')
group_name: str = Field(alias='groupName')

View File

@ -1 +1,100 @@
from nonebot.adapters import Message
from enum import Enum
from typing import Any, Dict, List, Union, Iterable
from pydantic import validate_arguments
from nonebot.adapters import Message as BaseMessage
from nonebot.adapters import MessageSegment as BaseMessageSegment
from nonebot.typing import overrides
class MessageType(str, Enum):
SOURCE = 'Source'
QUOTE = 'Quote'
AT = 'At'
AT_ALL = 'AtAll'
FACE = 'Face'
PLAIN = 'Plain'
IMAGE = 'Image'
FLASH_IMAGE = 'FlashImage'
VOICE = 'Voice'
XML = 'Xml'
JSON = 'Json'
APP = 'App'
POKE = 'Poke'
class MessageSegment(BaseMessageSegment):
type: MessageType
data: Dict[str, Any]
@overrides(BaseMessageSegment)
@validate_arguments
def __init__(self, type: MessageType, **data):
super().__init__(type=type, data=data)
@overrides(BaseMessageSegment)
def __str__(self) -> str:
if self.is_text():
return self.data.get('text', '')
return '[mirai:%s]' % ','.join([
self.type.value,
*map(
lambda s: '%s=%r' % s,
self.data.items(),
),
])
@overrides(BaseMessageSegment)
def __add__(self, other) -> "MessageChain":
return MessageChain(self) + other
@overrides(BaseMessageSegment)
def __radd__(self, other) -> "MessageChain":
return MessageChain(other) + self
@overrides(BaseMessageSegment)
def is_text(self) -> bool:
return self.type == MessageType.PLAIN
def as_dict(self) -> Dict[str, Any]:
return {'type': self.type.value, **self.data}
class MessageChain(BaseMessage):
@overrides(BaseMessage)
def __init__(self, message: Union[List[Dict[str, Any]], MessageSegment],
**kwargs):
super().__init__(**kwargs)
if isinstance(message, MessageSegment):
self.append(message)
elif isinstance(message, Iterable):
self.extend(self._construct(message))
else:
raise ValueError(
f'Type {type(message).__name__} is not supported in mirai adapter.'
)
@overrides(BaseMessage)
def _construct(
self, message: Iterable[Union[Dict[str, Any], MessageSegment]]
) -> List[MessageSegment]:
if isinstance(message, str):
raise ValueError(
"String operation is not supported in mirai adapter")
return [
*map(
lambda segment: segment if isinstance(segment, MessageSegment)
else MessageSegment(**segment), message)
]
def export(self) -> List[Dict[str, Any]]:
chain: List[Dict[str, Any]] = []
for segment in self.copy():
segment: MessageSegment
chain.append({'type': segment.type.value, **segment.data})
return chain
def __repr__(self) -> str:
return f'<{self.__class__.__name__} {[*self.copy()]}>'