🚧 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 typing import Optional
from nonebot.adapters import Bot as BaseBot from nonebot.adapters import Bot as BaseBot
from nonebot.adapters import Event as BaseEvent
from nonebot.drivers import Driver, WebSocket from nonebot.drivers import Driver, WebSocket
from nonebot.message import handle_event
from nonebot.typing import overrides from nonebot.typing import overrides
from .event import Event
class MiraiBot(BaseBot): class MiraiBot(BaseBot):
@ -28,12 +32,13 @@ class MiraiBot(BaseBot):
@overrides(BaseBot) @overrides(BaseBot)
async def handle_message(self, message: dict): async def handle_message(self, message: dict):
pprint(message) event = Event.new(message)
await handle_event(self, event)
@overrides(BaseBot) @overrides(BaseBot)
async def call_api(self, api: str, **data): async def call_api(self, api: str, **data):
return super().call_api(api, **data) return super().call_api(api, **data)
@overrides(BaseBot) @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) 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 enum import Enum
from typing import Dict, Any, Optional, Type
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, ValidationError
from typing_extensions import Literal from typing_extensions import Literal
from nonebot.adapters import Event as BaseEvent from nonebot.adapters import Event as BaseEvent
from nonebot.adapters import Message as BaseMessage from nonebot.adapters import Message as BaseMessage
from nonebot.typing import overrides from nonebot.typing import overrides
from nonebot.log import logger
from .constants import EVENT_TYPES
class SenderPermission(str, Enum): class SenderPermission(str, Enum):
@ -29,12 +28,54 @@ class SenderInfo(BaseModel):
group: SenderGroup group: SenderGroup
class PrivateSenderInfo(BaseModel):
id: int
nickname: str
remark: str
class Event(BaseEvent): class Event(BaseEvent):
type: str 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) @overrides(BaseEvent)
def get_type(self) -> Literal["message", "notice", "request", "meta_event"]: 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) @overrides(BaseEvent)
def get_event_name(self) -> str: 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 from .base import Event, SenderGroup, SenderInfo, SenderPermission
class BaseNoticeEvent(Event): class NoticeEvent(Event):
pass pass
class BaseMuteEvent(BaseNoticeEvent): class MuteEvent(NoticeEvent):
operator: SenderInfo operator: SenderInfo
class BotMuteEvent(BaseMuteEvent): class BotMuteEvent(MuteEvent):
pass pass
class BotUnmuteEvent(BaseMuteEvent): class BotUnmuteEvent(MuteEvent):
pass pass
class MemberMuteEvent(BaseMuteEvent): class MemberMuteEvent(MuteEvent):
duration_seconds: int = Field(alias='durationSeconds') duration_seconds: int = Field(alias='durationSeconds')
member: SenderInfo member: SenderInfo
operator: Optional[SenderInfo] = None operator: Optional[SenderInfo] = None
class MemberUnmuteEvent(BaseMuteEvent): class MemberUnmuteEvent(MuteEvent):
member: SenderInfo member: SenderInfo
operator: Optional[SenderInfo] = None operator: Optional[SenderInfo] = None
class BotJoinGroupEvent(BaseNoticeEvent): class BotJoinGroupEvent(NoticeEvent):
group: SenderGroup group: SenderGroup
@ -44,7 +44,7 @@ class BotLeaveEventKick(BotJoinGroupEvent):
pass pass
class MemberJoinEvent(BaseNoticeEvent): class MemberJoinEvent(NoticeEvent):
member: SenderInfo member: SenderInfo
@ -56,7 +56,7 @@ class MemberLeaveEventKick(MemberJoinEvent):
operator: Optional[SenderInfo] = None operator: Optional[SenderInfo] = None
class FriendRecallEvent(BaseNoticeEvent): class FriendRecallEvent(NoticeEvent):
author_id: int = Field(alias='authorId') author_id: int = Field(alias='authorId')
message_id: int = Field(alias='messageId') message_id: int = Field(alias='messageId')
time: int time: int
@ -68,7 +68,7 @@ class GroupRecallEvent(FriendRecallEvent):
operator: Optional[SenderInfo] = None operator: Optional[SenderInfo] = None
class GroupStateChangeEvent(BaseNoticeEvent): class GroupStateChangeEvent(NoticeEvent):
origin: Any origin: Any
current: Any current: Any
group: SenderGroup group: SenderGroup
@ -105,7 +105,7 @@ class GroupAllowMemberInviteEvent(GroupStateChangeEvent):
current: bool current: bool
class MemberStateChangeEvent(BaseNoticeEvent): class MemberStateChangeEvent(NoticeEvent):
member: SenderInfo member: SenderInfo
operator: Optional[SenderInfo] = None operator: Optional[SenderInfo] = None

View File

@ -1 +1,26 @@
from pydantic import Field
from .base import Event 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()]}>'