owpengram-server/internal/domain/media_category.go

157 lines
5.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package domain
// MediaCategory 是共享媒体标签页的基础类别。一条消息可同时属于多个类别
// (例如带链接的图片 = Photo + URL。落库为媒体索引表的 category 列SMALLINT
//
// 这里只存"基础类别";客户端的复合过滤器PhotoVideo / RoundVoice在查询期映射为
// 多个基础类别的并集(见 MediaCategoriesForFilter 的调用方)。
type MediaCategory int16
const (
MediaCategoryNone MediaCategory = 0
MediaCategoryPhoto MediaCategory = 1 // media.kind=photo
MediaCategoryVideo MediaCategory = 2 // document 含 video 属性且非 round
MediaCategoryGif MediaCategory = 3 // document 含 animated 属性
MediaCategoryFile MediaCategory = 4 // 通用文档(无 video/audio/animated/sticker 属性)
MediaCategoryMusic MediaCategory = 5 // document 含 audio 属性且 voice=false
MediaCategoryVoice MediaCategory = 6 // document 含 audio 属性且 voice=true
MediaCategoryRoundVideo MediaCategory = 7 // document 含 video 属性且 round_message=true视频消息
MediaCategoryURL MediaCategory = 8 // 文本含 url/text_url/email 实体或 messageMediaWebPage
MediaCategoryPoll MediaCategory = 9 // media.kind=poll
)
// MediaCategoryCounts 是共享媒体索引按基础类别聚合出的精确计数。
type MediaCategoryCounts map[MediaCategory]int
// CountAny 返回给定基础类别并集的计数。当前复合 filter 只由互斥类别组成
// (PhotoVideo=Photo+Video, RoundVoice=Voice+RoundVideo),因此可直接相加。
func (c MediaCategoryCounts) CountAny(categories []MediaCategory) int {
if len(categories) == 0 {
return 0
}
seen := make(map[MediaCategory]struct{}, len(categories))
total := 0
for _, category := range categories {
if category == MediaCategoryNone {
continue
}
if _, ok := seen[category]; ok {
continue
}
seen[category] = struct{}{}
total += c[category]
}
return total
}
// MediaSearchRequest 是共享媒体标签页分页查询的入参messages.search 媒体过滤分支)。
// Categories 是该标签页映射到的基础类别并集PhotoVideo→[Photo,Video]、RoundVoice→[Voice,RoundVideo])。
// 分页对齐历史语义OffsetID 为游标(返回 id 严格小于它、AddOffset 为额外偏移、MaxID/MinID 为闭区间。
type MediaSearchRequest struct {
Categories []MediaCategory
OffsetID int
AddOffset int
Limit int
MaxID int
MinID int
KnownCount int
HasKnownCount bool
}
// ClassifyMediaCategories 返回一条消息所属的全部共享媒体类别(可为空:无媒体且无链接,
// 或贴纸/地理/联系人等不进任何媒体标签页的载荷)。是媒体索引的唯一分类真值,写路径据此
// 维护索引、迁移回填须与其语义一致。
func ClassifyMediaCategories(media *MessageMedia, entities []MessageEntity) []MediaCategory {
cats := make([]MediaCategory, 0, 2)
add := func(category MediaCategory) {
if category == MediaCategoryNone {
return
}
for _, existing := range cats {
if existing == category {
return
}
}
cats = append(cats, category)
}
if media != nil {
switch media.Kind {
case MessageMediaKindPhoto:
add(MediaCategoryPhoto)
case MessageMediaKindDocument:
if c, ok := classifyDocumentCategory(media.Document); ok {
add(c)
}
case MessageMediaKindPoll:
add(MediaCategoryPoll)
case MessageMediaKindWebPage:
add(MediaCategoryURL)
}
}
if hasURLEntity(entities) {
add(MediaCategoryURL)
}
return cats
}
// classifyDocumentCategory 按 TL DocumentAttribute 判定一个文档落入哪个媒体类别。
// 客户端的标签页 UI 以 TL 属性(而非 MIME为准故这里也只看属性。优先级
// sticker不入任何标签页> animated(GIF) > audio(music/voice) > video(video/round) > 通用文件。
func classifyDocumentCategory(doc *Document) (MediaCategory, bool) {
if doc == nil {
return MediaCategoryNone, false
}
var (
hasSticker bool
hasAnimated bool
hasAudio bool
audioVoice bool
hasVideo bool
videoRound bool
)
for _, a := range doc.Attributes {
switch a.Kind {
case DocAttrSticker, DocAttrCustomEmoji:
hasSticker = true
case DocAttrAnimated:
hasAnimated = true
case DocAttrAudio:
hasAudio = true
if a.Voice {
audioVoice = true
}
case DocAttrVideo:
hasVideo = true
if a.RoundMessage {
videoRound = true
}
}
}
switch {
case hasSticker:
return MediaCategoryNone, false // 贴纸/自定义 emoji 不出现在共享媒体
case hasAnimated:
return MediaCategoryGif, true
case hasAudio:
if audioVoice {
return MediaCategoryVoice, true
}
return MediaCategoryMusic, true
case hasVideo:
if videoRound {
return MediaCategoryRoundVideo, true
}
return MediaCategoryVideo, true
default:
return MediaCategoryFile, true
}
}
func hasURLEntity(entities []MessageEntity) bool {
for _, e := range entities {
if e.Type == MessageEntityURL || e.Type == MessageEntityTextURL || e.Type == MessageEntityEmail {
return true
}
}
return false
}