When users send text+image together in Feishu, the message type is "post" (rich text) instead of "image". The image download logic only handled MsgTypeImage, so images embedded in post messages were silently ignored — the agent received raw JSON instead of actual image content. - Add extractPostImageKeys() to parse img tags from post content - Add extractPostText() to extract plain text from post content - Handle MsgTypePost in downloadInboundMedia to download embedded images - Handle MsgTypePost in appendMediaTags to add [image: photo] tag
141 lines
4 KiB
Go
141 lines
4 KiB
Go
package feishu
|
|
|
|
import (
|
|
"encoding/json"
|
|
"regexp"
|
|
"strings"
|
|
|
|
larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
|
|
)
|
|
|
|
// mentionPlaceholderRegex matches @_user_N placeholders inserted by Feishu for mentions.
|
|
var mentionPlaceholderRegex = regexp.MustCompile(`@_user_\d+`)
|
|
|
|
// stringValue safely dereferences a *string pointer.
|
|
func stringValue(v *string) string {
|
|
if v == nil {
|
|
return ""
|
|
}
|
|
return *v
|
|
}
|
|
|
|
// buildMarkdownCard builds a Feishu Interactive Card JSON 2.0 string with markdown content.
|
|
// JSON 2.0 cards support full CommonMark standard markdown syntax.
|
|
func buildMarkdownCard(content string) (string, error) {
|
|
card := map[string]any{
|
|
"schema": "2.0",
|
|
"body": map[string]any{
|
|
"elements": []map[string]any{
|
|
{
|
|
"tag": "markdown",
|
|
"content": content,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
data, err := json.Marshal(card)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(data), nil
|
|
}
|
|
|
|
// extractJSONStringField unmarshals content as JSON and returns the value of the given string field.
|
|
// Returns "" if the content is invalid JSON or the field is missing/empty.
|
|
func extractJSONStringField(content, field string) string {
|
|
var m map[string]json.RawMessage
|
|
if err := json.Unmarshal([]byte(content), &m); err != nil {
|
|
return ""
|
|
}
|
|
raw, ok := m[field]
|
|
if !ok {
|
|
return ""
|
|
}
|
|
var s string
|
|
if err := json.Unmarshal(raw, &s); err != nil {
|
|
return ""
|
|
}
|
|
return s
|
|
}
|
|
|
|
// extractImageKey extracts the image_key from a Feishu image message content JSON.
|
|
// Format: {"image_key": "img_xxx"}
|
|
func extractImageKey(content string) string { return extractJSONStringField(content, "image_key") }
|
|
|
|
// extractPostImageKeys extracts all image_key values from a Feishu post (rich text) message.
|
|
// Post content format: {"title":"...","content":[[{"tag":"img","image_key":"..."},...],...]}.
|
|
func extractPostImageKeys(rawContent string) []string {
|
|
var post struct {
|
|
Content [][]struct {
|
|
Tag string `json:"tag"`
|
|
ImageKey string `json:"image_key"`
|
|
} `json:"content"`
|
|
}
|
|
if err := json.Unmarshal([]byte(rawContent), &post); err != nil {
|
|
return nil
|
|
}
|
|
|
|
var keys []string
|
|
for _, paragraph := range post.Content {
|
|
for _, elem := range paragraph {
|
|
if elem.Tag == "img" && elem.ImageKey != "" {
|
|
keys = append(keys, elem.ImageKey)
|
|
}
|
|
}
|
|
}
|
|
return keys
|
|
}
|
|
|
|
// extractPostText extracts plain text from a Feishu post (rich text) message.
|
|
// Returns title and text elements joined by newlines. Falls back to rawContent on parse error.
|
|
func extractPostText(rawContent string) string {
|
|
var post struct {
|
|
Title string `json:"title"`
|
|
Content [][]struct {
|
|
Tag string `json:"tag"`
|
|
Text string `json:"text"`
|
|
} `json:"content"`
|
|
}
|
|
if err := json.Unmarshal([]byte(rawContent), &post); err != nil {
|
|
return rawContent
|
|
}
|
|
|
|
var parts []string
|
|
if post.Title != "" {
|
|
parts = append(parts, post.Title)
|
|
}
|
|
for _, paragraph := range post.Content {
|
|
for _, elem := range paragraph {
|
|
if elem.Tag == "text" && elem.Text != "" {
|
|
parts = append(parts, elem.Text)
|
|
}
|
|
}
|
|
}
|
|
if len(parts) == 0 {
|
|
return ""
|
|
}
|
|
return strings.Join(parts, "\n")
|
|
}
|
|
|
|
// extractFileKey extracts the file_key from a Feishu file/audio message content JSON.
|
|
// Format: {"file_key": "file_xxx", "file_name": "...", ...}
|
|
func extractFileKey(content string) string { return extractJSONStringField(content, "file_key") }
|
|
|
|
// extractFileName extracts the file_name from a Feishu file message content JSON.
|
|
func extractFileName(content string) string { return extractJSONStringField(content, "file_name") }
|
|
|
|
// stripMentionPlaceholders removes @_user_N placeholders from the text content.
|
|
// These are inserted by Feishu when users @mention someone in a message.
|
|
func stripMentionPlaceholders(content string, mentions []*larkim.MentionEvent) string {
|
|
if len(mentions) == 0 {
|
|
return content
|
|
}
|
|
for _, m := range mentions {
|
|
if m.Key != nil && *m.Key != "" {
|
|
content = strings.ReplaceAll(content, *m.Key, "")
|
|
}
|
|
}
|
|
// Also clean up any remaining @_user_N patterns
|
|
content = mentionPlaceholderRegex.ReplaceAllString(content, "")
|
|
return strings.TrimSpace(content)
|
|
}
|