2020-12-30 20:08:22 +08:00
|
|
|
|
from copy import copy
|
2021-11-12 11:53:42 +08:00
|
|
|
|
from typing import Any, Dict, Type, Union, Mapping, Iterable, cast
|
2020-12-06 02:30:19 +08:00
|
|
|
|
|
2020-12-30 20:08:22 +08:00
|
|
|
|
from nonebot.typing import overrides
|
2021-11-12 11:53:42 +08:00
|
|
|
|
from nonebot.adapters import Message as BaseMessage
|
|
|
|
|
from nonebot.adapters import MessageSegment as BaseMessageSegment
|
2020-12-30 18:33:54 +08:00
|
|
|
|
|
2020-12-03 00:59:32 +08:00
|
|
|
|
|
2021-06-18 01:23:13 +08:00
|
|
|
|
class MessageSegment(BaseMessageSegment["Message"]):
|
2020-12-03 00:59:32 +08:00
|
|
|
|
"""
|
|
|
|
|
钉钉 协议 MessageSegment 适配。具体方法参考协议消息段类型或源码。
|
|
|
|
|
"""
|
|
|
|
|
|
2021-06-18 01:23:13 +08:00
|
|
|
|
@classmethod
|
2020-12-30 20:08:22 +08:00
|
|
|
|
@overrides(BaseMessageSegment)
|
2021-06-18 01:23:13 +08:00
|
|
|
|
def get_message_class(cls) -> Type["Message"]:
|
|
|
|
|
return Message
|
2020-12-03 00:59:32 +08:00
|
|
|
|
|
2020-12-30 20:08:22 +08:00
|
|
|
|
@overrides(BaseMessageSegment)
|
2021-06-18 01:23:13 +08:00
|
|
|
|
def __str__(self) -> str:
|
2021-09-13 01:18:25 +08:00
|
|
|
|
"""
|
|
|
|
|
该消息段所代表的 str,在命令匹配部分使用,
|
|
|
|
|
钉钉目前只支持匹配 text 命令
|
|
|
|
|
"""
|
2020-12-03 00:59:32 +08:00
|
|
|
|
if self.type == "text":
|
2020-12-30 00:36:29 +08:00
|
|
|
|
return str(self.data["content"])
|
2020-12-03 00:59:32 +08:00
|
|
|
|
return ""
|
|
|
|
|
|
2021-09-13 01:18:25 +08:00
|
|
|
|
def __bool__(self) -> bool:
|
|
|
|
|
"""
|
|
|
|
|
因为暂时还不支持 text 和 markdown 之外的其他复杂消息段的 `__str__`(也不太需要),
|
|
|
|
|
会导致判断非这两种类型的消息段的布尔值为 true 的时候出错
|
|
|
|
|
"""
|
|
|
|
|
return self.data is not None
|
|
|
|
|
|
2020-12-30 20:08:22 +08:00
|
|
|
|
@overrides(BaseMessageSegment)
|
2020-12-29 12:12:35 +08:00
|
|
|
|
def is_text(self) -> bool:
|
|
|
|
|
return self.type == "text"
|
|
|
|
|
|
2020-12-30 00:36:29 +08:00
|
|
|
|
@staticmethod
|
|
|
|
|
def atAll() -> "MessageSegment":
|
2020-12-31 17:58:09 +08:00
|
|
|
|
"""@全体"""
|
2020-12-30 00:36:29 +08:00
|
|
|
|
return MessageSegment("at", {"isAtAll": True})
|
2020-12-03 00:59:32 +08:00
|
|
|
|
|
2020-12-30 00:36:29 +08:00
|
|
|
|
@staticmethod
|
|
|
|
|
def atMobiles(*mobileNumber: str) -> "MessageSegment":
|
2020-12-31 17:58:09 +08:00
|
|
|
|
"""@指定手机号人员"""
|
2020-12-30 00:36:29 +08:00
|
|
|
|
return MessageSegment("at", {"atMobiles": list(mobileNumber)})
|
2020-12-03 00:59:32 +08:00
|
|
|
|
|
2021-01-29 14:31:36 +08:00
|
|
|
|
@staticmethod
|
|
|
|
|
def atDingtalkIds(*dingtalkIds: str) -> "MessageSegment":
|
|
|
|
|
"""@指定 id,@ 默认会在消息段末尾。
|
|
|
|
|
所以你可以在消息中使用 @{senderId} 占位,发送出去之后 @ 就会出现在占位的位置:
|
|
|
|
|
```python
|
|
|
|
|
message = MessageSegment.text(f"@{event.senderId},你好")
|
|
|
|
|
message += MessageSegment.atDingtalkIds(event.senderId)
|
|
|
|
|
```
|
|
|
|
|
"""
|
|
|
|
|
return MessageSegment("at", {"atDingtalkIds": list(dingtalkIds)})
|
|
|
|
|
|
2020-12-03 00:59:32 +08:00
|
|
|
|
@staticmethod
|
2020-12-30 00:36:29 +08:00
|
|
|
|
def text(text: str) -> "MessageSegment":
|
2020-12-31 17:58:09 +08:00
|
|
|
|
"""发送 ``text`` 类型消息"""
|
2020-12-30 00:36:29 +08:00
|
|
|
|
return MessageSegment("text", {"content": text})
|
2020-12-03 00:59:32 +08:00
|
|
|
|
|
2020-12-30 18:33:54 +08:00
|
|
|
|
@staticmethod
|
|
|
|
|
def image(picURL: str) -> "MessageSegment":
|
2020-12-31 17:58:09 +08:00
|
|
|
|
"""发送 ``image`` 类型消息"""
|
2020-12-30 18:33:54 +08:00
|
|
|
|
return MessageSegment("image", {"picURL": picURL})
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def extension(dict_: dict) -> "MessageSegment":
|
2021-07-28 16:32:50 +08:00
|
|
|
|
"""标记 text 文本的 extension 属性,需要与 text 消息段相加。"""
|
2020-12-30 18:33:54 +08:00
|
|
|
|
return MessageSegment("extension", dict_)
|
|
|
|
|
|
2021-01-29 14:31:36 +08:00
|
|
|
|
@staticmethod
|
|
|
|
|
def code(code_language: str, code: str) -> "Message":
|
2021-07-28 16:32:50 +08:00
|
|
|
|
"""发送 code 消息段"""
|
2021-01-29 14:31:36 +08:00
|
|
|
|
message = MessageSegment.text(code)
|
|
|
|
|
message += MessageSegment.extension({
|
|
|
|
|
"text_type": "code_snippet",
|
|
|
|
|
"code_language": code_language
|
|
|
|
|
})
|
|
|
|
|
return message
|
|
|
|
|
|
2020-12-03 00:59:32 +08:00
|
|
|
|
@staticmethod
|
|
|
|
|
def markdown(title: str, text: str) -> "MessageSegment":
|
2020-12-31 17:58:09 +08:00
|
|
|
|
"""发送 ``markdown`` 类型消息"""
|
2020-12-30 00:36:29 +08:00
|
|
|
|
return MessageSegment(
|
|
|
|
|
"markdown",
|
|
|
|
|
{
|
2020-12-03 00:59:32 +08:00
|
|
|
|
"title": title,
|
|
|
|
|
"text": text,
|
|
|
|
|
},
|
2020-12-30 00:36:29 +08:00
|
|
|
|
)
|
2020-12-03 00:59:32 +08:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2020-12-30 18:33:54 +08:00
|
|
|
|
def actionCardSingleBtn(title: str, text: str, singleTitle: str,
|
|
|
|
|
singleURL) -> "MessageSegment":
|
2020-12-31 17:58:09 +08:00
|
|
|
|
"""发送 ``actionCardSingleBtn`` 类型消息"""
|
2020-12-03 00:59:32 +08:00
|
|
|
|
return MessageSegment(
|
|
|
|
|
"actionCard", {
|
2020-12-30 00:36:29 +08:00
|
|
|
|
"title": title,
|
|
|
|
|
"text": text,
|
2020-12-30 18:33:54 +08:00
|
|
|
|
"singleTitle": singleTitle,
|
|
|
|
|
"singleURL": singleURL
|
2020-12-03 00:59:32 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
2020-12-30 00:36:29 +08:00
|
|
|
|
def actionCardMultiBtns(
|
2020-12-03 00:59:32 +08:00
|
|
|
|
title: str,
|
|
|
|
|
text: str,
|
2020-12-30 18:33:54 +08:00
|
|
|
|
btns: list,
|
2020-12-03 00:59:32 +08:00
|
|
|
|
hideAvatar: bool = False,
|
|
|
|
|
btnOrientation: str = '1',
|
|
|
|
|
) -> "MessageSegment":
|
|
|
|
|
"""
|
2020-12-31 17:58:09 +08:00
|
|
|
|
发送 ``actionCardMultiBtn`` 类型消息
|
|
|
|
|
|
2020-12-03 00:59:32 +08:00
|
|
|
|
:参数:
|
|
|
|
|
|
|
|
|
|
* ``btnOrientation``: 0:按钮竖直排列 1:按钮横向排列
|
2021-07-28 16:32:50 +08:00
|
|
|
|
* ``btns``: ``[{ "title": title, "actionURL": actionURL }, ...]``
|
2020-12-03 00:59:32 +08:00
|
|
|
|
"""
|
|
|
|
|
return MessageSegment(
|
|
|
|
|
"actionCard", {
|
2020-12-30 00:36:29 +08:00
|
|
|
|
"title": title,
|
|
|
|
|
"text": text,
|
|
|
|
|
"hideAvatar": "1" if hideAvatar else "0",
|
|
|
|
|
"btnOrientation": btnOrientation,
|
|
|
|
|
"btns": btns
|
2020-12-03 00:59:32 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
2020-12-30 18:33:54 +08:00
|
|
|
|
def feedCard(links: list) -> "MessageSegment":
|
2020-12-03 00:59:32 +08:00
|
|
|
|
"""
|
2020-12-31 17:58:09 +08:00
|
|
|
|
发送 ``feedCard`` 类型消息
|
|
|
|
|
|
2020-12-03 00:59:32 +08:00
|
|
|
|
:参数:
|
|
|
|
|
|
2021-07-28 16:32:50 +08:00
|
|
|
|
* ``links``: ``[{ "title": xxx, "messageURL": xxx, "picURL": xxx }, ...]``
|
2020-12-03 00:59:32 +08:00
|
|
|
|
"""
|
2020-12-30 00:36:29 +08:00
|
|
|
|
return MessageSegment("feedCard", {"links": links})
|
2020-12-03 00:59:32 +08:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2020-12-30 18:33:54 +08:00
|
|
|
|
def raw(data) -> "MessageSegment":
|
|
|
|
|
return MessageSegment('raw', data)
|
|
|
|
|
|
2021-06-18 01:23:13 +08:00
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
2020-12-30 18:33:54 +08:00
|
|
|
|
# 让用户可以直接发送原始的消息格式
|
|
|
|
|
if self.type == "raw":
|
|
|
|
|
return copy(self.data)
|
|
|
|
|
|
|
|
|
|
# 不属于消息内容,只是作为消息段的辅助
|
|
|
|
|
if self.type in ["at", "extension"]:
|
|
|
|
|
return {self.type: copy(self.data)}
|
|
|
|
|
|
|
|
|
|
return {"msgtype": self.type, self.type: copy(self.data)}
|
2020-12-03 00:59:32 +08:00
|
|
|
|
|
|
|
|
|
|
2021-05-10 00:54:15 +08:00
|
|
|
|
class Message(BaseMessage[MessageSegment]):
|
2020-12-03 00:59:32 +08:00
|
|
|
|
"""
|
|
|
|
|
钉钉 协议 Message 适配。
|
|
|
|
|
"""
|
|
|
|
|
|
2021-06-18 01:23:13 +08:00
|
|
|
|
@classmethod
|
|
|
|
|
@overrides(BaseMessage)
|
|
|
|
|
def get_segment_class(cls) -> Type[MessageSegment]:
|
|
|
|
|
return MessageSegment
|
|
|
|
|
|
2020-12-03 00:59:32 +08:00
|
|
|
|
@staticmethod
|
2020-12-30 20:08:22 +08:00
|
|
|
|
@overrides(BaseMessage)
|
2020-12-31 14:00:59 +08:00
|
|
|
|
def _construct(
|
|
|
|
|
msg: Union[str, Mapping,
|
|
|
|
|
Iterable[Mapping]]) -> Iterable[MessageSegment]:
|
|
|
|
|
if isinstance(msg, Mapping):
|
2021-11-12 11:53:42 +08:00
|
|
|
|
msg = cast(Mapping[str, Any], msg)
|
2020-12-03 00:59:32 +08:00
|
|
|
|
yield MessageSegment(msg["type"], msg.get("data") or {})
|
|
|
|
|
elif isinstance(msg, str):
|
2020-12-03 18:47:58 +08:00
|
|
|
|
yield MessageSegment.text(msg)
|
2020-12-31 14:00:59 +08:00
|
|
|
|
elif isinstance(msg, Iterable):
|
|
|
|
|
for seg in msg:
|
|
|
|
|
yield MessageSegment(seg["type"], seg.get("data") or {})
|
2020-12-30 00:36:29 +08:00
|
|
|
|
|
|
|
|
|
def _produce(self) -> dict:
|
|
|
|
|
data = {}
|
2020-12-31 14:00:59 +08:00
|
|
|
|
segment: MessageSegment
|
2020-12-30 00:36:29 +08:00
|
|
|
|
for segment in self:
|
2020-12-30 18:33:54 +08:00
|
|
|
|
# text 可以和 text 合并
|
|
|
|
|
if segment.type == "text" and data.get("msgtype") == 'text':
|
2020-12-30 00:36:29 +08:00
|
|
|
|
data.setdefault("text", {})
|
|
|
|
|
data["text"]["content"] = data["text"].setdefault(
|
|
|
|
|
"content", "") + segment.data["content"]
|
2020-12-30 18:33:54 +08:00
|
|
|
|
else:
|
|
|
|
|
data.update(segment.to_dict())
|
2020-12-30 00:36:29 +08:00
|
|
|
|
return data
|