From 1315e9024afbeb1edf952fc7d32712c64dada61e Mon Sep 17 00:00:00 2001 From: tzzml Date: Mon, 2 Mar 2026 20:49:07 +0800 Subject: [PATCH] feat(feishu): add emoji reaction feedback and markdown card support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add emoji reaction (THINKING) when receiving messages for quick user feedback - Use Feishu Interactive Card (JSON 2.0) for full Markdown support - Add addReaction method for emoji reaction API calls πŸ“ Description Add quick feedback mechanism for Feishu channel using emoji reactions, and improve Markdown rendering by using Feishu Interactive Card format. πŸ—£οΈ Type of Change - ✨ New feature (non-breaking change which adds functionality) πŸ€– AI Code Generation - πŸ€– Fully AI-generated (100% AI, 0% Human) πŸ”— Related Issue N/A πŸ“š Technical Context - Uses Feishu Message Reaction API for emoji feedback - Uses Feishu Interactive Card JSON 2.0 schema for Markdown rendering - THINKING emoji indicates system is processing the request πŸ§ͺ Test Environment - __Hardware:__ PC - __OS:__ macOS - __Channels:__ Feishu (飞书) β˜‘οΈ Checklist - My code/docs follow the style of this project. - I have performed a self-review of my own changes. Co-Authored-By: Claude Opus 4.6 --- pkg/channels/feishu/feishu_64.go | 69 +++++++++++++++++++++++++++++--- 1 file changed, 64 insertions(+), 5 deletions(-) diff --git a/pkg/channels/feishu/feishu_64.go b/pkg/channels/feishu/feishu_64.go index 1db1bf669..ce6e72afe 100644 --- a/pkg/channels/feishu/feishu_64.go +++ b/pkg/channels/feishu/feishu_64.go @@ -102,16 +102,29 @@ func (c *FeishuChannel) Send(ctx context.Context, msg bus.OutboundMessage) error return fmt.Errorf("chat ID is empty") } - payload, err := json.Marshal(map[string]string{"text": msg.Content}) - if err != nil { - return fmt.Errorf("failed to marshal feishu content: %w", err) + // Construct a Feishu Interactive Card (JSON 2.0) for full Markdown support + card := map[string]interface{}{ + "schema": "2.0", + "config": map[string]interface{}{ + "wide_screen_mode": true, + }, + "body": map[string]interface{}{ + "elements": []map[string]interface{}{ + { + "tag": "markdown", + "content": msg.Content, + }, + }, + }, } + payload, _ := json.Marshal(card) + msgType := larkim.MsgTypeInteractive req := larkim.NewCreateMessageReqBuilder(). ReceiveIdType(larkim.ReceiveIdTypeChatId). Body(larkim.NewCreateMessageReqBodyBuilder(). ReceiveId(msg.ChatID). - MsgType(larkim.MsgTypeText). + MsgType(msgType). Content(string(payload)). Uuid(fmt.Sprintf("picoclaw-%d", time.Now().UnixNano())). Build()). @@ -127,7 +140,9 @@ func (c *FeishuChannel) Send(ctx context.Context, msg bus.OutboundMessage) error } logger.DebugCF("feishu", "Feishu message sent", map[string]any{ - "chat_id": msg.ChatID, + "chat_id": msg.ChatID, + "msg_type": msgType, + "payload": string(payload), }) return nil @@ -201,6 +216,16 @@ func (c *FeishuChannel) handleMessageReceive(ctx context.Context, event *larkim. return nil } + // Add emoji reaction for quick feedback (using "THINKING" to indicate processing) + if messageID != "" { + if _, err := c.addReaction(ctx, messageID, "THINKING"); err != nil { + logger.DebugCF("feishu", "Failed to add reaction", map[string]any{ + "message_id": messageID, + "error": err.Error(), + }) + } + } + c.HandleMessage(ctx, peer, messageID, senderID, chatID, content, nil, metadata, senderInfo) return nil } @@ -239,3 +264,37 @@ func extractFeishuMessageContent(message *larkim.EventMessage) string { return *message.Content } + +// addReaction adds an emoji reaction to a message for quick user feedback. +// Returns an undo function that removes the reaction, or nil on error. +func (c *FeishuChannel) addReaction(ctx context.Context, messageID, emojiType string) (func(), error) { + req := larkim.NewCreateMessageReactionReqBuilder(). + MessageId(messageID). + Body(larkim.NewCreateMessageReactionReqBodyBuilder(). + ReactionType(&larkim.Emoji{EmojiType: &emojiType}). + Build()). + Build() + + resp, err := c.client.Im.V1.MessageReaction.Create(ctx, req) + if err != nil { + return nil, fmt.Errorf("feishu add reaction: %w", err) + } + if !resp.Success() { + return nil, fmt.Errorf("feishu add reaction failed (code=%d msg=%s)", resp.Code, resp.Msg) + } + + reactionID := stringValue(resp.Data.ReactionId) + + undo := func() { + if reactionID == "" { + return + } + delReq := larkim.NewDeleteMessageReactionReqBuilder(). + MessageId(messageID). + ReactionId(reactionID). + Build() + _, _ = c.client.Im.V1.MessageReaction.Delete(ctx, delReq) + } + + return undo, nil +}