From 42838138b04acb9d71c6992f47842bf33615fd58 Mon Sep 17 00:00:00 2001 From: aishannon Date: Sun, 29 Mar 2026 16:54:59 +0800 Subject: [PATCH] feat(channel/qq): optimize message sending, seq generation, and message parsing 1. Define `kindType` as constants. 2. Set `seq` generation to global incremental, simplifying `seq` handling. (QQ platform validation rule is `chatID + replyMsgID + seq`, and `seq` does not affect display order.) 3. Increase API timeout from 5 seconds to 20 seconds to reduce send timeouts. 4. Prioritize sending messages using Markdown; fall back to plain text if Markdown sending fails. Also remove the `QQConfig.SendMarkdown` configuration option. 5. Restrict `InputNotify` to be sent only for direct messages. 6. Adjust message parsing to support emoji parsing and utilize the platform-provided ASR (Automatic Speech Recognition) content. --- pkg/channels/qq/qq.go | 103 ++++++++---------------------------------- 1 file changed, 19 insertions(+), 84 deletions(-) diff --git a/pkg/channels/qq/qq.go b/pkg/channels/qq/qq.go index a4ae437eb..4ef3337e3 100644 --- a/pkg/channels/qq/qq.go +++ b/pkg/channels/qq/qq.go @@ -328,7 +328,6 @@ func (c *QQChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) return channels.ErrNotRunning } chatKind := c.getChatKind(msg.ChatID) - var err error for _, part := range msg.Parts { fileInfo, err := c.uploadMedia(ctx, chatKind, msg.ChatID, part) if err != nil { @@ -349,10 +348,10 @@ func (c *QQChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) "chat_id": msg.ChatID, "error": err.Error(), }) - continue + return fmt.Errorf("qq send media: %w", channels.ErrTemporary) } } - return err + return nil } func (c *QQChannel) uploadMedia(ctx context.Context, @@ -582,8 +581,7 @@ func (c *QQChannel) handleC2CMessage() event.C2CMessageEventHandler { return nil } - scope := channels.BuildMediaScope("qq", senderID, data.ID) - content, mediaPaths := c.decodeMessage(context.Background(), event, (*dto.Message)(data), scope) + content, mediaPaths := c.decodeMessage(context.Background(), senderID, event, (*dto.Message)(data)) if content == "" && len(mediaPaths) == 0 { logger.DebugC("qq", "Received empty C2C message with no content/attachments, ignoring") return nil @@ -648,8 +646,7 @@ func (c *QQChannel) handleGroupATMessage() event.GroupATMessageEventHandler { return nil } - scope := channels.BuildMediaScope("qq", data.GroupID, data.ID) - content, mediaPaths := c.decodeMessage(context.Background(), event, (*dto.Message)(data), scope) + content, mediaPaths := c.decodeMessage(context.Background(), senderID, event, (*dto.Message)(data)) if content == "" { logger.DebugC("qq", "Received empty group message, ignoring") return nil @@ -697,7 +694,7 @@ func (c *QQChannel) handleGroupATMessage() event.GroupATMessageEventHandler { } func (c *QQChannel) extractInboundAttachments(chatID, messageID string, - attachments []*dto.MessageAttachment) ([]string, []string) { + attachments []MessageAttachment) ([]string, []string) { if len(attachments) == 0 { return nil, nil @@ -707,7 +704,7 @@ func (c *QQChannel) extractInboundAttachments(chatID, messageID string, mediaPaths := make([]string, 0, len(attachments)) notes := make([]string, 0, len(attachments)) - storeMedia := func(localPath string, attachment *dto.MessageAttachment) string { + storeMedia := func(localPath string, attachment MessageAttachment) string { if store := c.GetMediaStore(); store != nil { ref, err := store.Store(localPath, media.MediaMeta{ Filename: qqAttachmentFilename(attachment), @@ -723,17 +720,12 @@ func (c *QQChannel) extractInboundAttachments(chatID, messageID string, } for _, attachment := range attachments { - if attachment == nil { - continue - } - filename := qqAttachmentFilename(attachment) if localPath := c.downloadAttachment(attachment.URL, filename); localPath != "" { mediaPaths = append(mediaPaths, storeMedia(localPath, attachment)) } else if attachment.URL != "" { mediaPaths = append(mediaPaths, attachment.URL) } - notes = append(notes, qqAttachmentNote(attachment)) } @@ -776,10 +768,7 @@ func (c *QQChannel) downloadHeaders() map[string]string { return headers } -func qqAttachmentFilename(attachment *dto.MessageAttachment) string { - if attachment == nil { - return "attachment" - } +func qqAttachmentFilename(attachment MessageAttachment) string { if attachment.FileName != "" { return attachment.FileName } @@ -803,11 +792,7 @@ func qqAttachmentFilename(attachment *dto.MessageAttachment) string { } } -func qqAttachmentKind(attachment *dto.MessageAttachment) string { - if attachment == nil { - return "file" - } - +func qqAttachmentKind(attachment MessageAttachment) string { contentType := strings.ToLower(attachment.ContentType) filename := strings.ToLower(attachment.FileName) @@ -832,7 +817,7 @@ func qqAttachmentKind(attachment *dto.MessageAttachment) string { } } -func qqAttachmentNote(attachment *dto.MessageAttachment) string { +func qqAttachmentNote(attachment MessageAttachment) string { filename := qqAttachmentFilename(attachment) switch qqAttachmentKind(attachment) { @@ -841,6 +826,9 @@ func qqAttachmentNote(attachment *dto.MessageAttachment) string { case "audio": return fmt.Sprintf("[audio: %s]", filename) case "video": + if attachment.AsrReferText != "" { + return fmt.Sprintf("[video: %s]", attachment.AsrReferText) + } return fmt.Sprintf("[video: %s]", filename) default: return fmt.Sprintf("[file: %s]", filename) @@ -945,10 +933,9 @@ func (c *QQChannel) getReplyExtInfo(ctx context.Context, chatID string) (replyID return replyID, seq } -func (c *QQChannel) decodeMessage(ctx context.Context, event *dto.WSPayload, data *dto.Message, - scope string) (content string, mediaPaths []string) { - - content = parseEmojiText(data.Content) +func (c *QQChannel) decodeMessage(ctx context.Context, chatID string, event *dto.WSPayload, + data *dto.Message) (string, []string) { + content := parseEmojiText(data.Content) wavURL, asrReferText := getVoiceInfo(event) var attachments []MessageAttachment for _, att := range data.Attachments { @@ -967,63 +954,11 @@ func (c *QQChannel) decodeMessage(ctx context.Context, event *dto.WSPayload, dat FileName: att.FileName, }) } - processedPaths, attachmentContent := c.processAttachments(ctx, attachments, scope) - mediaPaths = processedPaths - if content != "" { - content += "\n" + processedPaths, attachmentContents := c.extractInboundAttachments(chatID, data.ID, attachments) + for _, note := range attachmentContents { + content = appendContent(content, note) } - content += attachmentContent - return content, mediaPaths -} - -// processAttachments processes all attachments in a message -func (c *QQChannel) processAttachments(ctx context.Context, attachments []MessageAttachment, - scope string) (mediaPaths []string, content string) { - - // Helper to register a local file with the media store - storeMedia := func(localPath, filename string) string { - store := c.GetMediaStore() - if store == nil { - logger.ErrorCF("qq", "media store is nil", map[string]any{ - "scope": scope, - }) - return "" - } - ref, err := store.Store(localPath, media.MediaMeta{Filename: filename, Source: "qq"}, scope) - if err == nil { - logger.InfoCF("qq", "Stored media", map[string]any{ - "scope": scope, - "localPath": localPath, - "filename": filename, - }) - return ref - } - logger.ErrorCF("qq", "Stored media err ", map[string]any{ - "scope": scope, - "localPath": localPath, - "err": err.Error(), - }) - return localPath - } - - for _, attachment := range attachments { - attachmentType := c.getAttachmentType(attachment) - ref := attachment.URL - localPath := c.downloadAttachment(attachment.URL, attachment.FileName) - if localPath != "" { - if ref = storeMedia(localPath, attachment.FileName); ref == "" { - ref = localPath - } - } - if attachmentType == "audio" && attachment.AsrReferText != "" { - content = appendContent(content, fmt.Sprintf("[audio: %s]", attachment.AsrReferText)) - } else { - content = appendContent(content, fmt.Sprintf("[%v: %s]", attachmentType, ref)) - } - mediaPaths = append(mediaPaths, ref) - } - - return mediaPaths, content + return content, processedPaths } // getAttachmentType determines the type of attachment (image, audio, video, file)