🏗️ change nonebot project structure

This commit is contained in:
yanyongyu
2021-02-11 17:29:16 +08:00
parent 3cc738c205
commit 31b8a5ff77
47 changed files with 2138 additions and 33 deletions

View File

@ -0,0 +1,29 @@
"""
\:\:\: warning
事件中为了使代码更加整洁, 我们采用了与PEP8相符的命名规则取代Mirai原有的驼峰命名
部分字段可能与文档在符号上不一致
\:\:\:
"""
from .base import (Event, GroupChatInfo, GroupInfo, PrivateChatInfo,
UserPermission)
from .message import *
from .notice import *
from .request import *
__all__ = [
'Event', 'GroupChatInfo', 'GroupInfo', 'PrivateChatInfo', 'UserPermission',
'MessageSource', 'MessageEvent', 'GroupMessage', 'FriendMessage',
'TempMessage', 'NoticeEvent', 'MuteEvent', 'BotMuteEvent', 'BotUnmuteEvent',
'MemberMuteEvent', 'MemberUnmuteEvent', 'BotJoinGroupEvent',
'BotLeaveEventActive', 'BotLeaveEventKick', 'MemberJoinEvent',
'MemberLeaveEventKick', 'MemberLeaveEventQuit', 'FriendRecallEvent',
'GroupRecallEvent', 'GroupStateChangeEvent', 'GroupNameChangeEvent',
'GroupEntranceAnnouncementChangeEvent', 'GroupMuteAllEvent',
'GroupAllowAnonymousChatEvent', 'GroupAllowConfessTalkEvent',
'GroupAllowMemberInviteEvent', 'MemberStateChangeEvent',
'MemberCardChangeEvent', 'MemberSpecialTitleChangeEvent',
'BotGroupPermissionChangeEvent', 'MemberPermissionChangeEvent',
'RequestEvent', 'NewFriendRequestEvent', 'MemberJoinRequestEvent',
'BotInvitedJoinGroupRequestEvent'
]

View File

@ -0,0 +1,133 @@
import json
from enum import Enum
from typing import Any, Dict, 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.log import logger
from nonebot.typing import overrides
class UserPermission(str, Enum):
"""
:说明:
用户权限枚举类
* ``OWNER``: 群主
* ``ADMINISTRATOR``: 群管理
* ``MEMBER``: 普通群成员
"""
OWNER = 'OWNER'
ADMINISTRATOR = 'ADMINISTRATOR'
MEMBER = 'MEMBER'
class GroupInfo(BaseModel):
id: int
name: str
permission: UserPermission
class GroupChatInfo(BaseModel):
id: int
name: str = Field(alias='memberName')
permission: UserPermission
group: GroupInfo
class PrivateChatInfo(BaseModel):
id: int
nickname: str
remark: str
class Event(BaseEvent):
"""
mirai-api-http 协议事件,字段与 mirai-api-http 一致。各事件字段参考 `mirai-api-http 事件类型`_
.. _mirai-api-http 事件类型:
https://github.com/project-mirai/mirai-api-http/blob/master/docs/EventType.md
"""
self_id: int
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__}: '
f'{e.errors()!r}. 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"]:
from . import message, notice, request, meta
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:
return self.type
@overrides(BaseEvent)
def get_event_description(self) -> str:
return str(self.normalize_dict())
@overrides(BaseEvent)
def get_message(self) -> BaseMessage:
raise ValueError("Event has no message!")
@overrides(BaseEvent)
def get_plaintext(self) -> str:
raise ValueError("Event has no message!")
@overrides(BaseEvent)
def get_user_id(self) -> str:
raise ValueError("Event has no message!")
@overrides(BaseEvent)
def get_session_id(self) -> str:
raise ValueError("Event has no message!")
@overrides(BaseEvent)
def is_tome(self) -> bool:
return False
def normalize_dict(self, **kwargs) -> Dict[str, Any]:
"""
返回可以被json正常反序列化的结构体
"""
return json.loads(self.json(**kwargs))

View File

