fix(feishu): filter out external URLs from card image extraction

Only Feishu-hosted image keys (img_xxx, icon_xxx) can be downloaded via
the Feishu API. External URLs in src field (https://...) should be
filtered out to avoid download failures.

- Add isFeishuImageKey() to detect Feishu-hosted keys vs external URLs
- Update extractImageKeysRecursive to skip external URLs in src field
- Add tests for external URL filtering and mixed scenarios
This commit is contained in:
ywj 2026-03-14 23:55:54 +08:00
parent c3e0724808
commit be4808dafe
2 changed files with 28 additions and 3 deletions

View file

@ -87,6 +87,7 @@ func stripMentionPlaceholders(content string, mentions []*larkim.MentionEvent) s
// extractCardImageKeys recursively extracts all image keys from a Feishu interactive card. // extractCardImageKeys recursively extracts all image keys from a Feishu interactive card.
// Image keys are used to download images from Feishu API. // Image keys are used to download images from Feishu API.
// Only Feishu-hosted keys are returned (img_xxx, icon_xxx); external URLs are filtered out.
func extractCardImageKeys(rawContent string) []string { func extractCardImageKeys(rawContent string) []string {
if rawContent == "" { if rawContent == "" {
return nil return nil
@ -102,7 +103,21 @@ func extractCardImageKeys(rawContent string) []string {
return keys return keys
} }
// isFeishuImageKey returns true if the string is a Feishu-hosted image key
// (not an external URL). Feishu keys typically start with img_, icon_, or file_.
func isFeishuImageKey(s string) bool {
if s == "" {
return false
}
// Filter out external URLs
if strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://") {
return false
}
return true
}
// extractImageKeysRecursive traverses card structure to find all image keys. // extractImageKeysRecursive traverses card structure to find all image keys.
// Only Feishu-hosted keys are collected; external URLs are skipped.
func extractImageKeysRecursive(v any, keys *[]string) { func extractImageKeysRecursive(v any, keys *[]string) {
switch val := v.(type) { switch val := v.(type) {
case map[string]any: case map[string]any:
@ -110,12 +125,12 @@ func extractImageKeysRecursive(v any, keys *[]string) {
if tag, ok := val["tag"].(string); ok { if tag, ok := val["tag"].(string); ok {
switch tag { switch tag {
case "img": case "img":
// Try img_key first (most common) // Try img_key first (most common, always Feishu-hosted)
if imgKey, ok := val["img_key"].(string); ok && imgKey != "" { if imgKey, ok := val["img_key"].(string); ok && imgKey != "" {
*keys = append(*keys, imgKey) *keys = append(*keys, imgKey)
} }
// Also try src (alternative format) // Also try src, but only if it's a Feishu-hosted key (not external URL)
if src, ok := val["src"].(string); ok && src != "" { if src, ok := val["src"].(string); ok && src != "" && isFeishuImageKey(src) {
*keys = append(*keys, src) *keys = append(*keys, src)
} }
case "icon": case "icon":

View file

@ -347,6 +347,16 @@ func TestExtractCardImageKeys(t *testing.T) {
content: `{"header":{"title":{"content":"Title"}},"elements":[{"tag":"div","text":{"content":"Description"}},{"tag":"img","img_key":"img_main"}]}`, content: `{"header":{"title":{"content":"Title"}},"elements":[{"tag":"div","text":{"content":"Description"}},{"tag":"img","img_key":"img_main"}]}`,
want: []string{"img_main"}, want: []string{"img_main"},
}, },
{
name: "external URL in src is filtered out",
content: `{"elements":[{"tag":"img","src":"https://example.com/image.png"}]}`,
want: nil,
},
{
name: "mixed Feishu keys and external URLs",
content: `{"elements":[{"tag":"img","img_key":"img_feishu"},{"tag":"img","src":"https://cdn.example.com/external.jpg"},{"tag":"img","src":"img_another"}]}`,
want: []string{"img_feishu", "img_another"},
},
} }
for _, tt := range tests { for _, tt := range tests {