mirror of
				https://github.com/nonebot/nonebot2.git
				synced 2025-10-30 22:46:40 +00:00 
			
		
		
		
	✨ Update ding adapter event logic
This commit is contained in:
		| @@ -335,22 +335,21 @@ class Message(list, abc.ABC): | ||||
|     """消息数组""" | ||||
|  | ||||
|     def __init__(self, | ||||
|                  message: Union[str, dict, list, T_MessageSegment, | ||||
|                                 T_Message] = None, | ||||
|                  message: Union[T_MessageSegment, T_Message, Any] = None, | ||||
|                  *args, | ||||
|                  **kwargs): | ||||
|         """ | ||||
|         :参数: | ||||
|  | ||||
|           * ``message: Union[str, dict, list, MessageSegment, Message]``: 消息内容 | ||||
|           * ``message: Union[MessageSegment, Message, Any]``: 消息内容 | ||||
|         """ | ||||
|         super().__init__(*args, **kwargs) | ||||
|         if isinstance(message, (str, dict, list)): | ||||
|             self.extend(self._construct(message)) | ||||
|         elif isinstance(message, Message): | ||||
|         if isinstance(message, Message): | ||||
|             self.extend(message) | ||||
|         elif isinstance(message, MessageSegment): | ||||
|             self.append(message) | ||||
|         else: | ||||
|             self.extend(self._construct(message)) | ||||
|  | ||||
|     def __str__(self): | ||||
|         return ''.join((str(seg) for seg in self)) | ||||
| @@ -365,9 +364,7 @@ class Message(list, abc.ABC): | ||||
|  | ||||
|     @staticmethod | ||||
|     @abc.abstractmethod | ||||
|     def _construct( | ||||
|             msg: Union[str, dict, list, | ||||
|                        BaseModel]) -> Iterable[T_MessageSegment]: | ||||
|     def _construct(msg: Union[Any]) -> Iterable[T_MessageSegment]: | ||||
|         raise NotImplementedError | ||||
|  | ||||
|     def __add__(self: T_Message, other: Union[str, T_MessageSegment, | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
| 协议详情请看: `钉钉文档`_ | ||||
|  | ||||
| .. _钉钉文档: | ||||
|     https://ding-doc.dingtalk.com/doc#/serverapi2/krgddi | ||||
|     https://ding-doc.dingtalk.com/document#/org-dev-guide/elzz1p/ | ||||
|  | ||||
| """ | ||||
|  | ||||
|   | ||||
| @@ -11,8 +11,8 @@ from nonebot.adapters import Bot as BaseBot | ||||
| from nonebot.exception import RequestDenied | ||||
|  | ||||
| from .utils import log | ||||
| from .event import Event | ||||
| from .model import MessageModel | ||||
| from .event import Event, MessageEvent, PrivateMessageEvent, GroupMessageEvent | ||||
| from .model import ConversationType | ||||
| from .message import Message, MessageSegment | ||||
| from .exception import NetworkError, ApiNotAvailable, ActionFailed, SessionExpired | ||||
|  | ||||
| @@ -50,7 +50,8 @@ class Bot(BaseBot): | ||||
|  | ||||
|         # 检查连接方式 | ||||
|         if connection_type not in ["http"]: | ||||
|             raise RequestDenied(405, "Unsupported connection type") | ||||
|             raise RequestDenied( | ||||
|                 405, "Unsupported connection type, available type: `http`") | ||||
|  | ||||
|         # 检查 timestamp | ||||
|         if not timestamp: | ||||
| @@ -73,22 +74,30 @@ class Bot(BaseBot): | ||||
|         return body["chatbotUserId"] | ||||
|  | ||||
|     async def handle_message(self, body: dict): | ||||
|         message = MessageModel.parse_obj(body) | ||||
|         if not message: | ||||
|         if not body: | ||||
|             return | ||||
|  | ||||
|         # 判断消息类型,生成不同的 Event | ||||
|         conversation_type = body["conversationType"] | ||||
|         if conversation_type == ConversationType.private: | ||||
|             event = PrivateMessageEvent.parse_obj(body) | ||||
|         else: | ||||
|             event = GroupMessageEvent.parse_obj(body) | ||||
|  | ||||
|         if not event: | ||||
|             return | ||||
|  | ||||
|         try: | ||||
|             event = Event(message) | ||||
|             await handle_event(self, event) | ||||
|         except Exception as e: | ||||
|             logger.opt(colors=True, exception=e).error( | ||||
|                 f"<r><bg #f8bbd0>Failed to handle event. Raw: {message}</bg #f8bbd0></r>" | ||||
|                 f"<r><bg #f8bbd0>Failed to handle event. Raw: {event}</bg #f8bbd0></r>" | ||||
|             ) | ||||
|         return | ||||
|  | ||||
|     async def call_api(self, | ||||
|                        api: str, | ||||
|                        event: Optional[Event] = None, | ||||
|                        event: Optional[MessageEvent] = None, | ||||
|                        **data) -> Any: | ||||
|         """ | ||||
|         :说明: | ||||
| @@ -124,10 +133,10 @@ class Bot(BaseBot): | ||||
|             if event: | ||||
|                 # 确保 sessionWebhook 没有过期 | ||||
|                 if int(datetime.now().timestamp()) > int( | ||||
|                         event.raw_event.sessionWebhookExpiredTime / 1000): | ||||
|                         event.sessionWebhookExpiredTime / 1000): | ||||
|                     raise SessionExpired | ||||
|  | ||||
|                 target = event.raw_event.sessionWebhook | ||||
|                 target = event.sessionWebhook | ||||
|             else: | ||||
|                 target = None | ||||
|  | ||||
|   | ||||
| @@ -1,197 +1,84 @@ | ||||
| from typing import Union, Optional | ||||
| from typing_extensions import Literal | ||||
|  | ||||
| from pydantic import BaseModel, validator, parse_obj_as | ||||
| from pydantic.fields import ModelField | ||||
|  | ||||
| from nonebot.adapters import Event as BaseEvent | ||||
| from nonebot.utils import escape_tag | ||||
|  | ||||
| from .message import Message | ||||
| from .model import MessageModel, ConversationType, TextMessage | ||||
| from .model import MessageModel, PrivateMessageModel, GroupMessageModel, ConversationType, TextMessage | ||||
|  | ||||
|  | ||||
| class Event(BaseEvent): | ||||
|     """ | ||||
|     钉钉 协议 Event 适配。继承属性参考 `BaseEvent <./#class-baseevent>`_ 。 | ||||
|     """ | ||||
|     message: Message = None | ||||
|  | ||||
|     def __init__(self, message: MessageModel): | ||||
|         super().__init__(message) | ||||
|     def __init__(self, **data): | ||||
|         super().__init__(**data) | ||||
|         # 其实目前钉钉机器人只能接收到 text 类型的消息 | ||||
|         self._message = Message(getattr(message, message.msgtype or "text")) | ||||
|         message: Union[TextMessage] = getattr(self, self.msgtype, None) | ||||
|         self.message = parse_obj_as(Message, message) | ||||
|  | ||||
|     @property | ||||
|     def raw_event(self) -> MessageModel: | ||||
|         """原始上报消息""" | ||||
|         return self._raw_event | ||||
|  | ||||
|     @property | ||||
|     def id(self) -> Optional[str]: | ||||
|         """ | ||||
|         - 类型: ``Optional[str]`` | ||||
|         - 说明: 消息 ID | ||||
|         """ | ||||
|         return self.raw_event.msgId | ||||
|  | ||||
|     @property | ||||
|     def name(self) -> str: | ||||
|         """ | ||||
|         - 类型: ``str`` | ||||
|         - 说明: 事件名称,由 `type`.`detail_type` 组合而成 | ||||
|         """ | ||||
|         return self.type + "." + self.detail_type | ||||
|  | ||||
|     @property | ||||
|     def self_id(self) -> str: | ||||
|         """ | ||||
|         - 类型: ``str`` | ||||
|         - 说明: 机器人自身 ID | ||||
|         """ | ||||
|         return str(self.raw_event.chatbotUserId) | ||||
|  | ||||
|     @property | ||||
|     def time(self) -> int: | ||||
|         """ | ||||
|         - 类型: ``int`` | ||||
|         - 说明: 消息的时间戳,单位 s | ||||
|         """ | ||||
|         # 单位 ms -> s | ||||
|         return int(self.raw_event.createAt / 1000) | ||||
|  | ||||
|     @property | ||||
|     def type(self) -> str: | ||||
|     def get_type(self) -> Literal["message"]: | ||||
|         """ | ||||
|         - 类型: ``str`` | ||||
|         - 说明: 事件类型 | ||||
|         """ | ||||
|         return "message" | ||||
|  | ||||
|     @type.setter | ||||
|     def type(self, value) -> None: | ||||
|         pass | ||||
|     def get_event_name(self) -> str: | ||||
|         detail_type = self.conversationType.name | ||||
|         return self.get_type() + "." + detail_type | ||||
|  | ||||
|     @property | ||||
|     def detail_type(self) -> str: | ||||
|     def get_event_description(self) -> str: | ||||
|         return (f'Message[{self.msgtype}] {self.msgId} from {self.senderId} "' + | ||||
|                 "".join( | ||||
|                     map( | ||||
|                         lambda x: escape_tag(str(x)) | ||||
|                         if x.is_text() else f"<le>{escape_tag(str(x))}</le>", | ||||
|                         self.message, | ||||
|                     )) + '"') | ||||
|  | ||||
|     def get_user_id(self) -> str: | ||||
|         return self.senderId | ||||
|  | ||||
|     def get_session_id(self) -> str: | ||||
|         """ | ||||
|         - 类型: ``str`` | ||||
|         - 说明: 事件详细类型 | ||||
|         - 说明: 消息 ID | ||||
|         """ | ||||
|         return self.raw_event.conversationType.name | ||||
|         return self.msgId | ||||
|  | ||||
|     @detail_type.setter | ||||
|     def detail_type(self, value) -> None: | ||||
|         if value == "private": | ||||
|             self.raw_event.conversationType = ConversationType.private | ||||
|         if value == "group": | ||||
|             self.raw_event.conversationType = ConversationType.group | ||||
|  | ||||
|     @property | ||||
|     def sub_type(self) -> None: | ||||
|     def get_message(self) -> "Message": | ||||
|         """ | ||||
|         - 类型: ``None`` | ||||
|         - 说明: 钉钉适配器无事件子类型 | ||||
|         """ | ||||
|         return None | ||||
|  | ||||
|     @sub_type.setter | ||||
|     def sub_type(self, value) -> None: | ||||
|         pass | ||||
|  | ||||
|     @property | ||||
|     def user_id(self) -> Optional[str]: | ||||
|         """ | ||||
|         - 类型: ``Optional[str]`` | ||||
|         - 说明: 发送者 ID | ||||
|         """ | ||||
|         return self.raw_event.senderId | ||||
|  | ||||
|     @user_id.setter | ||||
|     def user_id(self, value) -> None: | ||||
|         self.raw_event.senderId = value | ||||
|  | ||||
|     @property | ||||
|     def group_id(self) -> Optional[str]: | ||||
|         """ | ||||
|         - 类型: ``Optional[str]`` | ||||
|         - 说明: 事件主体群 ID | ||||
|         """ | ||||
|         return self.raw_event.conversationId | ||||
|  | ||||
|     @group_id.setter | ||||
|     def group_id(self, value) -> None: | ||||
|         self.raw_event.conversationId = value | ||||
|  | ||||
|     @property | ||||
|     def to_me(self) -> Optional[bool]: | ||||
|         """ | ||||
|         - 类型: ``Optional[bool]`` | ||||
|         - 说明: 消息是否与机器人相关 | ||||
|         """ | ||||
|         return self.detail_type == "private" or self.raw_event.isInAtList | ||||
|  | ||||
|     @property | ||||
|     def message(self) -> Optional["Message"]: | ||||
|         """ | ||||
|         - 类型: ``Optional[Message]`` | ||||
|         - 类型: ``Message`` | ||||
|         - 说明: 消息内容 | ||||
|         """ | ||||
|         return self._message | ||||
|         return self.message | ||||
|  | ||||
|     @message.setter | ||||
|     def message(self, value) -> None: | ||||
|         self._message = value | ||||
|  | ||||
|     @property | ||||
|     def reply(self) -> None: | ||||
|     def get_plaintext(self) -> str: | ||||
|         """ | ||||
|         - 类型: ``None`` | ||||
|         - 说明: 回复消息详情 | ||||
|         """ | ||||
|         raise ValueError("暂不支持 reply") | ||||
|  | ||||
|     @property | ||||
|     def raw_message(self) -> Optional[Union[TextMessage]]: | ||||
|         """ | ||||
|         - 类型: ``Optional[str]`` | ||||
|         - 说明: 原始消息 | ||||
|         """ | ||||
|         return getattr(self.raw_event, self.raw_event.msgtype) | ||||
|  | ||||
|     @raw_message.setter | ||||
|     def raw_message(self, value) -> None: | ||||
|         setattr(self.raw_event, self.raw_event.msgtype, value) | ||||
|  | ||||
|     @property | ||||
|     def plain_text(self) -> Optional[str]: | ||||
|         """ | ||||
|         - 类型: ``Optional[str]`` | ||||
|         - 类型: ``str`` | ||||
|         - 说明: 纯文本消息内容 | ||||
|         """ | ||||
|         return self.message and self.message.extract_plain_text().strip() | ||||
|         return self.message.extract_plain_text().strip() if self.message else "" | ||||
|  | ||||
|     @property | ||||
|     def sender(self) -> Optional[dict]: | ||||
|         """ | ||||
|         - 类型: ``Optional[dict]`` | ||||
|         - 说明: 消息发送者信息 | ||||
|         """ | ||||
|         result = { | ||||
|             # 加密的发送者ID。 | ||||
|             "senderId": self.raw_event.senderId, | ||||
|             # 发送者昵称。 | ||||
|             "senderNick": self.raw_event.senderNick, | ||||
|             # 企业内部群有的发送者当前群的企业 corpId。 | ||||
|             "senderCorpId": self.raw_event.senderCorpId, | ||||
|             # 企业内部群有的发送者在企业内的 userId。 | ||||
|             "senderStaffId": self.raw_event.senderStaffId, | ||||
|             "role": "admin" if self.raw_event.isAdmin else "member" | ||||
|         } | ||||
|         return result | ||||
|  | ||||
|     @sender.setter | ||||
|     def sender(self, value) -> None: | ||||
| class MessageEvent(MessageModel, Event): | ||||
|     pass | ||||
|  | ||||
|         def set_wrapper(name): | ||||
|             if value.get(name): | ||||
|                 setattr(self.raw_event, name, value.get(name)) | ||||
|  | ||||
|         set_wrapper("senderId") | ||||
|         set_wrapper("senderNick") | ||||
|         set_wrapper("senderCorpId") | ||||
|         set_wrapper("senderStaffId") | ||||
| class PrivateMessageEvent(PrivateMessageModel, Event): | ||||
|  | ||||
|     def is_tome(self) -> bool: | ||||
|         return True | ||||
|  | ||||
|  | ||||
| class GroupMessageEvent(GroupMessageModel, Event): | ||||
|  | ||||
|     def is_tome(self) -> bool: | ||||
|         return self.isInAtList | ||||
|   | ||||
| @@ -37,6 +37,12 @@ class MessageSegment(BaseMessageSegment): | ||||
|                 return MessageSegment.from_segment(self) | ||||
|         return Message(self) + other | ||||
|  | ||||
|     def __radd__(self, other) -> "Message": | ||||
|         return Message(other) + self | ||||
|  | ||||
|     def is_text(self) -> bool: | ||||
|         return self.type == "text" | ||||
|  | ||||
|     def atMobile(self, mobileNumber): | ||||
|         self.data.setdefault("at", {}) | ||||
|         self.data["at"].setdefault("atMobiles", []) | ||||
| @@ -118,6 +124,10 @@ class Message(BaseMessage): | ||||
|     钉钉 协议 Message 适配。 | ||||
|     """ | ||||
|  | ||||
|     @classmethod | ||||
|     def _validate(cls, value): | ||||
|         return cls(value) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _construct( | ||||
|             msg: Union[str, dict, list, | ||||
|   | ||||
| @@ -26,23 +26,31 @@ class ConversationType(str, Enum): | ||||
|  | ||||
|  | ||||
| class MessageModel(BaseModel): | ||||
|     msgtype: str = None | ||||
|     text: Optional[TextMessage] = None | ||||
|     msgId: str | ||||
|     chatbotUserId: str = None | ||||
|     conversationId: str = None | ||||
|     conversationType: ConversationType = None | ||||
|     # ms | ||||
|     createAt: int = None | ||||
|     conversationType: ConversationType = None | ||||
|     conversationId: str = None | ||||
|     conversationTitle: str = None | ||||
|     isAdmin: bool = None | ||||
|     msgId: str = None | ||||
|     msgtype: str = None | ||||
|     senderCorpId: str = None | ||||
|     senderId: str = None | ||||
|     senderNick: str = None | ||||
|     senderCorpId: str = None | ||||
|     senderStaffId: str = None | ||||
|     chatbotUserId: str = None | ||||
|     chatbotCorpId: str = None | ||||
|     atUsers: List[AtUsersItem] = None | ||||
|     sessionWebhook: str = None | ||||
|     # ms | ||||
|     sessionWebhookExpiredTime: int = None | ||||
|     isAdmin: bool = None | ||||
|     text: Optional[TextMessage] = None | ||||
|  | ||||
|  | ||||
| class PrivateMessageModel(MessageModel): | ||||
|     chatbotCorpId: str = None | ||||
|     conversationType: ConversationType = ConversationType.private | ||||
|     senderStaffId: str = None | ||||
|  | ||||
|  | ||||
| class GroupMessageModel(MessageModel): | ||||
|     atUsers: List[AtUsersItem] = None | ||||
|     conversationType: ConversationType = ConversationType.group | ||||
|     conversationTitle: str = None | ||||
|     isInAtList: bool = None | ||||
|   | ||||
		Reference in New Issue
	
	Block a user