feat(dingtalk): prefix group replies with sender mention
In group chats, prefix the outbound markdown reply with an H1 naming the sender (# @<nick>) so the addressed user can spot it in a busy group. Private replies are sent verbatim. Visual only: DingTalk markdown robot messages do not push real @ notifications (that would require staffId plumbing and a different message type). sender_name is read from OutboundMessage.metadata, which the agent loop already propagates from inbound metadata. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
Xubin Ren
co-authored by
Claude
parent
14e692e40d
commit
205889f9e0
@@ -713,8 +713,15 @@ class DingTalkChannel(BaseChannel):
|
|||||||
if not token:
|
if not token:
|
||||||
raise RuntimeError("DingTalk access token unavailable")
|
raise RuntimeError("DingTalk access token unavailable")
|
||||||
|
|
||||||
if msg.content and msg.content.strip():
|
content = msg.content.strip() if msg.content else ""
|
||||||
if not await self._send_markdown_text(token, msg.chat_id, msg.content.strip()):
|
if content:
|
||||||
|
# In group chats, prefix the reply with a markdown header naming the
|
||||||
|
# sender so the addressed user can spot the reply. Visual only —
|
||||||
|
# DingTalk's markdown robot messages do not push real @ notifications.
|
||||||
|
sender_name = msg.metadata.get("sender_name") if msg.metadata else None
|
||||||
|
if msg.chat_id.startswith("group:") and sender_name:
|
||||||
|
content = f"# @{sender_name}\n\n{content}"
|
||||||
|
if not await self._send_markdown_text(token, msg.chat_id, content):
|
||||||
raise RuntimeError("DingTalk text message was not delivered")
|
raise RuntimeError("DingTalk text message was not delivered")
|
||||||
|
|
||||||
for media_ref in msg.media or []:
|
for media_ref in msg.media or []:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import json
|
||||||
import zipfile
|
import zipfile
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
@@ -252,6 +253,56 @@ async def test_group_send_uses_group_messages_api() -> None:
|
|||||||
assert call["json"]["msgKey"] == "sampleMarkdown"
|
assert call["json"]["msgKey"] == "sampleMarkdown"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_group_send_prepends_sender_mention(monkeypatch) -> None:
|
||||||
|
"""Group replies are prefixed with a markdown header naming the sender."""
|
||||||
|
config = DingTalkConfig(client_id="app", client_secret="secret", allow_from=["*"])
|
||||||
|
channel = DingTalkChannel(config, MessageBus())
|
||||||
|
channel._http = _FakeHttp()
|
||||||
|
|
||||||
|
async def _fake_token() -> str:
|
||||||
|
return "token"
|
||||||
|
|
||||||
|
monkeypatch.setattr(channel, "_get_access_token", _fake_token)
|
||||||
|
|
||||||
|
await channel.send(
|
||||||
|
OutboundMessage(
|
||||||
|
channel="dingtalk",
|
||||||
|
chat_id="group:conv123",
|
||||||
|
content="hello",
|
||||||
|
metadata={"sender_name": "Alice"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
sent_text = json.loads(channel._http.calls[0]["json"]["msgParam"])["text"]
|
||||||
|
assert sent_text == "# @Alice\n\nhello"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_private_send_does_not_prepend_mention(monkeypatch) -> None:
|
||||||
|
"""Private replies are sent verbatim, without the sender header."""
|
||||||
|
config = DingTalkConfig(client_id="app", client_secret="secret", allow_from=["*"])
|
||||||
|
channel = DingTalkChannel(config, MessageBus())
|
||||||
|
channel._http = _FakeHttp()
|
||||||
|
|
||||||
|
async def _fake_token() -> str:
|
||||||
|
return "token"
|
||||||
|
|
||||||
|
monkeypatch.setattr(channel, "_get_access_token", _fake_token)
|
||||||
|
|
||||||
|
await channel.send(
|
||||||
|
OutboundMessage(
|
||||||
|
channel="dingtalk",
|
||||||
|
chat_id="user1", # private chat: no "group:" prefix
|
||||||
|
content="hello",
|
||||||
|
metadata={"sender_name": "Alice"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
sent_text = json.loads(channel._http.calls[0]["json"]["msgParam"])["text"]
|
||||||
|
assert sent_text == "hello"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_handler_uses_voice_recognition_text_when_text_is_empty(monkeypatch) -> None:
|
async def test_handler_uses_voice_recognition_text_when_text_is_empty(monkeypatch) -> None:
|
||||||
bus = MessageBus()
|
bus = MessageBus()
|
||||||
|
|||||||
Reference in New Issue
Block a user