@ -0,0 +1,85 @@
from datetime import datetime
from typing import Any, Optional
from pydantic import BaseModel, Field
from nonebot.typing import overrides
from ..message import MessageChain
from .base import Event, GroupChatInfo, PrivateChatInfo
class MessageSource(BaseModel):
id: int
time: datetime
class MessageEvent(Event):
"""消息事件基类"""
message_chain: MessageChain = Field(alias='messageChain')
source: Optional[MessageSource] = None
sender: Any
@overrides(Event)
def get_message(self) -> MessageChain:
return self.message_chain
@overrides(Event)
def get_plaintext(self) -> str:
return self.message_chain.extract_plain_text()
@overrides(Event)
def get_user_id(self) -> str:
raise NotImplementedError
@overrides(Event)
def get_session_id(self) -> str:
raise NotImplementedError
class GroupMessage(MessageEvent):
"""群消息事件"""
sender: GroupChatInfo
to_me: bool = False
@overrides(MessageEvent)
def get_session_id(self) -> str:
return f'group_{self.sender.group.id}_' + self.get_user_id()
@overrides(MessageEvent)
def get_user_id(self) -> str:
return str(self.sender.id)
@overrides(MessageEvent)
def is_tome(self) -> bool:
return self.to_me
class FriendMessage(MessageEvent):
"""好友消息事件"""
sender: PrivateChatInfo
@overrides(MessageEvent)
def get_user_id(self) -> str:
return str(self.sender.id)
@overrides(MessageEvent)
def get_session_id(self) -> str:
return 'friend_' + self.get_user_id()
@overrides(MessageEvent)
def is_tome(self) -> bool:
return True
class TempMessage(MessageEvent):
"""临时会话消息事件"""
sender: GroupChatInfo
@overrides(MessageEvent)
def get_session_id(self) -> str:
return f'temp_{self.sender.group.id}_' + self.get_user_id()
@overrides(MessageEvent)
def is_tome(self) -> bool:
return True

View File

@ -0,0 +1,31 @@
from .base import Event
class MetaEvent(Event):
"""元事件基类"""
qq: int
class BotOnlineEvent(MetaEvent):
"""Bot登录成功"""
pass
class BotOfflineEventActive(MetaEvent):
"""Bot主动离线"""
pass
class BotOfflineEventForce(MetaEvent):
"""Bot被挤下线"""
pass
class BotOfflineEventDropped(MetaEvent):
"""Bot被服务器断开或因网络问题而掉线"""
pass
class BotReloginEvent(MetaEvent):
"""Bot主动重新登录"""
pass

View File

@ -0,0 +1,156 @@
from typing import Any, Optional
from pydantic import Field
from .base import Event, GroupChatInfo, GroupInfo, UserPermission
class NoticeEvent(Event):
"""通知事件基类"""
pass
class MuteEvent(NoticeEvent):
"""禁言类事件基类"""
operator: GroupChatInfo
class BotMuteEvent(MuteEvent):
"""Bot被禁言"""
pass
class BotUnmuteEvent(MuteEvent):
"""Bot被取消禁言"""
pass
class MemberMuteEvent(MuteEvent):
"""群成员被禁言事件该成员不是Bot"""
duration_seconds: int = Field(alias='durationSeconds')
member: GroupChatInfo
operator: Optional[GroupChatInfo] = None
class MemberUnmuteEvent(MuteEvent):
"""群成员被取消禁言事件该成员不是Bot"""
member: GroupChatInfo
operator: Optional[GroupChatInfo] = None
class BotJoinGroupEvent(NoticeEvent):
"""Bot加入了一个新群"""
group: GroupInfo
class BotLeaveEventActive(BotJoinGroupEvent):
"""Bot主动退出一个群"""
pass
class BotLeaveEventKick(BotJoinGroupEvent):
"""Bot被踢出一个群"""
pass
class MemberJoinEvent(NoticeEvent):
"""新人入群的事件"""
member: GroupChatInfo
class MemberLeaveEventKick(MemberJoinEvent):
"""成员被踢出群该成员不是Bot"""
operator: Optional[GroupChatInfo] = None
class MemberLeaveEventQuit(MemberJoinEvent):
"""成员主动离群该成员不是Bot"""
pass
class FriendRecallEvent(NoticeEvent):
"""好友消息撤回"""
author_id: int = Field(alias='authorId')
message_id: int = Field(alias='messageId')
time: int
operator: int
class GroupRecallEvent(FriendRecallEvent):
"""群消息撤回"""
group: GroupInfo
operator: Optional[GroupChatInfo] = None
class GroupStateChangeEvent(NoticeEvent):
"""群变化事件基类"""
origin: Any
current: Any
group: GroupInfo
operator: Optional[GroupChatInfo] = None
class GroupNameChangeEvent(GroupStateChangeEvent):
"""某个群名改变"""
origin: str
current: str
class GroupEntranceAnnouncementChangeEvent(GroupStateChangeEvent):
"""某群入群公告改变"""
origin: str
current: str
class GroupMuteAllEvent(GroupStateChangeEvent):
"""全员禁言"""
origin: bool
current: bool
class GroupAllowAnonymousChatEvent(GroupStateChangeEvent):
"""匿名聊天"""
origin: bool
current: bool
class GroupAllowConfessTalkEvent(GroupStateChangeEvent):
"""坦白说"""
origin: bool
current: bool
class GroupAllowMemberInviteEvent(GroupStateChangeEvent):
"""允许群员邀请好友加群"""
origin: bool
current: bool
class MemberStateChangeEvent(NoticeEvent):
"""群成员变化事件基类"""
member: GroupChatInfo
operator: Optional[GroupChatInfo] = None
class MemberCardChangeEvent(MemberStateChangeEvent):
"""群名片改动"""
origin: str
current: str
class MemberSpecialTitleChangeEvent(MemberStateChangeEvent):
"""群头衔改动(只有群主有操作限权)"""
origin: str
current: str
class BotGroupPermissionChangeEvent(MemberStateChangeEvent):
"""Bot在群里的权限被改变"""
origin: UserPermission
current: UserPermission
class MemberPermissionChangeEvent(MemberStateChangeEvent):
"""成员权限改变的事件该成员不是Bot"""
origin: UserPermission
current: UserPermission

