fix(dingtalk): parse rich text image messages

This commit is contained in:
姚章浩
2026-07-16 00:49:26 +08:00
parent c9c4f0aaa5
commit 7ccf3365f0
4 changed files with 119 additions and 14 deletions
+4 -13
View File
@@ -176,23 +176,14 @@ func parseCallbackMessage(msg *callbackMessage) *im.IncomingMessage {
userID = msg.SenderID
}
content := ""
text := ""
if msg.Text != nil {
content = strings.TrimSpace(msg.Text.Content)
text = msg.Text.Content
}
messageType := im.MessageTypeText
fileKey := ""
messageType, content, fileKey := parseDingTalkStreamPayload(msg.Msgtype, msg.Content, text)
fileName := ""
if strings.EqualFold(msg.Msgtype, "picture") {
messageType = im.MessageTypeImage
if messageType == im.MessageTypeImage {
fileName = msg.MsgID + ".png"
content = ""
var picture struct {
DownloadCode string `json:"downloadCode"`
}
if json.Unmarshal(msg.Content, &picture) == nil {
fileKey = strings.TrimSpace(picture.DownloadCode)
}
}
return &im.IncomingMessage{
+22
View File
@@ -41,6 +41,28 @@ func TestParseCallbackMessagePicture(t *testing.T) {
}
}
func TestParseCallbackMessageRichTextWithPictureAndText(t *testing.T) {
msg := &callbackMessage{
MsgID: "msg-richtext-1",
Msgtype: "richText",
Content: []byte(`{"richText":[{"text":" 请描述这张图片 "},{"type":"picture","downloadCode":"rich-webhook-code"}]}`),
}
got := parseCallbackMessage(msg)
if got.MessageType != im.MessageTypeImage {
t.Fatalf("MessageType = %q, want image", got.MessageType)
}
if got.Content != "请描述这张图片" {
t.Fatalf("Content = %q", got.Content)
}
if got.FileKey != "rich-webhook-code" {
t.Fatalf("FileKey = %q", got.FileKey)
}
if got.FileName != "msg-richtext-1.png" {
t.Fatalf("FileName = %q", got.FileName)
}
}
func TestAdapterDownloadFileUsesMessageFilesAPI(t *testing.T) {
var sawToken bool
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+38 -1
View File
@@ -102,9 +102,11 @@ func (c *LongConnClient) onChatBotMessage(ctx context.Context, data *chatbot.Bot
}
func parseDingTalkStreamPayload(msgType string, rawContent interface{}, text string) (im.MessageType, string, string) {
if !strings.EqualFold(strings.TrimSpace(msgType), "picture") {
normalizedType := strings.ToLower(strings.TrimSpace(msgType))
if normalizedType != "picture" && normalizedType != "richtext" {
return im.MessageTypeText, strings.TrimSpace(text), ""
}
var payload []byte
switch value := rawContent.(type) {
case string:
@@ -120,6 +122,41 @@ func parseDingTalkStreamPayload(msgType string, rawContent interface{}, text str
return im.MessageTypeImage, "", ""
}
}
if normalizedType == "richtext" {
var rich struct {
Items []struct {
Text string `json:"text"`
Type string `json:"type"`
DownloadCode string `json:"downloadCode"`
} `json:"richText"`
}
if json.Unmarshal(payload, &rich) != nil {
return im.MessageTypeText, strings.TrimSpace(text), ""
}
var textParts []string
fileKey := ""
hasPicture := false
for _, item := range rich.Items {
if itemText := strings.TrimSpace(item.Text); itemText != "" {
textParts = append(textParts, itemText)
}
if strings.EqualFold(strings.TrimSpace(item.Type), "picture") {
hasPicture = true
if fileKey == "" {
fileKey = strings.TrimSpace(item.DownloadCode)
}
}
}
content := strings.Join(textParts, "\n")
if hasPicture {
return im.MessageTypeImage, content, fileKey
}
return im.MessageTypeText, content, ""
}
var picture struct {
DownloadCode string `json:"downloadCode"`
}
+55
View File
@@ -35,3 +35,58 @@ func TestParseDingTalkStreamPayloadPictureJSONString(t *testing.T) {
t.Fatalf("got type=%q fileKey=%q", messageType, fileKey)
}
}
func TestParseDingTalkStreamPayloadRichTextWithPictureAndText(t *testing.T) {
messageType, content, fileKey := parseDingTalkStreamPayload("richText", map[string]interface{}{
"richText": []interface{}{
map[string]interface{}{"text": " 请回答图片中的问题 "},
map[string]interface{}{"type": "picture", "downloadCode": "rich-stream-code"},
},
}, "")
if messageType != im.MessageTypeImage {
t.Fatalf("MessageType = %q, want image", messageType)
}
if content != "请回答图片中的问题" {
t.Fatalf("content = %q", content)
}
if fileKey != "rich-stream-code" {
t.Fatalf("fileKey = %q", fileKey)
}
}
func TestParseDingTalkStreamPayloadRichTextJoinsTextParts(t *testing.T) {
messageType, content, fileKey := parseDingTalkStreamPayload("richText", map[string]interface{}{
"richText": []interface{}{
map[string]interface{}{"text": "第一段"},
map[string]interface{}{"type": "picture", "downloadCode": "rich-code"},
map[string]interface{}{"text": "第二段"},
},
}, "")
if messageType != im.MessageTypeImage || content != "第一段\n第二段" || fileKey != "rich-code" {
t.Fatalf("got type=%q content=%q fileKey=%q", messageType, content, fileKey)
}
}
func TestParseDingTalkStreamPayloadRichTextWithoutPictureStaysText(t *testing.T) {
messageType, content, fileKey := parseDingTalkStreamPayload("richText", map[string]interface{}{
"richText": []interface{}{
map[string]interface{}{"text": " 只有文字 "},
},
}, "")
if messageType != im.MessageTypeText || content != "只有文字" || fileKey != "" {
t.Fatalf("got type=%q content=%q fileKey=%q", messageType, content, fileKey)
}
}
func TestParseDingTalkStreamPayloadRichTextPictureWithoutDownloadCodeStaysImage(t *testing.T) {
messageType, content, fileKey := parseDingTalkStreamPayload("richText", map[string]interface{}{
"richText": []interface{}{
map[string]interface{}{"text": "识别图片"},
map[string]interface{}{"type": "picture"},
},
}, "")
if messageType != im.MessageTypeImage || content != "识别图片" || fileKey != "" {
t.Fatalf("got type=%q content=%q fileKey=%q", messageType, content, fileKey)
}
}