chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
157
internal/domain/media_category.go
Normal file
157
internal/domain/media_category.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
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
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue