2021-01-29 21:19:13 +08:00
|
|
|
|
from enum import Enum
|
2021-11-22 23:21:26 +08:00
|
|
|
|
from typing import Any, Dict, List, Type, Union, Iterable, Optional
|
2021-01-29 21:19:13 +08:00
|
|
|
|
|
|
|
|
|
from pydantic import validate_arguments
|
|
|
|
|
|
2021-11-22 23:21:26 +08:00
|
|
|
|
from nonebot.typing import overrides
|
2021-01-29 21:19:13 +08:00
|
|
|
|
from nonebot.adapters import Message as BaseMessage
|
|
|
|
|
from nonebot.adapters import MessageSegment as BaseMessageSegment
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MessageType(str, Enum):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""消息类型枚举类"""
|
2021-11-22 23:21:26 +08:00
|
|
|
|
|
|
|
|
|
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"
|
2021-01-29 21:19:13 +08:00
|
|
|
|
|
|
|
|
|
|
2021-06-18 01:23:13 +08:00
|
|
|
|
class MessageSegment(BaseMessageSegment["MessageChain"]):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
2021-02-23 23:34:24 +08:00
|
|
|
|
Mirai-API-HTTP 协议 MessageSegment 适配。具体方法参考 `mirai-api-http 消息类型`_
|
2021-02-01 01:04:30 +08:00
|
|
|
|
|
|
|
|
|
.. _mirai-api-http 消息类型:
|
|
|
|
|
https://github.com/project-mirai/mirai-api-http/blob/master/docs/MessageType.md
|
|
|
|
|
"""
|
|
|
|
|
|
2021-01-29 21:19:13 +08:00
|
|
|
|
type: MessageType
|
|
|
|
|
data: Dict[str, Any]
|
|
|
|
|
|
2021-06-18 01:23:13 +08:00
|
|
|
|
@classmethod
|
|
|
|
|
def get_message_class(cls) -> Type["MessageChain"]:
|
|
|
|
|
return MessageChain
|
|
|
|
|
|
2021-01-29 21:19:13 +08:00
|
|
|
|
@validate_arguments
|
2021-06-18 01:23:13 +08:00
|
|
|
|
@overrides(BaseMessageSegment)
|
|
|
|
|
def __init__(self, type: MessageType, **data: Any):
|
2021-11-22 23:21:26 +08:00
|
|
|
|
super().__init__(
|
|
|
|
|
type=type, data={k: v for k, v in data.items() if v is not None}
|
|
|
|
|
)
|
2021-01-29 21:19:13 +08:00
|
|
|
|
|
|
|
|
|
@overrides(BaseMessageSegment)
|
|
|
|
|
def __str__(self) -> str:
|
2021-11-22 23:21:26 +08:00
|
|
|
|
return self.data["text"] if self.is_text() else repr(self)
|
2021-02-07 12:40:31 +08:00
|
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
2021-11-22 23:21:26 +08:00
|
|
|
|
return "[mirai:%s]" % ",".join(
|
|
|
|
|
[
|
|
|
|
|
self.type.value,
|
|
|
|
|
*map(
|
|
|
|
|
lambda s: "%s=%r" % s,
|
|
|
|
|
self.data.items(),
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
)
|
2021-01-29 21:19:13 +08:00
|
|
|
|
|
|
|
|
|
@overrides(BaseMessageSegment)
|
|
|
|
|
def is_text(self) -> bool:
|
|
|
|
|
return self.type == MessageType.PLAIN
|
|
|
|
|
|
|
|
|
|
def as_dict(self) -> Dict[str, Any]:
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""导出可以被正常json序列化的结构体"""
|
2021-11-22 23:21:26 +08:00
|
|
|
|
return {"type": self.type.value, **self.data}
|
2021-01-29 21:19:13 +08:00
|
|
|
|
|
2021-01-30 21:51:51 +08:00
|
|
|
|
@classmethod
|
|
|
|
|
def source(cls, id: int, time: int):
|
|
|
|
|
return cls(type=MessageType.SOURCE, id=id, time=time)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2021-11-22 23:21:26 +08:00
|
|
|
|
def quote(
|
|
|
|
|
cls,
|
|
|
|
|
id: int,
|
|
|
|
|
group_id: int,
|
|
|
|
|
sender_id: int,
|
|
|
|
|
target_id: int,
|
|
|
|
|
origin: "MessageChain",
|
|
|
|
|
):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-02-23 23:34:24 +08:00
|
|
|
|
生成回复引用消息段
|
2021-02-01 01:04:30 +08:00
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``id: int``: 被引用回复的原消息的message_id
|
|
|
|
|
* ``group_id: int``: 被引用回复的原消息所接收的群号,当为好友消息时为0
|
|
|
|
|
* ``sender_id: int``: 被引用回复的原消息的发送者的QQ号
|
|
|
|
|
* ``target_id: int``: 被引用回复的原消息的接收者者的QQ号(或群号)
|
|
|
|
|
* ``origin: MessageChain``: 被引用回复的原消息的消息链对象
|
|
|
|
|
"""
|
2021-11-22 23:21:26 +08:00
|
|
|
|
return cls(
|
|
|
|
|
type=MessageType.QUOTE,
|
|
|
|
|
id=id,
|
|
|
|
|
groupId=group_id,
|
|
|
|
|
senderId=sender_id,
|
|
|
|
|
targetId=target_id,
|
|
|
|
|
origin=origin.export(),
|
|
|
|
|
)
|
2021-01-30 21:51:51 +08:00
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def at(cls, target: int):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-02-23 23:34:24 +08:00
|
|
|
|
@某个人
|
2021-02-01 01:04:30 +08:00
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``target: int``: 群员QQ号
|
|
|
|
|
"""
|
2021-01-30 21:51:51 +08:00
|
|
|
|
return cls(type=MessageType.AT, target=target)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def at_all(cls):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
@全体成员
|
|
|
|
|
"""
|
2021-01-30 21:51:51 +08:00
|
|
|
|
return cls(type=MessageType.AT_ALL)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def face(cls, face_id: Optional[int] = None, name: Optional[str] = None):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-02-23 23:34:24 +08:00
|
|
|
|
发送QQ表情
|
|
|
|
|
|
2021-02-01 01:04:30 +08:00
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``face_id: Optional[int]``: QQ表情编号,可选,优先高于name
|
|
|
|
|
* ``name: Optional[str]``: QQ表情拼音,可选
|
|
|
|
|
"""
|
2021-01-30 21:51:51 +08:00
|
|
|
|
return cls(type=MessageType.FACE, faceId=face_id, name=name)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def plain(cls, text: str):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-02-23 23:34:24 +08:00
|
|
|
|
纯文本消息
|
2021-02-01 01:04:30 +08:00
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``text: str``: 文字消息
|
|
|
|
|
"""
|
2021-01-30 21:51:51 +08:00
|
|
|
|
return cls(type=MessageType.PLAIN, text=text)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2021-11-22 23:21:26 +08:00
|
|
|
|
def image(
|
|
|
|
|
cls,
|
|
|
|
|
image_id: Optional[str] = None,
|
|
|
|
|
url: Optional[str] = None,
|
|
|
|
|
path: Optional[str] = None,
|
|
|
|
|
):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-02-23 23:34:24 +08:00
|
|
|
|
图片消息
|
|
|
|
|
|
2021-02-01 01:04:30 +08:00
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``image_id: Optional[str]``: 图片的image_id,群图片与好友图片格式不同。不为空时将忽略url属性
|
|
|
|
|
* ``url: Optional[str]``: 图片的URL,发送时可作网络图片的链接
|
|
|
|
|
* ``path: Optional[str]``: 图片的路径,发送本地图片
|
|
|
|
|
"""
|
2021-01-30 21:51:51 +08:00
|
|
|
|
return cls(type=MessageType.IMAGE, imageId=image_id, url=url, path=path)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
2021-11-22 23:21:26 +08:00
|
|
|
|
def flash_image(
|
|
|
|
|
cls,
|
|
|
|
|
image_id: Optional[str] = None,
|
|
|
|
|
url: Optional[str] = None,
|
|
|
|
|
path: Optional[str] = None,
|
|
|
|
|
):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-02-23 23:34:24 +08:00
|
|
|
|
闪照消息
|
|
|
|
|
|
2021-02-01 01:04:30 +08:00
|
|
|
|
:参数:
|
2021-02-23 23:34:24 +08:00
|
|
|
|
|
2021-02-01 01:04:30 +08:00
|
|
|
|
同 ``image``
|
|
|
|
|
"""
|
2021-11-22 23:21:26 +08:00
|
|
|
|
return cls(type=MessageType.FLASH_IMAGE, imageId=image_id, url=url, path=path)
|
2021-01-30 21:51:51 +08:00
|
|
|
|
|
|
|
|
|
@classmethod
|
2021-11-22 23:21:26 +08:00
|
|
|
|
def voice(
|
|
|
|
|
cls,
|
|
|
|
|
voice_id: Optional[str] = None,
|
|
|
|
|
url: Optional[str] = None,
|
|
|
|
|
path: Optional[str] = None,
|
|
|
|
|
):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
语音消息
|
|
|
|
|
|
2021-02-23 23:34:24 +08:00
|
|
|
|
:参数:
|
2021-02-01 01:04:30 +08:00
|
|
|
|
|
|
|
|
|
* ``voice_id: Optional[str]``: 语音的voice_id,不为空时将忽略url属性
|
|
|
|
|
* ``url: Optional[str]``: 语音的URL,发送时可作网络语音的链接
|
|
|
|
|
* ``path: Optional[str]``: 语音的路径,发送本地语音
|
|
|
|
|
"""
|
2021-11-22 23:21:26 +08:00
|
|
|
|
return cls(type=MessageType.FLASH_IMAGE, imageId=voice_id, url=url, path=path)
|
2021-01-30 21:51:51 +08:00
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def xml(cls, xml: str):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-02-23 23:34:24 +08:00
|
|
|
|
XML消息
|
2021-02-01 01:04:30 +08:00
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``xml: str``: XML文本
|
|
|
|
|
"""
|
2021-01-30 21:51:51 +08:00
|
|
|
|
return cls(type=MessageType.XML, xml=xml)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def json(cls, json: str):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-02-23 23:34:24 +08:00
|
|
|
|
Json消息
|
2021-02-01 01:04:30 +08:00
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``json: str``: Json文本
|
|
|
|
|
"""
|
2021-01-30 21:51:51 +08:00
|
|
|
|
return cls(type=MessageType.JSON, json=json)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def app(cls, content: str):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-02-23 23:34:24 +08:00
|
|
|
|
应用程序消息
|
2021-02-01 01:04:30 +08:00
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``content: str``: 内容
|
|
|
|
|
"""
|
2021-01-30 21:51:51 +08:00
|
|
|
|
return cls(type=MessageType.APP, content=content)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def poke(cls, name: str):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
2021-02-23 23:34:24 +08:00
|
|
|
|
戳一戳消息
|
2021-02-01 01:04:30 +08:00
|
|
|
|
|
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``name: str``: 戳一戳的类型
|
2021-02-01 14:24:45 +08:00
|
|
|
|
|
|
|
|
|
* ``Poke``: 戳一戳
|
|
|
|
|
* ``ShowLove``: 比心
|
|
|
|
|
* ``Like``: 点赞
|
|
|
|
|
* ``Heartbroken``: 心碎
|
|
|
|
|
* ``SixSixSix``: 666
|
|
|
|
|
* ``FangDaZhao``: 放大招
|
|
|
|
|
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
2021-01-30 21:51:51 +08:00
|
|
|
|
return cls(type=MessageType.POKE, name=name)
|
|
|
|
|
|
2021-01-29 21:19:13 +08:00
|
|
|
|
|
2021-05-10 00:54:15 +08:00
|
|
|
|
class MessageChain(BaseMessage[MessageSegment]):
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""
|
2021-02-07 17:16:12 +08:00
|
|
|
|
Mirai 协议 Message 适配
|
2021-02-23 23:34:24 +08:00
|
|
|
|
|
2021-02-01 01:04:30 +08:00
|
|
|
|
由于Mirai协议的Message实现较为特殊, 故使用MessageChain命名
|
|
|
|
|
"""
|
2021-01-29 21:19:13 +08:00
|
|
|
|
|
2021-06-18 01:23:13 +08:00
|
|
|
|
@classmethod
|
|
|
|
|
@overrides(BaseMessage)
|
|
|
|
|
def get_segment_class(cls) -> Type[MessageSegment]:
|
|
|
|
|
return MessageSegment
|
|
|
|
|
|
2021-01-29 21:19:13 +08:00
|
|
|
|
@overrides(BaseMessage)
|
|
|
|
|
def _construct(
|
2021-01-31 16:02:59 +08:00
|
|
|
|
self, message: Union[List[Dict[str, Any]], Iterable[MessageSegment]]
|
2021-01-29 21:19:13 +08:00
|
|
|
|
) -> List[MessageSegment]:
|
|
|
|
|
if isinstance(message, str):
|
2021-08-19 01:36:51 +08:00
|
|
|
|
return [MessageSegment.plain(text=message)]
|
2021-01-29 21:19:13 +08:00
|
|
|
|
return [
|
|
|
|
|
*map(
|
2021-11-22 23:21:26 +08:00
|
|
|
|
lambda x: x if isinstance(x, MessageSegment) else MessageSegment(**x),
|
|
|
|
|
message,
|
|
|
|
|
)
|
2021-01-29 21:19:13 +08:00
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def export(self) -> List[Dict[str, Any]]:
|
2021-02-01 01:04:30 +08:00
|
|
|
|
"""导出为可以被正常json序列化的数组"""
|
2021-11-22 23:21:26 +08:00
|
|
|
|
return [*map(lambda segment: segment.as_dict(), self.copy())] # type: ignore
|
2021-01-29 21:19:13 +08:00
|
|
|
|
|
2021-02-07 11:52:50 +08:00
|
|
|
|
def extract_first(self, *type: MessageType) -> Optional[MessageSegment]:
|
2021-02-07 21:26:45 +08:00
|
|
|
|
"""
|
|
|
|
|
:说明:
|
|
|
|
|
|
|
|
|
|
弹出该消息链的第一个消息
|
2021-02-23 23:34:24 +08:00
|
|
|
|
|
2021-02-07 21:26:45 +08:00
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* `*type: MessageType`: 指定的消息类型, 当指定后如类型不匹配不弹出
|
|
|
|
|
"""
|
2021-02-07 11:52:50 +08:00
|
|
|
|
if not len(self):
|
|
|
|
|
return None
|
|
|
|
|
first: MessageSegment = self[0]
|
|
|
|
|
if (not type) or (first.type in type):
|
|
|
|
|
return self.pop(0)
|
|
|
|
|
return None
|
|
|
|
|
|
2021-01-29 21:19:13 +08:00
|
|
|
|
def __repr__(self) -> str:
|
2021-11-22 23:21:26 +08:00
|
|
|
|
return f"<{self.__class__.__name__} {[*self.copy()]}>"
|