package dto import ( "encoding/json" "fmt" "carrot_bbs/internal/model" ) // ParseSegmentData 解析Segment数据到目标结构体 func ParseSegmentData(segment model.MessageSegment, target interface{}) error { dataBytes, err := json.Marshal(segment.Data) if err != nil { return err } return json.Unmarshal(dataBytes, target) } // NewTextSegment 创建文本Segment func NewTextSegment(content string) model.MessageSegment { return model.MessageSegment{ Type: string(SegmentTypeText), Data: map[string]interface{}{"text": content}, } } // NewImageSegment 创建图片Segment func NewImageSegment(url string, width, height int, thumbnailURL string) model.MessageSegment { return model.MessageSegment{ Type: string(SegmentTypeImage), Data: map[string]interface{}{ "url": url, "width": width, "height": height, "thumbnail_url": thumbnailURL, }, } } // NewImageSegmentWithSize 创建带文件大小的图片Segment func NewImageSegmentWithSize(url string, width, height int, thumbnailURL string, fileSize int64) model.MessageSegment { return model.MessageSegment{ Type: string(SegmentTypeImage), Data: map[string]interface{}{ "url": url, "width": width, "height": height, "thumbnail_url": thumbnailURL, "file_size": fileSize, }, } } // NewVoiceSegment 创建语音Segment func NewVoiceSegment(url string, duration int, fileSize int64) model.MessageSegment { return model.MessageSegment{ Type: string(SegmentTypeVoice), Data: map[string]interface{}{ "url": url, "duration": duration, "file_size": fileSize, }, } } // NewVideoSegment 创建视频Segment func NewVideoSegment(url string, width, height, duration int, thumbnailURL string, fileSize int64) model.MessageSegment { return model.MessageSegment{ Type: string(SegmentTypeVideo), Data: map[string]interface{}{ "url": url, "width": width, "height": height, "duration": duration, "thumbnail_url": thumbnailURL, "file_size": fileSize, }, } } // NewFileSegment 创建文件Segment func NewFileSegment(url, name string, size int64, mimeType string) model.MessageSegment { return model.MessageSegment{ Type: string(SegmentTypeFile), Data: map[string]interface{}{ "url": url, "name": name, "size": size, "mime_type": mimeType, }, } } // NewAtSegment 创建@Segment,只存储 user_id,昵称由前端根据群成员列表实时解析 func NewAtSegment(userID string) model.MessageSegment { return model.MessageSegment{ Type: string(SegmentTypeAt), Data: map[string]interface{}{ "user_id": userID, }, } } // NewAtAllSegment 创建@所有人Segment func NewAtAllSegment() model.MessageSegment { return model.MessageSegment{ Type: string(SegmentTypeAt), Data: map[string]interface{}{ "user_id": "all", }, } } // NewReplySegment 创建回复Segment func NewReplySegment(messageID string) model.MessageSegment { return model.MessageSegment{ Type: string(SegmentTypeReply), Data: map[string]interface{}{"id": messageID}, } } // NewFaceSegment 创建表情Segment func NewFaceSegment(id int, name, url string) model.MessageSegment { return model.MessageSegment{ Type: string(SegmentTypeFace), Data: map[string]interface{}{ "id": id, "name": name, "url": url, }, } } // NewLinkSegment 创建链接Segment func NewLinkSegment(url, title, description, image string) model.MessageSegment { return model.MessageSegment{ Type: string(SegmentTypeLink), Data: map[string]interface{}{ "url": url, "title": title, "description": description, "image": image, }, } } // ExtractTextContentFromJSON 从JSON格式的segments中提取纯文本内容 // 用于从数据库读取的 []byte 格式的 segments // 已废弃:现在数据库直接存储 model.MessageSegments 类型 func ExtractTextContentFromJSON(segmentsJSON []byte) string { if len(segmentsJSON) == 0 { return "" } var segments model.MessageSegments if err := json.Unmarshal(segmentsJSON, &segments); err != nil { return "" } return ExtractTextContentFromModel(segments) } // ExtractTextContentFromModel 从 model.MessageSegments 中提取纯文本内容 func ExtractTextContentFromModel(segments model.MessageSegments) string { var result string for _, segment := range segments { switch segment.Type { case "text": if text, ok := segment.Data["text"].(string); ok { result += text } case "at": userID, _ := segment.Data["user_id"].(string) if userID == "all" { result += "@所有人 " } else if userID != "" { // 昵称由前端实时解析,后端文本提取仅用于推送通知兜底 result += "@某人 " } case "image": result += "[图片]" case "voice": result += "[语音]" case "video": result += "[视频]" case "file": if name, ok := segment.Data["name"].(string); ok && name != "" { result += fmt.Sprintf("[文件:%s]", name) } else { result += "[文件]" } case "face": if name, ok := segment.Data["name"].(string); ok && name != "" { result += fmt.Sprintf("[%s]", name) } else { result += "[表情]" } case "link": if title, ok := segment.Data["title"].(string); ok && title != "" { result += fmt.Sprintf("[链接:%s]", title) } else { result += "[链接]" } } } return result } // ExtractTextContent 从消息链中提取纯文本内容 // 用于搜索、通知展示等场景 func ExtractTextContent(segments model.MessageSegments) string { return ExtractTextContentFromModel(segments) } // ExtractMentionedUsers 从消息链中提取被@的用户ID列表 // 不包括 "all"(@所有人) func ExtractMentionedUsers(segments model.MessageSegments) []string { var userIDs []string seen := make(map[string]bool) for _, segment := range segments { if segment.Type == string(SegmentTypeAt) { userID, _ := segment.Data["user_id"].(string) if userID != "all" && userID != "" && !seen[userID] { userIDs = append(userIDs, userID) seen[userID] = true } } } return userIDs } // IsAtAll 检查消息是否@了所有人 func IsAtAll(segments model.MessageSegments) bool { for _, segment := range segments { if segment.Type == string(SegmentTypeAt) { if userID, ok := segment.Data["user_id"].(string); ok && userID == "all" { return true } } } return false } // GetReplyMessageID 从消息链中获取被回复的消息ID // 如果没有回复segment,返回空字符串 func GetReplyMessageID(segments model.MessageSegments) string { for _, segment := range segments { if segment.Type == string(SegmentTypeReply) { if id, ok := segment.Data["id"].(string); ok { return id } } } return "" } // BuildSegmentsFromContent 从旧版content构建segments // 用于兼容旧版本消息 func BuildSegmentsFromContent(contentType, content string, mediaURL *string) model.MessageSegments { var segments model.MessageSegments switch contentType { case "text": segments = append(segments, NewTextSegment(content)) case "image": if mediaURL != nil { segments = append(segments, NewImageSegment(*mediaURL, 0, 0, "")) } case "voice": if mediaURL != nil { segments = append(segments, NewVoiceSegment(*mediaURL, 0, 0)) } case "video": if mediaURL != nil { segments = append(segments, NewVideoSegment(*mediaURL, 0, 0, 0, "", 0)) } case "file": if mediaURL != nil { segments = append(segments, NewFileSegment(*mediaURL, content, 0, "")) } default: // 默认当作文本处理 if content != "" { segments = append(segments, NewTextSegment(content)) } } return segments } // HasSegmentType 检查消息链中是否包含指定类型的segment func HasSegmentType(segments model.MessageSegments, segmentType SegmentType) bool { for _, segment := range segments { if segment.Type == string(segmentType) { return true } } return false } // GetSegmentsByType 获取消息链中所有指定类型的segment func GetSegmentsByType(segments model.MessageSegments, segmentType SegmentType) []model.MessageSegment { var result []model.MessageSegment for _, segment := range segments { if segment.Type == string(segmentType) { result = append(result, segment) } } return result } // GetFirstImageURL 获取消息链中第一张图片的URL // 如果没有图片,返回空字符串 func GetFirstImageURL(segments model.MessageSegments) string { for _, segment := range segments { if segment.Type == string(SegmentTypeImage) { if url, ok := segment.Data["url"].(string); ok { return url } } } return "" } // GetFirstMediaURL 获取消息链中第一个媒体文件的URL(图片/视频/语音/文件) // 用于兼容旧版本API func GetFirstMediaURL(segments model.MessageSegments) string { for _, segment := range segments { switch segment.Type { case string(SegmentTypeImage), string(SegmentTypeVideo), string(SegmentTypeVoice), string(SegmentTypeFile): if url, ok := segment.Data["url"].(string); ok { return url } } } return "" } // DetermineContentType 从消息链推断消息类型(用于兼容旧版本) func DetermineContentType(segments model.MessageSegments) string { if len(segments) == 0 { return "text" } // 优先检查媒体类型 for _, segment := range segments { switch segment.Type { case string(SegmentTypeImage): return "image" case string(SegmentTypeVideo): return "video" case string(SegmentTypeVoice): return "voice" case string(SegmentTypeFile): return "file" } } // 默认返回text return "text" }