diff --git a/nonebot/adapters/ding/bot.py b/nonebot/adapters/ding/bot.py
index eddccd33..a736dc09 100644
--- a/nonebot/adapters/ding/bot.py
+++ b/nonebot/adapters/ding/bot.py
@@ -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 {api}")
- 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)
diff --git a/nonebot/adapters/ding/event.py b/nonebot/adapters/ding/event.py
index d5c670e5..a049a079 100644
--- a/nonebot/adapters/ding/event.py
+++ b/nonebot/adapters/ding/event.py
@@ -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
diff --git a/nonebot/adapters/ding/exception.py b/nonebot/adapters/ding/exception.py
index 63721efc..df416932 100644
--- a/nonebot/adapters/ding/exception.py
+++ b/nonebot/adapters/ding/exception.py
@@ -37,7 +37,7 @@ class ActionFailed(BaseActionFailed, DingAdapterException):
self.errmsg = errmsg
def __repr__(self):
- return f""
+ return f""
def __str__(self):
return self.__repr__()
diff --git a/nonebot/adapters/ding/message.py b/nonebot/adapters/ding/message.py
index db3a0083..ad4aa198 100644
--- a/nonebot/adapters/ding/message.py
+++ b/nonebot/adapters/ding/message.py
@@ -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
diff --git a/nonebot/matcher.py b/nonebot/matcher.py
index 3b9b54c2..1045973b 100644
--- a/nonebot/matcher.py
+++ b/nonebot/matcher.py
@@ -113,7 +113,7 @@ class Matcher(metaclass=MatcherMeta):
self.state = self._default_state.copy()
def __repr__(self) -> str:
- return (f"")
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})
diff --git a/tests/test_plugins/test_ding.py b/tests/test_plugins/test_ding.py
new file mode 100644
index 00000000..fca234eb
--- /dev/null
+++ b/tests/test_plugins/test_ding.py
@@ -0,0 +1,160 @@
+from nonebot.rule import to_me
+from nonebot.plugin import on_command
+from nonebot.adapters.ding import Bot as DingBot, MessageSegment, MessageEvent
+
+markdown = on_command("markdown", to_me())
+
+
+@markdown.handle()
+async def test_handler(bot: DingBot):
+ message = MessageSegment.markdown(
+ "Hello, This is NoneBot",
+ "#### NoneBot \n> Nonebot 是一款高性能的 Python 机器人框架\n> \n> [GitHub 仓库地址](https://github.com/nonebot/nonebot2) \n"
+ )
+ await markdown.finish(message)
+
+
+actionCardSingleBtn = on_command("actionCardSingleBtn", to_me())
+
+
+@actionCardSingleBtn.handle()
+async def test_handler(bot: DingBot):
+ message = MessageSegment.actionCardSingleBtn(
+ title="打造一间咖啡厅",
+ text=
+ " \n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
+ singleTitle="阅读全文",
+ singleURL="https://www.dingtalk.com/")
+ await actionCardSingleBtn.finish(message)
+
+
+actionCard = on_command("actionCard", to_me())
+
+
+@actionCard.handle()
+async def test_handler(bot: DingBot):
+ message = MessageSegment.raw({
+ "msgtype": "actionCard",
+ "actionCard": {
+ "title":
+ "乔布斯 20 年前想打造一间苹果咖啡厅,而它正是 Apple Store 的前身",
+ "text":
+ " \n\n #### 乔布斯 20 年前想打造的苹果咖啡厅 \n\n Apple Store 的设计正从原来满满的科技感走向生活化,而其生活化的走向其实可以追溯到 20 年前苹果一个建立咖啡馆的计划",
+ "hideAvatar":
+ "0",
+ "btnOrientation":
+ "0",
+ "btns": [{
+ "title": "内容不错",
+ "actionURL": "https://www.dingtalk.com/"
+ }, {
+ "title": "不感兴趣",
+ "actionURL": "https://www.dingtalk.com/"
+ }]
+ }
+ })
+ await actionCard.finish(message)
+
+
+feedCard = on_command("feedCard", to_me())
+
+
+@feedCard.handle()
+async def test_handler(bot: DingBot):
+ message = MessageSegment.raw({
+ "msgtype": "feedCard",
+ "feedCard": {
+ "links": [{
+ "title":
+ "时代的火车向前开1",
+ "messageURL":
+ "https://www.dingtalk.com/",
+ "picURL":
+ "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"
+ }, {
+ "title":
+ "时代的火车向前开2",
+ "messageURL":
+ "https://www.dingtalk.com/",
+ "picURL":
+ "https://img.alicdn.com/tfs/TB1NwmBEL9TBuNjy1zbXXXpepXa-2400-1218.png"
+ }]
+ }
+ })
+ await feedCard.finish(message)
+
+
+atme = on_command("atme", to_me())
+
+
+@atme.handle()
+async def test_handler(bot: DingBot, event: MessageEvent):
+ message = f"@{event.senderNick} at you" + MessageSegment.atMobiles(
+ "13800000001")
+ await atme.finish(message)
+
+
+image = on_command("image", to_me())
+
+
+@image.handle()
+async def test_handler(bot: DingBot, event: MessageEvent):
+ message = MessageSegment.image(
+ "https://static-aliyun-doc.oss-accelerate.aliyuncs.com/assets/img/zh-CN/0634199951/p158167.png"
+ )
+ await image.finish(message)
+
+
+textAdd = on_command("t", to_me())
+
+
+@textAdd.handle()
+async def test_handler(bot: DingBot, event: MessageEvent):
+ message = "第一段消息\n" + MessageSegment.text("asdawefaefa\n")
+ await textAdd.send(message)
+
+ message = message + MessageSegment.text("第二段消息\n")
+ await textAdd.send(message)
+
+ message = message + MessageSegment.text(
+ "\n第三段消息\n") + "adfkasfkhsdkfahskdjasdashdkjasdf"
+ message = message + MessageSegment.extension({
+ "text_type": "code_snippet",
+ "code_language": "C#"
+ })
+ await textAdd.send(message)
+
+
+code = on_command("code", to_me())
+
+
+@code.handle()
+async def test_handler(bot: DingBot, event: MessageEvent):
+ raw = MessageSegment.raw({
+ "msgtype": "text",
+ "text": {
+ "content": 'print("hello world")'
+ },
+ "extension": {
+ "text_type": "code_snippet",
+ "code_language": "Python",
+ }
+ })
+ await code.send(raw)
+ message = MessageSegment.text("""using System;
+
+namespace HelloWorld
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ Console.WriteLine("Hello World!");
+ }
+ }
+}""")
+ message += MessageSegment.extension({
+ "text_type": "code_snippet",
+ "code_language": "C#"
+ })
+ await code.finish(message)
diff --git a/tests/test_plugins/test_permission.py b/tests/test_plugins/test_permission.py
index a7157dff..ee9a45de 100644
--- a/tests/test_plugins/test_permission.py
+++ b/tests/test_plugins/test_permission.py
@@ -11,8 +11,3 @@ test_command = on_startswith("hello", to_me(), permission=SUPERUSER)
@test_command.handle()
async def test_handler(bot: CQHTTPBot):
await test_command.finish("cqhttp hello")
-
-
-@test_command.handle()
-async def test_handler(bot: DingBot):
- await test_command.finish("ding hello")