"""Kook 适配器事件。"""
import asyncio
import inspect
from enum import IntEnum
from collections import UserDict
from typing import ( # type: ignore
TYPE_CHECKING,
Any,
Dict,
List,
Type,
Tuple,
Union,
Literal,
TypeVar,
Optional,
)
from pydantic import Field, HttpUrl, BaseModel, validator, root_validator
from iamai.event import Event
from .api import Role, User, Emoji, Guild, Channel
from .message import KookMessage, MessageDeserializer
if TYPE_CHECKING:
from . import KookAdapter
from .message import T_KookMSG
T_KookEvent = TypeVar("T_KookEvent", bound="KookEvent")
[docs]
class ResultStore:
_seq = 1
_futures: Dict[Tuple[str, int], asyncio.Future] = {}
_sn_map = {}
[docs]
@classmethod
def set_sn(cls, self_id: str, sn: int) -> None:
cls._sn_map[self_id] = sn
[docs]
@classmethod
def get_sn(cls, self_id: str) -> int:
return cls._sn_map.get(self_id, 0)
[docs]
class AttrDict(UserDict):
def __init__(self, data=None):
initial = dict(data) # type: ignore
for k in initial:
if isinstance(initial[k], dict):
initial[k] = AttrDict(initial[k]) # type: ignore
super().__init__(initial)
def __getattr__(self, name):
return self[name]
[docs]
class PermissionOverwrite(BaseModel):
role_id: Optional[int] = None
allow: Optional[int] = None
deny: Optional[int] = None
[docs]
class PermissionUser(BaseModel):
user: Optional[User] = None
allow: Optional[int] = None
deny: Optional[int] = None
[docs]
class ChannelRoleInfo(BaseModel):
"""频道角色权限详情"""
permission_overwrites: Optional[List[PermissionOverwrite]] = None
"""针对角色在该频道的权限覆写规则组成的列表"""
permission_users: Optional[List[PermissionUser]] = None
"""针对用户在该频道的权限覆写规则组成的列表"""
permission_sync: Optional[int] = None
"""权限设置是否与分组同步, 1 or 0"""
[docs]
class Quote(BaseModel):
"""引用消息"""
id_: Optional[str] = Field(None, alias="id")
"""引用消息 id"""
type: Optional[int] = None
"""引用消息类型"""
content: Optional[str] = None
"""引用消息内容"""
create_at: Optional[int] = None
"""引用消息创建时间(毫秒)"""
author: Optional[User] = None
"""作者的用户信息"""
[docs]
class Attachments(BaseModel):
"""附加的多媒体数据"""
type: Optional[str] = None
"""多媒体类型"""
url: Optional[str] = None
"""多媒体地址"""
name: Optional[str] = None
"""多媒体名"""
size: Optional[int] = None
"""大小 单位(B)"""
[docs]
class URL(BaseModel):
url: Optional[str] = None
"""资源的 url"""
[docs]
class ListReturn(BaseModel):
meta: Optional[Meta] = None
sort: Optional[Dict[str, Any]] = None
[docs]
class BlackList(BaseModel):
"""黑名单"""
user_id: Optional[str] = None
"""用户 id"""
created_time: Optional[int] = None
"""加入黑名单的时间戳(毫秒)"""
remark: Optional[str] = None
"""加入黑名单的原因"""
user: Optional[User] = None
"""用户"""
[docs]
class BlackListsReturn(ListReturn):
"""获取黑名单列表返回信息"""
blacklists: Optional[List[BlackList]] = Field(None, alias="items")
"""黑名单列表"""
[docs]
class MessageCreateReturn(BaseModel):
"""发送频道消息返回信息"""
msg_id: Optional[str] = None
"""服务端生成的消息 id"""
msg_timestamp: Optional[int] = None
"""消息发送时间(服务器时间戳)"""
nonce: Optional[str] = None
"""随机字符串"""
[docs]
class ChannelRoleReturn(BaseModel):
"""创建或更新频道角色权限返回信息"""
role_id: Optional[int] = None
user_id: Optional[str] = None
allow: Optional[int] = None
deny: Optional[int] = None
[docs]
class GuildsReturn(ListReturn):
guilds: Optional[List[Guild]] = Field(None, alias="items")
[docs]
class ChannelsReturn(ListReturn):
channels: Optional[List[Channel]] = Field(None, alias="items")
[docs]
class GuildUsersRetrun(ListReturn):
"""服务器中的用户列表"""
users: Optional[List[User]] = Field(None, alias="items")
"""用户列表"""
user_count: Optional[int] = None
"""用户数量"""
online_count: Optional[int] = None
"""在线用户数量"""
offline_count: Optional[int] = None
"""离线用户数量"""
[docs]
class Reaction(BaseModel):
emoji: Optional[Emoji] = None
count: Optional[int] = None
me: Optional[bool] = None
[docs]
class MentionInfo(BaseModel):
mention_part: Optional[List[dict]] = None
mention_role_part: Optional[List[dict]] = None
channel_part: Optional[List[dict]] = None
item_part: Optional[List[dict]] = None
[docs]
class BaseMessage(BaseModel):
id_: Optional[str] = Field(None, alias="id")
"""消息 ID"""
type: Optional[int] = None
"""消息类型"""
content: Optional[str] = None
"""消息内容"""
embeds: Optional[List[dict]] = None
"""超链接解析数据"""
attachments: Optional[Union[bool, Attachments]] = None
"""附加的多媒体数据"""
create_at: Optional[int] = None
"""创建时间"""
updated_at: Optional[int] = None
"""更新时间"""
reactions: Optional[List[Reaction]] = None
"""回应数据"""
image_name: Optional[str] = None
""""""
read_status: Optional[bool] = None
"""是否已读"""
quote: Optional[Quote] = None
"""引用数据"""
mention_info: Optional[MentionInfo] = None
"""引用特定用户或特定角色的信息"""
[docs]
class ChannelMessage(BaseMessage):
"""频道消息"""
author: Optional[User] = None
mention: Optional[List[Any]] = None
mention_all: Optional[bool] = None
mention_roles: Optional[List[Any]] = None
mention_here: Optional[bool] = None
[docs]
class DirectMessage(BaseMessage):
"""私聊消息"""
author_id: Optional[str] = None
"""作者的用户 ID"""
from_type: Optional[int] = None
"""from_type"""
msg_icon: Optional[str] = None
"""msg_icon"""
[docs]
class ChannelMessagesReturn(BaseModel):
"""获取私信聊天消息列表返回信息"""
direct_messages: Optional[List[ChannelMessage]] = Field(None, alias="items")
[docs]
class DirectMessagesReturn(BaseModel):
"""获取私信聊天消息列表返回信息"""
direct_messages: Optional[List[DirectMessage]] = Field(None, alias="items")
[docs]
class ReactionUser(User):
reaction_time: Optional[int] = None
[docs]
class TargetInfo(BaseModel):
"""私聊会话 目标用户信息"""
id_: Optional[str] = Field(None, alias="id")
"""目标用户 ID"""
username: Optional[str] = None
"""目标用户名"""
online: Optional[bool] = None
"""是否在线"""
avatar: Optional[str] = None
"""头像图片链接"""
[docs]
class UserChat(BaseModel):
"""私聊会话"""
code: Optional[str] = None
"""私信会话 Code"""
last_read_time: Optional[int] = None
"""上次阅读消息的时间 (毫秒)"""
latest_msg_time: Optional[int] = None
"""最新消息时间 (毫秒)"""
unread_count: Optional[int] = None
"""未读消息数"""
target_info: Optional[TargetInfo] = None
"""目标用户信息"""
[docs]
class UserChatsReturn(ListReturn):
"""获取私信聊天会话列表返回信息"""
user_chats: Optional[List[UserChat]] = Field(None, alias="items")
"""私聊会话列表"""
[docs]
class RolesReturn(ListReturn):
"""获取服务器角色列表返回信息"""
roles: Optional[List[Role]] = Field(None, alias="items")
"""服务器角色列表"""
[docs]
class GuilRoleReturn(BaseModel):
"""赋予或删除用户角色返回信息"""
user_id: Optional[str] = None
"""用户 id"""
guild_id: Optional[str] = None
"""服务器 id"""
roles: Optional[List[int]] = None
"""角色 id 的列表"""
[docs]
class IntimacyImg(BaseModel):
"""形象图片的总列表"""
id_: Optional[str] = Field(None, alias="id")
""" 形象图片的 id"""
url: Optional[str] = None
"""形象图片的地址"""
[docs]
class IntimacyIndexReturn(BaseModel):
"""获取用户亲密度返回信息"""
img_url: Optional[str] = None
"""机器人给用户显示的形象图片地址"""
social_info: Optional[str] = None
"""机器人显示给用户的社交信息"""
last_read: Optional[int] = None
"""用户上次查看的时间戳"""
score: Optional[int] = None
"""亲密度,0-2200"""
img_list: Optional[List[IntimacyImg]] = None
"""形象图片的总列表"""
[docs]
class GuildEmoji(BaseModel):
"""服务器表情"""
name: Optional[str] = None
"""表情的名称"""
id_: Optional[str] = Field(None, alias="id")
"""表情的 ID"""
user_info: Optional[User] = None
"""上传用户"""
[docs]
class GuildEmojisReturn(ListReturn):
"""获取服务器表情列表返回信息"""
roles: Optional[List[GuildEmoji]] = Field(None, alias="items")
"""服务器表情列表"""
[docs]
class Invite(BaseModel):
"""邀请信息"""
guild_id: Optional[str] = None
"""服务器 id"""
channel_id: Optional[str] = None
"""频道 id"""
url_code: Optional[str] = None
"""url code"""
url: Optional[str] = None
"""地址"""
user: Optional[User] = None
"""用户"""
[docs]
class InvitesReturn(ListReturn):
"""获取邀请列表返回信息"""
roles: Optional[List[Invite]] = Field(None, alias="items")
"""邀请列表"""
[docs]
class EventTypes(IntEnum):
"""
事件主要格式
Kook 协议事件,字段与 Kook 一致。各事件字段参考 `Kook 文档`
.. Kook 文档:
https://developer.kookapp.cn/doc/event/event-introduction#事件主要格式
"""
text = 1
image = 2
video = 3
file = 4
audio = 8
kmarkdown = 9
card = 10
sys = 255
[docs]
class SignalTypes(IntEnum):
"""
信令类型
Kook 协议信令,字段与 Kook 一致。各事件字段参考 `Kook 文档`
.. Kook 文档:
https://developer.kookapp.cn/doc/websocket#信令格式
"""
EVENT = 0
HELLO = 1
PING = 2
PONG = 3
RESUME = 4
RECONNECT = 5
RESUME_ACK = 6
SYS = 255
[docs]
class Attachment(BaseModel):
type: str
name: str
url: HttpUrl
file_type: Optional[str] = Field(None)
size: Optional[int] = Field(None)
duration: Optional[float] = Field(None)
width: Optional[int] = Field(None)
hight: Optional[int] = Field(None)
[docs]
class OriginEvent(Event["KookAdapter"]):
"""为了区分信令中非Event事件,增加了前置OriginEvent"""
__event__ = ""
post_type: str
[docs]
class Kmarkdown(BaseModel):
raw_content: str
mention_part: list
mention_role_part: list
[docs]
class EventMessage(BaseModel):
type: Union[int, str]
guild_id: Optional[str]
channel_name: Optional[str]
mention: Optional[List]
mention_all: Optional[bool]
mention_roles: Optional[List]
mention_here: Optional[bool]
nav_channels: Optional[List]
author: User
kmarkdown: Optional[Kmarkdown]
code: Optional[str] = None
attachments: Optional[Attachment] = None
content: KookMessage
[docs]
class KookEvent(OriginEvent):
"""
事件主要格式,来自 d 字段
Kook 协议事件,字段与 Kook 一致。各事件字段参考 `Kook 文档`
.. Kook 文档:
https://developer.kookapp.cn/doc/event/event-introduction
"""
__event__ = ""
channel_type: Literal["PERSON", "GROUP"]
type_: int = Field(alias="type")
"""1:文字消息\n2:图片消息\n3:视频消息\n4:文件消息\n8:音频消息\n9:KMarkdown\n10:card消息\n255:系统消息\n其它的暂未开放"""
target_id: str
"""
发送目的\n
频道消息类时, 代表的是频道 channel_id\n
如果 channel_type 为 GROUP 组播且 type 为 255 系统消息时,则代表服务器 guild_id"""
author_id: Optional[str] = None
content: KookMessage
msg_id: str
msg_timestamp: int
nonce: str
extra: Extra
user_id: str
post_type: str
self_id: Optional[str] = None # onebot兼容
# Message Events
[docs]
class MessageEvent(KookEvent):
"""消息事件"""
__event__ = "message"
post_type: Literal["message"] = "message"
message_type: str # group private 其实是person
sub_type: str
event: EventMessage
def __repr__(self) -> str:
return f'Event<{self.post_type}>: "{self.content}"'
[docs]
def get_plain_text(self) -> str:
"""获取消息的纯文本内容。
Returns:
消息的纯文本内容。
"""
return self.content.get_plain_text() # type: ignore
[docs]
async def reply(self, msg: "T_KookMSG") -> Dict[str, Any]:
"""回复消息。
Args:
msg: 回复消息的内容,同 `call_api()` 方法。
Returns:
API 请求响应。
"""
raise NotImplementedError
[docs]
class PrivateMessageEvent(MessageEvent):
"""私聊消息"""
__event__ = "message.private"
message_type: Literal["private"]
[docs]
async def reply(self, msg: "T_KookMSG") -> Dict[str, Any]:
return await self.adapter.call_api(
api="direct-message/create", target_id=self.author_id, content=msg
)
[docs]
class ChannelMessageEvent(MessageEvent):
"""公共频道消息"""
__event__ = "message.group"
message_type: Literal["group"]
group_id: str
[docs]
async def reply(self, msg: "T_KookMSG") -> Dict[str, Any]:
return await self.adapter.call_api(
"message/create", target_id=self.target_id, content=msg
)
# Notice Events
[docs]
class NoticeEvent(KookEvent):
"""通知事件"""
__event__ = "notice"
post_type: Literal["notice"]
notice_type: str
def __repr__(self) -> str:
return f'Event<{self.post_type}>: "{self.content}"'
# Channel Events
[docs]
class ChannelNoticeEvent(NoticeEvent):
"""频道消息事件"""
__event__ = "notice"
group_id: int
[docs]
class ChannelAddReactionEvent(ChannelNoticeEvent):
"""频道内用户添加 reaction"""
__event__ = "notice.added_reaction"
notice_type: Literal["added_reaction"]
[docs]
class ChannelDeletedReactionEvent(ChannelNoticeEvent):
"""频道内用户删除 reaction"""
__event__ = "notice.deleted_reaction"
notice_type: Literal["deleted_reaction"]
[docs]
class ChannelUpdatedMessageEvent(ChannelNoticeEvent):
"""频道消息更新"""
__event__ = "notice.updated_message"
notice_type: Literal["updated_message"]
[docs]
class ChannelDeleteMessageEvent(ChannelNoticeEvent):
"""频道消息被删除"""
__event__ = "notice.deleted_message"
notice_type: Literal["deleted_message"]
[docs]
class ChannelAddedEvent(ChannelNoticeEvent):
"""新增频道"""
__event__ = "notice.added_channel"
notice_type: Literal["added_channel"]
[docs]
class ChannelUpdatedEvent(ChannelNoticeEvent):
"""修改频道信息"""
__event__ = "notice.updated_channel"
notice_type: Literal["updated_channel"]
[docs]
class ChannelDeleteEvent(ChannelNoticeEvent):
"""删除频道"""
__event__ = "notice.deleted_channel"
notice_type: Literal["deleted_channel"]
[docs]
class ChannelPinnedMessageEvent(ChannelNoticeEvent):
"""新增频道置顶消息"""
__event__ = "notice.pinned_message"
notice_type: Literal["pinned_message"]
[docs]
class ChannelUnpinnedMessageEvent(ChannelNoticeEvent):
"""取消频道置顶消息"""
__event__ = "notice.unpinned_message"
notice_type: Literal["unpinned_message"]
# Private Events
[docs]
class PrivateNoticeEvent(NoticeEvent):
"私聊消息事件"
[docs]
class PrivateUpdateMessageEvent(PrivateNoticeEvent):
"""私聊消息更新"""
__event__ = "notice.updated_private_message"
notice_type: Literal["updated_private_message"]
[docs]
class PrivateDeleteMessageEvent(PrivateNoticeEvent):
"""私聊消息删除"""
__event__ = "notice.deleted_private_message"
notice_type: Literal["deleted_private_message"]
[docs]
class PrivateAddReactionEvent(PrivateNoticeEvent):
"""私聊内用户添加 reaction"""
__event__ = "notice.private_added_reaction"
notice_type: Literal["private_added_reaction"]
[docs]
class PrivateDeleteReactionEvent(PrivateNoticeEvent):
"""私聊内用户取消 reaction"""
__event__ = "notice.private_deleted_reaction"
notice_type: Literal["private_deleted_reaction"]
# Guild Events
[docs]
class GuildNoticeEvent(NoticeEvent):
"""服务器相关事件"""
group_id: int
[docs]
def get_guild_id(self):
return self.target_id # type: ignore
# Guild Member Events
[docs]
class GuildMemberNoticeEvent(GuildNoticeEvent):
"""服务器成员相关事件"""
pass
[docs]
class GuildMemberIncreaseNoticeEvent(GuildMemberNoticeEvent):
"""新成员加入服务器"""
__event__ = "notice.joined_guild"
notice_type: Literal["joined_guild"]
[docs]
class GuildMemberDecreaseNoticeEvent(GuildMemberNoticeEvent):
"""服务器成员退出"""
__event__ = "notice.exited_guild"
notice_type: Literal["exited_guild"]
[docs]
class GuildMemberUpdateNoticeEvent(GuildMemberNoticeEvent):
"""服务器成员信息更新(修改昵称)"""
__event__ = "notice.updated_guild_member"
notice_type: Literal["updated_guild_member"]
[docs]
class GuildMemberOnlineNoticeEvent(GuildMemberNoticeEvent):
"""服务器成员上线"""
__event__ = "notice.guild_member_online"
notice_type: Literal["guild_member_online"]
[docs]
class GuildMemberOfflineNoticeEvent(GuildMemberNoticeEvent):
"""服务器成员下线"""
__event__ = "notice.guild_member_offline"
notice_type: Literal["guild_member_offline"]
# Guild Role Events
[docs]
class GuildRoleNoticeEvent(GuildNoticeEvent):
"""服务器角色相关事件"""
[docs]
class GuildRoleAddNoticeEvent(GuildRoleNoticeEvent):
"""服务器角色增加"""
__event__ = "notice.added_role"
notice_type: Literal["added_role"]
[docs]
class GuildRoleDeleteNoticeEvent(GuildRoleNoticeEvent):
"""服务器角色增加"""
__event__ = "notice.deleted_role"
notice_type: Literal["deleted_role"]
[docs]
class GuildRoleUpdateNoticeEvent(GuildRoleNoticeEvent):
"""服务器角色增加"""
__event__ = "notice.updated_role"
notice_type: Literal["updated_role"]
# Guild Events
[docs]
class GuildUpdateNoticeEvent(GuildNoticeEvent):
"""服务器信息更新"""
__event__ = "notice.updated_guild"
notice_type: Literal["updated_guild"]
[docs]
class GuildDeleteNoticeEvent(GuildNoticeEvent):
"""服务器删除"""
__event__ = "notice.deleted_guild"
notice_type: Literal["deleted_guild"]
[docs]
class GuildAddBlockListNoticeEvent(GuildNoticeEvent):
"""服务器封禁用户"""
__event__ = "notice.added_block_list"
notice_type: Literal["added_block_list"]
[docs]
class GuildDeleteBlockListNoticeEvent(GuildNoticeEvent):
"""服务器取消封禁用户"""
__event__ = "notice.deleted_block_list"
notice_type: Literal["deleted_block_list"]
# User Events
[docs]
class UserNoticeEvent(NoticeEvent):
"""用户相关事件列表"""
group_id: int
[docs]
class UserJoinAudioChannelNoticeEvent(UserNoticeEvent):
"""用户加入语音频道"""
__event__ = "notice.joined_channel"
notice_type: Literal["joined_channel"]
[docs]
class UserJoinAudioChannelEvent(UserNoticeEvent):
"""用户退出语音频道"""
__event__ = "notice.exited_channel"
notice_type: Literal["exited_channel"]
[docs]
class UserInfoUpdateNoticeEvent(UserNoticeEvent):
"""
用户信息更新
该事件与服务器无关, 遵循以下条件:
- 仅当用户的 用户名 或 头像 变更时
- 仅通知与该用户存在关联的用户或 Bot
a. 存在聊天会话
b. 双方好友关系
"""
__event__ = "notice.user_updated"
notice_type: Literal["user_updated"]
[docs]
class SelfJoinGuildNoticeEvent(NoticeEvent):
"""
自己新加入服务器
当自己被邀请或主动加入新的服务器时, 产生该事件
"""
__event__ = "notice.self_joined_guild"
notice_type: Literal["self_joined_guild"]
user_id: str
group_id: int
[docs]
class SelfExitGuildNoticeEvent(NoticeEvent):
"""
自己退出服务器
当自己被踢出服务器或被拉黑或主动退出服务器时, 产生该事件
"""
__event__ = "notice.self_exited_guild"
notice_type: Literal["self_exited_guild"]
user_id: str
group_id: int
[docs]
class CartBtnClickNoticeEvent(NoticeEvent):
"""
Card 消息中的 Button 点击事件
"""
__event__ = "notice.message_btn_click"
notice_type: Literal["message_btn_click"]
user_id: str
group_id: int
# Meta Events
# 事件类映射
_kook_events = {
model.__event__: model
for model in globals().values()
if inspect.isclass(model) and issubclass(model, OriginEvent)
}
[docs]
def get_event_class(
post_type: str, event_type: str, sub_type: Optional[str] = None
) -> Type[T_KookEvent]: # type: ignore
"""根据接收到的消息类型返回对应的事件类。
Args:
post_type: 请求类型。
event_type: 事件类型。
sub_type: 子类型。
Returns:
对应的事件类。
"""
if sub_type is None:
return _kook_events[".".join((post_type, event_type))] # type: ignore
return (
_kook_events.get(".".join((post_type, event_type, sub_type)))
or _kook_events[".".join((post_type, event_type))]
) # type: ignore