363 lines
9.5 KiB
Go
363 lines
9.5 KiB
Go
|
|
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"
|
|||
|
|
}
|