mirror of
				https://github.com/nonebot/nonebot2.git
				synced 2025-10-31 06:56:39 +00:00 
			
		
		
		
	⚡ improve ding adapter
add tests/test_ding.py add some log
This commit is contained in:
		| @@ -19,6 +19,8 @@ from .event import Event, MessageEvent, PrivateMessageEvent, GroupMessageEvent, | ||||
| if TYPE_CHECKING: | ||||
|     from nonebot.drivers import Driver | ||||
|  | ||||
| SEND_BY_SESSION_WEBHOOK = "send_by_sessionWebhook" | ||||
|  | ||||
|  | ||||
| class Bot(BaseBot): | ||||
|     """ | ||||
| @@ -89,7 +91,7 @@ class Bot(BaseBot): | ||||
|             else: | ||||
|                 raise ValueError("Unsupported conversation type") | ||||
|         except Exception as e: | ||||
|             log("Error", "Event Parser Error", e) | ||||
|             log("ERROR", "Event Parser Error", e) | ||||
|             return | ||||
|  | ||||
|         try: | ||||
| @@ -135,7 +137,7 @@ class Bot(BaseBot): | ||||
|  | ||||
|         log("DEBUG", f"Calling API <y>{api}</y>") | ||||
|  | ||||
|         if api == "send_message": | ||||
|         if api == SEND_BY_SESSION_WEBHOOK: | ||||
|             if event: | ||||
|                 # 确保 sessionWebhook 没有过期 | ||||
|                 if int(datetime.now().timestamp()) > int( | ||||
| @@ -208,10 +210,8 @@ class Bot(BaseBot): | ||||
|         params.update(kwargs) | ||||
|  | ||||
|         if at_sender and event.conversationType != ConversationType.private: | ||||
|             params[ | ||||
|                 "message"] = f"@{event.senderId} " + msg + MessageSegment.atMobiles( | ||||
|                     event.senderId) | ||||
|             params["message"] = f"@{event.senderNick} " + msg | ||||
|         else: | ||||
|             params["message"] = msg | ||||
|  | ||||
|         return await self.call_api("send_message", **params) | ||||
|         return await self.call_api(SEND_BY_SESSION_WEBHOOK, **params) | ||||
|   | ||||
| @@ -2,9 +2,8 @@ from enum import Enum | ||||
| from typing import List, Optional | ||||
| from typing_extensions import Literal | ||||
|  | ||||
| from pydantic import BaseModel | ||||
| from pydantic import BaseModel, root_validator | ||||
|  | ||||
| from nonebot.utils import escape_tag | ||||
| from nonebot.typing import overrides | ||||
| from nonebot.adapters import Event as BaseEvent | ||||
|  | ||||
| @@ -27,27 +26,27 @@ class Event(BaseEvent): | ||||
|  | ||||
|     @overrides(BaseEvent) | ||||
|     def get_event_name(self) -> str: | ||||
|         raise ValueError("Event has no type!") | ||||
|         raise ValueError("Event has no name!") | ||||
|  | ||||
|     @overrides(BaseEvent) | ||||
|     def get_event_description(self) -> str: | ||||
|         raise ValueError("Event has no type!") | ||||
|         raise ValueError("Event has no description!") | ||||
|  | ||||
|     @overrides(BaseEvent) | ||||
|     def get_message(self) -> "Message": | ||||
|         raise ValueError("Event has no type!") | ||||
|         raise ValueError("Event has no message!") | ||||
|  | ||||
|     @overrides(BaseEvent) | ||||
|     def get_plaintext(self) -> str: | ||||
|         raise ValueError("Event has no type!") | ||||
|         raise ValueError("Event has no plaintext!") | ||||
|  | ||||
|     @overrides(BaseEvent) | ||||
|     def get_user_id(self) -> str: | ||||
|         raise ValueError("Event has no type!") | ||||
|         raise ValueError("Event has no user_id!") | ||||
|  | ||||
|     @overrides(BaseEvent) | ||||
|     def get_session_id(self) -> str: | ||||
|         raise ValueError("Event has no type!") | ||||
|         raise ValueError("Event has no session_id!") | ||||
|  | ||||
|     @overrides(BaseEvent) | ||||
|     def is_tome(self) -> bool: | ||||
| @@ -82,6 +81,21 @@ class MessageEvent(Event): | ||||
|     sessionWebhookExpiredTime: int | ||||
|     isAdmin: bool | ||||
|  | ||||
|     message: Message | ||||
|  | ||||
|     @root_validator(pre=True) | ||||
|     def gen_message(cls, values: dict): | ||||
|         assert "msgtype" in values, "msgtype must be specified" | ||||
|         # 其实目前钉钉机器人只能接收到 text 类型的消息 | ||||
|         assert values[ | ||||
|             "msgtype"] in values, f"{values['msgtype']} must be specified" | ||||
|         content = values[values['msgtype']]['content'] | ||||
|         # 如果是被 @,第一个字符将会为空格,移除特殊情况 | ||||
|         if content[0] == ' ': | ||||
|             content = content[1:] | ||||
|         values["message"] = content | ||||
|         return values | ||||
|  | ||||
|     @overrides(Event) | ||||
|     def get_type(self) -> Literal["message", "notice", "request", "meta_event"]: | ||||
|         return "message" | ||||
| @@ -94,6 +108,10 @@ class MessageEvent(Event): | ||||
|     def get_event_description(self) -> str: | ||||
|         return f'Message[{self.msgtype}] {self.msgId} from {self.senderId} "{self.text.content}"' | ||||
|  | ||||
|     @overrides(BaseEvent) | ||||
|     def get_message(self) -> Message: | ||||
|         return self.message | ||||
|  | ||||
|     @overrides(BaseEvent) | ||||
|     def get_plaintext(self) -> str: | ||||
|         return self.text.content | ||||
|   | ||||
| @@ -37,7 +37,7 @@ class ActionFailed(BaseActionFailed, DingAdapterException): | ||||
|         self.errmsg = errmsg | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return f"<ApiError errcode={self.errcode} errmsg={self.errmsg}>" | ||||
|         return f"<ApiError errcode={self.errcode} errmsg=\"{self.errmsg}\">" | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.__repr__() | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| from typing import Any, Dict, Union, Iterable | ||||
|  | ||||
| from nonebot.adapters import Message as BaseMessage, MessageSegment as BaseMessageSegment | ||||
|  | ||||
| from copy import copy | ||||
|  | ||||
|  | ||||
| class MessageSegment(BaseMessageSegment): | ||||
|     """ | ||||
| @@ -39,6 +40,16 @@ class MessageSegment(BaseMessageSegment): | ||||
|     def text(text: str) -> "MessageSegment": | ||||
|         return MessageSegment("text", {"content": text}) | ||||
|  | ||||
|     @staticmethod | ||||
|     def image(picURL: str) -> "MessageSegment": | ||||
|         return MessageSegment("image", {"picURL": picURL}) | ||||
|  | ||||
|     @staticmethod | ||||
|     def extension(dict_: dict) -> "MessageSegment": | ||||
|         """"标记 text 文本的 extension 属性,需要与 text 消息段相加。 | ||||
|         """ | ||||
|         return MessageSegment("extension", dict_) | ||||
|  | ||||
|     @staticmethod | ||||
|     def markdown(title: str, text: str) -> "MessageSegment": | ||||
|         return MessageSegment( | ||||
| @@ -50,21 +61,21 @@ class MessageSegment(BaseMessageSegment): | ||||
|         ) | ||||
|  | ||||
|     @staticmethod | ||||
|     def actionCardSingleBtn(title: str, text: str, btnTitle: str, | ||||
|                             btnUrl) -> "MessageSegment": | ||||
|     def actionCardSingleBtn(title: str, text: str, singleTitle: str, | ||||
|                             singleURL) -> "MessageSegment": | ||||
|         return MessageSegment( | ||||
|             "actionCard", { | ||||
|                 "title": title, | ||||
|                 "text": text, | ||||
|                 "singleTitle": btnTitle, | ||||
|                 "singleURL": btnUrl | ||||
|                 "singleTitle": singleTitle, | ||||
|                 "singleURL": singleURL | ||||
|             }) | ||||
|  | ||||
|     @staticmethod | ||||
|     def actionCardMultiBtns( | ||||
|         title: str, | ||||
|         text: str, | ||||
|         btns: list = [], | ||||
|         btns: list, | ||||
|         hideAvatar: bool = False, | ||||
|         btnOrientation: str = '1', | ||||
|     ) -> "MessageSegment": | ||||
| @@ -85,7 +96,7 @@ class MessageSegment(BaseMessageSegment): | ||||
|             }) | ||||
|  | ||||
|     @staticmethod | ||||
|     def feedCard(links: list = []) -> "MessageSegment": | ||||
|     def feedCard(links: list) -> "MessageSegment": | ||||
|         """ | ||||
|         :参数: | ||||
|  | ||||
| @@ -94,9 +105,19 @@ class MessageSegment(BaseMessageSegment): | ||||
|         return MessageSegment("feedCard", {"links": links}) | ||||
|  | ||||
|     @staticmethod | ||||
|     def empty() -> "MessageSegment": | ||||
|         """不想回复消息到群里""" | ||||
|         return MessageSegment("empty", {}) | ||||
|     def raw(data) -> "MessageSegment": | ||||
|         return MessageSegment('raw', data) | ||||
|  | ||||
|     def to_dict(self) -> dict: | ||||
|         # 让用户可以直接发送原始的消息格式 | ||||
|         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)} | ||||
|  | ||||
|  | ||||
| class Message(BaseMessage): | ||||
| @@ -104,10 +125,6 @@ class Message(BaseMessage): | ||||
|     钉钉 协议 Message 适配。 | ||||
|     """ | ||||
|  | ||||
|     @classmethod | ||||
|     def _validate(cls, value): | ||||
|         return cls(value) | ||||
|  | ||||
|     @staticmethod | ||||
|     def _construct(msg: Union[str, dict, list]) -> Iterable[MessageSegment]: | ||||
|         if isinstance(msg, dict): | ||||
| @@ -121,23 +138,11 @@ class Message(BaseMessage): | ||||
|     def _produce(self) -> dict: | ||||
|         data = {} | ||||
|         for segment in self: | ||||
|             if segment.type == "text": | ||||
|                 data["msgtype"] = "text" | ||||
|             # text 可以和 text 合并 | ||||
|             if segment.type == "text" and data.get("msgtype") == 'text': | ||||
|                 data.setdefault("text", {}) | ||||
|                 data["text"]["content"] = data["text"].setdefault( | ||||
|                     "content", "") + segment.data["content"] | ||||
|             elif segment.type == "markdown": | ||||
|                 data["msgtype"] = "markdown" | ||||
|                 data.setdefault("markdown", {}) | ||||
|                 data["markdown"]["text"] = data["markdown"].setdefault( | ||||
|                     "content", "") + segment.data["content"] | ||||
|             elif segment.type == "empty": | ||||
|                 data["msgtype"] = "empty" | ||||
|             elif segment.type == "at" and "atMobiles" in segment.data: | ||||
|                 data.setdefault("at", {}) | ||||
|                 data["at"]["atMobiles"] = data["at"].setdefault( | ||||
|                     "atMobiles", []) + segment.data["atMobiles"] | ||||
|             elif segment.data: | ||||
|                 data.setdefault(segment.type, {}) | ||||
|                 data[segment.type].update(segment.data) | ||||
|             else: | ||||
|                 data.update(segment.to_dict()) | ||||
|         return data | ||||
|   | ||||
| @@ -113,7 +113,7 @@ class Matcher(metaclass=MatcherMeta): | ||||
|         self.state = self._default_state.copy() | ||||
|  | ||||
|     def __repr__(self) -> str: | ||||
|         return (f"<Matcher from {self.module or 'unknow'}, type={self.type}, " | ||||
|         return (f"<Matcher from {self.module or 'unknown'}, type={self.type}, " | ||||
|                 f"priority={self.priority}, temp={self.temp}>") | ||||
|  | ||||
|     def __str__(self) -> str: | ||||
| @@ -460,13 +460,23 @@ class Matcher(metaclass=MatcherMeta): | ||||
|         if not hasattr(handler, "__params__"): | ||||
|             self.process_handler(handler) | ||||
|         params = getattr(handler, "__params__") | ||||
|  | ||||
|         BotType = ((params["bot"] is not inspect.Parameter.empty) and | ||||
|                    inspect.isclass(params["bot"]) and params["bot"]) | ||||
|         if BotType and not isinstance(bot, BotType): | ||||
|             logger.info( | ||||
|                 f"Matcher {self} bot type {type(bot)} not match annotation {BotType}, ignored" | ||||
|             ) | ||||
|             return | ||||
|  | ||||
|         EventType = ((params["event"] is not inspect.Parameter.empty) and | ||||
|                      inspect.isclass(params["event"]) and params["event"]) | ||||
|         if (BotType and not isinstance(bot, BotType)) or ( | ||||
|                 EventType and not isinstance(event, EventType)): | ||||
|         if EventType and not isinstance(event, EventType): | ||||
|             logger.info( | ||||
|                 f"Matcher {self} event type {type(event)} not match annotation {EventType}, ignored" | ||||
|             ) | ||||
|             return | ||||
|  | ||||
|         args = {"bot": bot, "event": event, "state": state, "matcher": self} | ||||
|         await handler( | ||||
|             **{k: v for k, v in args.items() if params[k] is not None}) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user