package main import ( "fmt" "net/url" "os" "path/filepath" "strings" ) var afterSalesImageKeys = map[string]struct{}{ "file_path": {}, "filepath": {}, "file_name": {}, "filename": {}, "path": {}, "local_path": {}, "localpath": {}, "image_path": {}, "imagepath": {}, "thumb_path": {}, "thumbnail": {}, "url": {}, "image_url": {}, "file_id": {}, "fileid": {}, "aes_key": {}, "md5": {}, "content": {}, "message": {}, "text": {}, } func extractAfterSalesImage(raw map[string]interface{}) (string, string) { values := collectImageLikeStrings(raw) for _, value := range values { if isLocalImagePath(value) { return value, "" } } for _, value := range values { if looksLikeImageRef(value) { return "", value } } return "", "" } func extractAfterSalesImageFromMessage(msg autoReplyMessage, raw map[string]interface{}) (string, string) { for _, value := range []string{msg.MediaLocalPath, msg.MediaFileName} { if isLocalImagePath(value) { return normalizedLocalImagePath(value), "" } } if path, _ := extractAfterSalesImage(raw); path != "" { return normalizedLocalImagePath(path), "" } if shouldDownloadAfterSalesImage(msg, raw) { if path, err := ensureAutoReplyMediaLocalPath(msg); err == nil && isLocalImagePath(path) { return normalizedLocalImagePath(path), "" } } if _, ref := extractAfterSalesImage(raw); ref != "" { return "", ref } for _, value := range []string{msg.MediaURL, msg.MediaFileID} { if looksLikeImageRef(value) { return "", value } } return "", "" } func shouldDownloadAfterSalesImage(msg autoReplyMessage, raw map[string]interface{}) bool { switch strings.TrimSpace(msg.MediaKind) { case "image", "emoji": return true } if msg.RawType == 11042 { return true } if strings.TrimSpace(msg.MediaURL) != "" || strings.TrimSpace(msg.MediaFileID) != "" { return looksLikeImageMessage(raw) } return false } func normalizedLocalImagePath(value string) string { value = strings.Trim(strings.TrimSpace(value), "\"'") if value == "" { return "" } if filepath.IsAbs(value) { return value } candidate := resolveAutoReplyPath(value) if _, err := os.Stat(candidate); err == nil { return candidate } return value } func resolveAfterSalesImageRef(value string) string { value = strings.TrimSpace(value) if value == "" { return "" } if isLocalImagePath(value) { return normalizedLocalImagePath(value) } lower := strings.ToLower(value) if !strings.HasPrefix(lower, "http://") && !strings.HasPrefix(lower, "https://") { return "" } parsed, err := url.Parse(value) if err != nil { return "" } ext := strings.ToLower(filepath.Ext(parsed.Path)) switch ext { case ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp": default: ext = ".jpg" } base := filepath.Base(parsed.Path) if base == "." || base == string(filepath.Separator) || strings.TrimSpace(base) == "" { base = "after_sales_image" } savePath := generateSavePath("after_sales_images", base, ext) if savePath == "" { return "" } if err := downloadPlainMedia(value, savePath); err != nil { return "" } if isLocalImagePath(savePath) { return savePath } return "" } func looksLikeImageMessage(raw map[string]interface{}) bool { if raw == nil { return false } if typeVal, ok := raw["type"]; ok { switch intFromAny(typeVal) { case 11042, 11044, 11045, 11046: return true } } values := collectImageLikeStrings(raw) for _, value := range values { if isLocalImagePath(value) || looksLikeImageRef(value) { return true } } return false } func collectImageLikeStrings(value interface{}) []string { var result []string var walk func(interface{}, string) walk = func(item interface{}, key string) { switch v := item.(type) { case map[string]interface{}: for k, child := range v { walk(child, strings.ToLower(strings.TrimSpace(k))) } case []interface{}: for _, child := range v { walk(child, key) } case string: text := strings.TrimSpace(v) if text == "" { return } if _, ok := afterSalesImageKeys[key]; ok || isLocalImagePath(text) || looksLikeImageRef(text) { result = append(result, text) } case fmt.Stringer: text := strings.TrimSpace(v.String()) if text != "" { result = append(result, text) } } } walk(value, "") return uniqueNonEmptyStrings(result) } func isLocalImagePath(value string) bool { value = strings.Trim(strings.TrimSpace(value), "\"'") if value == "" { return false } if strings.HasPrefix(strings.ToLower(value), "http://") || strings.HasPrefix(strings.ToLower(value), "https://") { return false } ext := strings.ToLower(filepath.Ext(value)) switch ext { case ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp": default: return false } if filepath.IsAbs(value) { if _, err := os.Stat(value); err == nil { return true } return false } candidate := resolveAutoReplyPath(value) if _, err := os.Stat(candidate); err == nil { return true } return false } func looksLikeImageRef(value string) bool { value = strings.TrimSpace(value) if value == "" { return false } lower := strings.ToLower(value) if strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://") { if parsed, err := url.Parse(value); err == nil { ext := strings.ToLower(filepath.Ext(parsed.Path)) return ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" || ext == ".bmp" || ext == ".webp" || strings.Contains(lower, "image") } return strings.Contains(lower, "image") } ext := strings.ToLower(filepath.Ext(value)) return ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif" || ext == ".bmp" || ext == ".webp" || strings.Contains(lower, "image") || strings.Contains(lower, "file_id") || strings.Contains(lower, "fileid") }