mirror of
				https://github.com/nonebot/nonebot2.git
				synced 2025-10-31 15:06:42 +00:00 
			
		
		
		
	🎨 format code using black and isort
This commit is contained in:
		| @@ -16,10 +16,18 @@ from nonebot.drivers import Driver, HTTPRequest, HTTPResponse, HTTPConnection | ||||
| from .config import Config as DingConfig | ||||
| from .utils import log, calc_hmac_base64 | ||||
| from .message import Message, MessageSegment | ||||
| from .exception import (ActionFailed, NetworkError, SessionExpired, | ||||
|                         ApiNotAvailable) | ||||
| from .event import (MessageEvent, ConversationType, GroupMessageEvent, | ||||
|                     PrivateMessageEvent) | ||||
| from .exception import ( | ||||
|     ActionFailed, | ||||
|     NetworkError, | ||||
|     SessionExpired, | ||||
|     ApiNotAvailable, | ||||
| ) | ||||
| from .event import ( | ||||
|     MessageEvent, | ||||
|     ConversationType, | ||||
|     GroupMessageEvent, | ||||
|     PrivateMessageEvent, | ||||
| ) | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from nonebot.config import Config | ||||
| @@ -31,6 +39,7 @@ class Bot(BaseBot): | ||||
|     """ | ||||
|     钉钉 协议 Bot 适配。继承属性参考 `BaseBot <./#class-basebot>`_ 。 | ||||
|     """ | ||||
|  | ||||
|     ding_config: DingConfig | ||||
|  | ||||
|     @property | ||||
| @@ -48,8 +57,8 @@ class Bot(BaseBot): | ||||
|     @classmethod | ||||
|     @overrides(BaseBot) | ||||
|     async def check_permission( | ||||
|             cls, driver: Driver, | ||||
|             request: HTTPConnection) -> Tuple[Optional[str], HTTPResponse]: | ||||
|         cls, driver: Driver, request: HTTPConnection | ||||
|     ) -> Tuple[Optional[str], HTTPResponse]: | ||||
|         """ | ||||
|         :说明: | ||||
|  | ||||
| @@ -61,7 +70,8 @@ class Bot(BaseBot): | ||||
|         # 检查连接方式 | ||||
|         if not isinstance(request, HTTPRequest): | ||||
|             return None, HTTPResponse( | ||||
|                 405, b"Unsupported connection type, available type: `http`") | ||||
|                 405, b"Unsupported connection type, available type: `http`" | ||||
|             ) | ||||
|  | ||||
|         # 检查 timestamp | ||||
|         if not timestamp: | ||||
| @@ -74,13 +84,15 @@ class Bot(BaseBot): | ||||
|                 log("WARNING", "Missing Signature Header") | ||||
|                 return None, HTTPResponse(400, b"Missing `sign` Header") | ||||
|             sign_base64 = calc_hmac_base64(str(timestamp), secret) | ||||
|             if sign != sign_base64.decode('utf-8'): | ||||
|             if sign != sign_base64.decode("utf-8"): | ||||
|                 log("WARNING", "Signature Header is invalid") | ||||
|                 return None, HTTPResponse(403, b"Signature is invalid") | ||||
|         else: | ||||
|             log("WARNING", "Ding signature check ignored!") | ||||
|         return (json.loads(request.body.decode())["chatbotUserId"], | ||||
|                 HTTPResponse(204, b'')) | ||||
|         return ( | ||||
|             json.loads(request.body.decode())["chatbotUserId"], | ||||
|             HTTPResponse(204, b""), | ||||
|         ) | ||||
|  | ||||
|     @overrides(BaseBot) | ||||
|     async def handle_message(self, message: bytes): | ||||
| @@ -111,10 +123,9 @@ class Bot(BaseBot): | ||||
|         return | ||||
|  | ||||
|     @overrides(BaseBot) | ||||
|     async def _call_api(self, | ||||
|                         api: str, | ||||
|                         event: Optional[MessageEvent] = None, | ||||
|                         **data) -> Any: | ||||
|     async def _call_api( | ||||
|         self, api: str, event: Optional[MessageEvent] = None, **data | ||||
|     ) -> Any: | ||||
|         if not isinstance(self.request, HTTPRequest): | ||||
|             log("ERROR", "Only support http connection.") | ||||
|             return | ||||
| @@ -138,7 +149,8 @@ class Bot(BaseBot): | ||||
|             if event: | ||||
|                 # 确保 sessionWebhook 没有过期 | ||||
|                 if int(datetime.now().timestamp()) > int( | ||||
|                         event.sessionWebhookExpiredTime / 1000): | ||||
|                     event.sessionWebhookExpiredTime / 1000 | ||||
|                 ): | ||||
|                     raise SessionExpired | ||||
|  | ||||
|                 webhook = event.sessionWebhook | ||||
| @@ -150,32 +162,37 @@ class Bot(BaseBot): | ||||
|         if not message: | ||||
|             raise ValueError("Message not found") | ||||
|         try: | ||||
|             async with httpx.AsyncClient(headers=headers, | ||||
|                                          follow_redirects=True) as client: | ||||
|                 response = await client.post(webhook, | ||||
|                                              params=params, | ||||
|                                              json=message._produce(), | ||||
|                                              timeout=self.config.api_timeout) | ||||
|             async with httpx.AsyncClient( | ||||
|                 headers=headers, follow_redirects=True | ||||
|             ) as client: | ||||
|                 response = await client.post( | ||||
|                     webhook, | ||||
|                     params=params, | ||||
|                     json=message._produce(), | ||||
|                     timeout=self.config.api_timeout, | ||||
|                 ) | ||||
|  | ||||
|             if 200 <= response.status_code < 300: | ||||
|                 result = response.json() | ||||
|                 if isinstance(result, dict): | ||||
|                     if result.get("errcode") != 0: | ||||
|                         raise ActionFailed(errcode=result.get("errcode"), | ||||
|                                            errmsg=result.get("errmsg")) | ||||
|                         raise ActionFailed( | ||||
|                             errcode=result.get("errcode"), errmsg=result.get("errmsg") | ||||
|                         ) | ||||
|                     return result | ||||
|             raise NetworkError(f"HTTP request received unexpected " | ||||
|                                f"status code: {response.status_code}") | ||||
|             raise NetworkError( | ||||
|                 f"HTTP request received unexpected " | ||||
|                 f"status code: {response.status_code}" | ||||
|             ) | ||||
|         except httpx.InvalidURL: | ||||
|             raise NetworkError("API root url invalid") | ||||
|         except httpx.HTTPError: | ||||
|             raise NetworkError("HTTP request failed") | ||||
|  | ||||
|     @overrides(BaseBot) | ||||
|     async def call_api(self, | ||||
|                        api: str, | ||||
|                        event: Optional[MessageEvent] = None, | ||||
|                        **data) -> Any: | ||||
|     async def call_api( | ||||
|         self, api: str, event: Optional[MessageEvent] = None, **data | ||||
|     ) -> Any: | ||||
|         """ | ||||
|         :说明: | ||||
|  | ||||
| @@ -199,13 +216,15 @@ class Bot(BaseBot): | ||||
|         return await super().call_api(api, event=event, **data) | ||||
|  | ||||
|     @overrides(BaseBot) | ||||
|     async def send(self, | ||||
|                    event: MessageEvent, | ||||
|                    message: Union[str, "Message", "MessageSegment"], | ||||
|                    at_sender: bool = False, | ||||
|                    webhook: Optional[str] = None, | ||||
|                    secret: Optional[str] = None, | ||||
|                    **kwargs) -> Any: | ||||
|     async def send( | ||||
|         self, | ||||
|         event: MessageEvent, | ||||
|         message: Union[str, "Message", "MessageSegment"], | ||||
|         at_sender: bool = False, | ||||
|         webhook: Optional[str] = None, | ||||
|         secret: Optional[str] = None, | ||||
|         **kwargs, | ||||
|     ) -> Any: | ||||
|         """ | ||||
|         :说明: | ||||
|  | ||||
| @@ -241,9 +260,11 @@ class Bot(BaseBot): | ||||
|         params.update(kwargs) | ||||
|  | ||||
|         if at_sender and event.conversationType != ConversationType.private: | ||||
|             params[ | ||||
|                 "message"] = f"@{event.senderId} " + msg + MessageSegment.atDingtalkIds( | ||||
|                     event.senderId) | ||||
|             params["message"] = ( | ||||
|                 f"@{event.senderId} " | ||||
|                 + msg | ||||
|                 + MessageSegment.atDingtalkIds(event.senderId) | ||||
|             ) | ||||
|         else: | ||||
|             params["message"] = msg | ||||
|  | ||||
|   | ||||
| @@ -12,6 +12,7 @@ class Config(BaseModel): | ||||
|       - ``access_token`` / ``ding_access_token``: 钉钉令牌 | ||||
|       - ``secret`` / ``ding_secret``: 钉钉 HTTP 上报数据签名口令 | ||||
|     """ | ||||
|  | ||||
|     secret: Optional[str] = Field(default=None, alias="ding_secret") | ||||
|     access_token: Optional[str] = Field(default=None, alias="ding_access_token") | ||||
|  | ||||
|   | ||||
| @@ -69,6 +69,7 @@ class ConversationType(str, Enum): | ||||
|  | ||||
| class MessageEvent(Event): | ||||
|     """消息事件""" | ||||
|  | ||||
|     msgtype: str | ||||
|     text: TextMessage | ||||
|     msgId: str | ||||
| @@ -88,11 +89,10 @@ class MessageEvent(Event): | ||||
|     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'] | ||||
|         assert values["msgtype"] in values, f"{values['msgtype']} must be specified" | ||||
|         content = values[values["msgtype"]]["content"] | ||||
|         # 如果是被 @,第一个字符将会为空格,移除特殊情况 | ||||
|         if content[0] == ' ': | ||||
|         if content[0] == " ": | ||||
|             content = content[1:] | ||||
|         values["message"] = content | ||||
|         return values | ||||
| @@ -128,6 +128,7 @@ class MessageEvent(Event): | ||||
|  | ||||
| class PrivateMessageEvent(MessageEvent): | ||||
|     """私聊消息事件""" | ||||
|  | ||||
|     chatbotCorpId: str | ||||
|     senderStaffId: Optional[str] | ||||
|     conversationType: ConversationType = ConversationType.private | ||||
| @@ -135,6 +136,7 @@ class PrivateMessageEvent(MessageEvent): | ||||
|  | ||||
| class GroupMessageEvent(MessageEvent): | ||||
|     """群消息事件""" | ||||
|  | ||||
|     atUsers: List[AtUsersItem] | ||||
|     conversationType: ConversationType = ConversationType.group | ||||
|     conversationTitle: str | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
| from typing import Optional | ||||
|  | ||||
| from nonebot.exception import (AdapterException, ActionFailed as | ||||
|                                BaseActionFailed, ApiNotAvailable as | ||||
|                                BaseApiNotAvailable, NetworkError as | ||||
|                                BaseNetworkError) | ||||
| from nonebot.exception import AdapterException | ||||
| from nonebot.exception import ActionFailed as BaseActionFailed | ||||
| from nonebot.exception import NetworkError as BaseNetworkError | ||||
| from nonebot.exception import ApiNotAvailable as BaseApiNotAvailable | ||||
|  | ||||
|  | ||||
| class DingAdapterException(AdapterException): | ||||
| @@ -29,15 +29,13 @@ class ActionFailed(BaseActionFailed, DingAdapterException): | ||||
|       * ``errmsg: Optional[str]``: 错误信息 | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, | ||||
|                  errcode: Optional[int] = None, | ||||
|                  errmsg: Optional[str] = None): | ||||
|     def __init__(self, errcode: Optional[int] = None, errmsg: Optional[str] = None): | ||||
|         super().__init__() | ||||
|         self.errcode = errcode | ||||
|         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__() | ||||
|   | ||||
| @@ -77,10 +77,9 @@ class MessageSegment(BaseMessageSegment["Message"]): | ||||
|     def code(code_language: str, code: str) -> "Message": | ||||
|         """发送 code 消息段""" | ||||
|         message = MessageSegment.text(code) | ||||
|         message += MessageSegment.extension({ | ||||
|             "text_type": "code_snippet", | ||||
|             "code_language": code_language | ||||
|         }) | ||||
|         message += MessageSegment.extension( | ||||
|             {"text_type": "code_snippet", "code_language": code_language} | ||||
|         ) | ||||
|         return message | ||||
|  | ||||
|     @staticmethod | ||||
| @@ -95,16 +94,19 @@ class MessageSegment(BaseMessageSegment["Message"]): | ||||
|         ) | ||||
|  | ||||
|     @staticmethod | ||||
|     def actionCardSingleBtn(title: str, text: str, singleTitle: str, | ||||
|                             singleURL) -> "MessageSegment": | ||||
|     def actionCardSingleBtn( | ||||
|         title: str, text: str, singleTitle: str, singleURL | ||||
|     ) -> "MessageSegment": | ||||
|         """发送 ``actionCardSingleBtn`` 类型消息""" | ||||
|         return MessageSegment( | ||||
|             "actionCard", { | ||||
|             "actionCard", | ||||
|             { | ||||
|                 "title": title, | ||||
|                 "text": text, | ||||
|                 "singleTitle": singleTitle, | ||||
|                 "singleURL": singleURL | ||||
|             }) | ||||
|                 "singleURL": singleURL, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     @staticmethod | ||||
|     def actionCardMultiBtns( | ||||
| @@ -112,7 +114,7 @@ class MessageSegment(BaseMessageSegment["Message"]): | ||||
|         text: str, | ||||
|         btns: list, | ||||
|         hideAvatar: bool = False, | ||||
|         btnOrientation: str = '1', | ||||
|         btnOrientation: str = "1", | ||||
|     ) -> "MessageSegment": | ||||
|         """ | ||||
|         发送 ``actionCardMultiBtn`` 类型消息 | ||||
| @@ -123,13 +125,15 @@ class MessageSegment(BaseMessageSegment["Message"]): | ||||
|             * ``btns``: ``[{ "title": title, "actionURL": actionURL }, ...]`` | ||||
|         """ | ||||
|         return MessageSegment( | ||||
|             "actionCard", { | ||||
|             "actionCard", | ||||
|             { | ||||
|                 "title": title, | ||||
|                 "text": text, | ||||
|                 "hideAvatar": "1" if hideAvatar else "0", | ||||
|                 "btnOrientation": btnOrientation, | ||||
|                 "btns": btns | ||||
|             }) | ||||
|                 "btns": btns, | ||||
|             }, | ||||
|         ) | ||||
|  | ||||
|     @staticmethod | ||||
|     def feedCard(links: list) -> "MessageSegment": | ||||
| @@ -144,7 +148,7 @@ class MessageSegment(BaseMessageSegment["Message"]): | ||||
|  | ||||
|     @staticmethod | ||||
|     def raw(data) -> "MessageSegment": | ||||
|         return MessageSegment('raw', data) | ||||
|         return MessageSegment("raw", data) | ||||
|  | ||||
|     def to_dict(self) -> Dict[str, Any]: | ||||
|         # 让用户可以直接发送原始的消息格式 | ||||
| @@ -171,8 +175,8 @@ class Message(BaseMessage[MessageSegment]): | ||||
|     @staticmethod | ||||
|     @overrides(BaseMessage) | ||||
|     def _construct( | ||||
|         msg: Union[str, Mapping, | ||||
|                    Iterable[Mapping]]) -> Iterable[MessageSegment]: | ||||
|         msg: Union[str, Mapping, Iterable[Mapping]] | ||||
|     ) -> Iterable[MessageSegment]: | ||||
|         if isinstance(msg, Mapping): | ||||
|             msg = cast(Mapping[str, Any], msg) | ||||
|             yield MessageSegment(msg["type"], msg.get("data") or {}) | ||||
| @@ -187,10 +191,11 @@ class Message(BaseMessage[MessageSegment]): | ||||
|         segment: MessageSegment | ||||
|         for segment in self: | ||||
|             # text 可以和 text 合并 | ||||
|             if segment.type == "text" and data.get("msgtype") == 'text': | ||||
|             if segment.type == "text" and data.get("msgtype") == "text": | ||||
|                 data.setdefault("text", {}) | ||||
|                 data["text"]["content"] = data["text"].setdefault( | ||||
|                     "content", "") + segment.data["content"] | ||||
|                 data["text"]["content"] = ( | ||||
|                     data["text"].setdefault("content", "") + segment.data["content"] | ||||
|                 ) | ||||
|             else: | ||||
|                 data.update(segment.to_dict()) | ||||
|         return data | ||||
|   | ||||
| @@ -8,10 +8,10 @@ log = logger_wrapper("DING") | ||||
|  | ||||
|  | ||||
| def calc_hmac_base64(timestamp: str, secret: str): | ||||
|     secret_enc = secret.encode('utf-8') | ||||
|     string_to_sign = '{}\n{}'.format(timestamp, secret) | ||||
|     string_to_sign_enc = string_to_sign.encode('utf-8') | ||||
|     hmac_code = hmac.new(secret_enc, | ||||
|                          string_to_sign_enc, | ||||
|                          digestmod=hashlib.sha256).digest() | ||||
|     secret_enc = secret.encode("utf-8") | ||||
|     string_to_sign = "{}\n{}".format(timestamp, secret) | ||||
|     string_to_sign_enc = string_to_sign.encode("utf-8") | ||||
|     hmac_code = hmac.new( | ||||
|         secret_enc, string_to_sign_enc, digestmod=hashlib.sha256 | ||||
|     ).digest() | ||||
|     return base64.b64encode(hmac_code) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user