View File

@ -0,0 +1,170 @@
from typing import TYPE_CHECKING
from pydantic import Field
from typing_extensions import Literal
from .base import Event
if TYPE_CHECKING:
from ..bot import Bot
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')
async def approve(self, bot: "Bot"):
"""
:说明:
通过此人的好友申请
:参数:
* ``bot: Bot``: 当前的 ``Bot`` 对象
"""
return await bot.api.post('/resp/newFriendRequestEvent',
params={
'eventId': self.event_id,
'groupId': self.group_id,
'fromId': self.from_id,
'operate': 0
})
async def reject(self,
bot: "Bot",
operate: Literal[1, 2] = 1,
message: str = ''):
"""
:说明:
拒绝此人的好友申请
:参数:
* ``bot: Bot``: 当前的 ``Bot`` 对象
* ``operate: Literal[1, 2]``: 响应的操作类型
* ``1``: 拒绝添加好友
* ``2``: 拒绝添加好友并添加黑名单,不再接收该用户的好友申请
* ``message: str``: 回复的信息
"""
assert operate > 0
return await bot.api.post('/resp/newFriendRequestEvent',
params={
'eventId': self.event_id,
'groupId': self.group_id,
'fromId': self.from_id,
'operate': operate,
'message': message
})
class MemberJoinRequestEvent(RequestEvent):
"""用户入群申请Bot需要有管理员权限"""
from_id: int = Field(alias='fromId')
group_id: int = Field(alias='groupId')
group_name: str = Field(alias='groupName')
async def approve(self, bot: "Bot"):
"""
:说明:
通过此人的加群申请
:参数:
* ``bot: Bot``: 当前的 ``Bot`` 对象
"""
return await bot.api.post('/resp/memberJoinRequestEvent',
params={
'eventId': self.event_id,
'groupId': self.group_id,
'fromId': self.from_id,
'operate': 0
})
async def reject(self,
bot: "Bot",
operate: Literal[1, 2, 3, 4] = 1,
message: str = ''):
"""
:说明:
拒绝(忽略)此人的加群申请
:参数:
* ``bot: Bot``: 当前的 ``Bot`` 对象
* ``operate: Literal[1, 2, 3, 4]``: 响应的操作类型
* ``1``: 拒绝入群
* ``2``: 忽略请求
* ``3``: 拒绝入群并添加黑名单,不再接收该用户的入群申请
* ``4``: 忽略入群并添加黑名单,不再接收该用户的入群申请
* ``message: str``: 回复的信息
"""
assert operate > 0
return await bot.api.post('/resp/memberJoinRequestEvent',
params={
'eventId': self.event_id,
'groupId': self.group_id,
'fromId': self.from_id,
'operate': operate,
'message': message
})
class BotInvitedJoinGroupRequestEvent(RequestEvent):
"""Bot被邀请入群申请"""
from_id: int = Field(alias='fromId')
group_id: int = Field(alias='groupId')
group_name: str = Field(alias='groupName')
async def approve(self, bot: "Bot"):
"""
:说明:
通过这份被邀请入群申请
:参数:
* ``bot: Bot``: 当前的 ``Bot`` 对象
"""
return await bot.api.post('/resp/botInvitedJoinGroupRequestEvent',
params={
'eventId': self.event_id,
'groupId': self.group_id,
'fromId': self.from_id,
'operate': 0
})
async def reject(self, bot: "Bot", message: str = ""):
"""
:说明:
拒绝这份被邀请入群申请
:参数:
* ``bot: Bot``: 当前的 ``Bot`` 对象
* ``message: str``: 邀请消息
"""
return await bot.api.post('/resp/botInvitedJoinGroupRequestEvent',
params={
'eventId': self.event_id,
'groupId': self.group_id,
'fromId': self.from_id,
'operate': 1,
'message': message
})