fix for retention logic

This commit is contained in:
onysd 2026-09-03 05:00:42 +03:00
parent 70c0ba44f0
commit e6bfe2d444
35 changed files with 2108 additions and 132 deletions

View file

@ -634,6 +634,10 @@ const (
ChannelActionSuggestedPostApproval ChannelMessageActionType = "suggested_post_approval"
ChannelActionSuggestedPostSuccess ChannelMessageActionType = "suggested_post_success"
ChannelActionSuggestedPostRefund ChannelMessageActionType = "suggested_post_refund"
// ChannelActionCustomText 映射 messageActionCustomActionstock TL 类型):
// 当前唯一用途是把 storage retention 硬回收/主动淘汰清理掉的频道消息就地
// 转成这条通知(见 EditChannelMessageRequest.RetentionPurge
ChannelActionCustomText ChannelMessageActionType = "custom_text"
)
// ChannelMessageAction describes a service action without depending on tg.*.
@ -678,6 +682,8 @@ type ChannelMessageAction struct {
SuggestedPostScheduleDate int
SuggestedPostPrice *SuggestedPostPrice
SuggestedPostPayerInitiated bool
// Text 仅 custom_text 服务消息使用messageActionCustomAction.message
Text string
}
// ChannelMessage is a single stored message in a channel/supergroup.
@ -1967,6 +1973,15 @@ type EditChannelMessageRequest struct {
// 仅当目标当前 media 仍是 ID==ExpectedWebPageID 的 pending 链接预览才替换。
WebPageResolve bool
ExpectedWebPageID int64
// RetentionPurge 置位时为存储 retention 硬回收/主动淘汰的服务端内部编辑:
// 绕过普通的发送者/管理员编辑权限校验,仅由 internal/app/files 的
// retention purge 通知路径设置。见 EditMessageRequest.RetentionPurge。
RetentionPurge bool
// RetentionPurgeAction 与 RetentionPurge 搭配使用:频道消息的服务动作
// 存于独立的 Action 字段(不像私聊消息把服务动作折进 Media.Kind=service
// 所以 retention 通知在频道侧必须整条消息转成一条新的服务消息(清空
// body/media只设置这个 Action而不是像私聊那样替换 Media。
RetentionPurgeAction *ChannelMessageAction
}
// EditChannelMessageResult describes one channel edit update.

View file

@ -591,6 +591,11 @@ const (
// 状态切换与关闭请求。会话级保护不能写入普通消息的 NoForwards 字段。
MessageServiceActionNoForwardsToggle MessageServiceActionKind = "no_forwards_toggle"
MessageServiceActionNoForwardsRequest MessageServiceActionKind = "no_forwards_request"
// MessageServiceActionCustomText 映射 messageActionCustomActionstock TL
// 类型,两端客户端已原生支持,无需任何 client 补丁):服务端把纯文本状态
// 通知当前唯一用途storage retention 硬回收把已清理媒体的消息就地转成
// 这条通知)渲染成居中灰色系统气泡,而不是一次看起来像普通编辑的文本替换。
MessageServiceActionCustomText MessageServiceActionKind = "custom_text"
)
// MessagePhoneCallAction 是 messageActionPhoneCall 的协议中立载荷。
@ -673,6 +678,8 @@ type MessageServiceAction struct {
RequestedPeer *MessageRequestedPeerAction `json:"requested_peer,omitempty"`
ChatThemeEmoticon string `json:"chat_theme_emoticon,omitempty"`
NoForwards *MessageNoForwardsAction `json:"no_forwards,omitempty"`
// Text 承载 MessageServiceActionCustomText 的固定通知文案messageActionCustomAction.message
Text string `json:"text,omitempty"`
}
// MessageMedia 是一条消息媒体载荷的业务表示(落库为消息行上的 JSONB 快照)。

View file

@ -159,6 +159,26 @@ func classifyDocumentCategory(doc *Document) (MediaCategory, bool) {
}
}
// DocumentMediaCategory returns the single document-attribute category
// (Video/Gif/Music/Voice/RoundVideo/File) for a document media payload, used
// to populate the documents.category column alongside the shared media
// index. Unlike ClassifyMediaCategories (which can also add unrelated
// categories such as URL from message entities), this only ever answers "what
// kind of document is this", returning MediaCategoryNone for a non-document
// payload or for sticker/custom-emoji documents (they classify to nothing --
// see classifyDocumentCategory). Zero new classification logic: reuses
// classifyDocumentCategory exactly.
func DocumentMediaCategory(media *MessageMedia) MediaCategory {
if media == nil || media.Kind != MessageMediaKindDocument {
return MediaCategoryNone
}
c, ok := classifyDocumentCategory(media.Document)
if !ok {
return MediaCategoryNone
}
return c
}
func hasURLEntity(entities []MessageEntity) bool {
for _, e := range entities {
if e.Type == MessageEntityURL || e.Type == MessageEntityTextURL || e.Type == MessageEntityEmail {

View file

@ -1,5 +1,7 @@
package domain
import "time"
// MediaKind identifies which table a media_references row points into.
type MediaKind string
@ -45,6 +47,16 @@ type OrphanCandidate struct {
OrphanedAt int64 // unix seconds
}
// EvictionCandidate is one document/photo still owning file_blobs bytes,
// used by the active-eviction sweep (TELESRV_STORAGE_EVICTION_ENABLE) to pick
// the oldest media overall across both tables once total physical storage
// exceeds TELESRV_STORAGE_MAX_TOTAL_BYTES.
type EvictionCandidate struct {
Kind MediaKind
MediaID int64
CreatedAt time.Time
}
// MediaRefTarget identifies one document/photo embedded in a message's media
// snapshot.
type MediaRefTarget struct {

View file

@ -555,6 +555,12 @@ type OutboxReadDateRequest struct {
ID int
}
// RetentionPurgeNoticeText is the fixed notice text used for the
// messageActionCustomAction (MessageServiceActionCustomText) service message
// a storage-retention blob purge (hard mode or active eviction) turns a
// message into once its underlying document/photo bytes are gone.
const RetentionPurgeNoticeText = "This file was deleted by the server's storage retention policy."
// EditMessageRequest 是账号视角下编辑一条已发送私聊消息的命令。
// Media 非 nil 时整体替换消息媒体快照(当前唯一调用方是 live location 续报/停止,
// rpc 层负责限定媒体种类nil 表示纯文本编辑。
@ -589,6 +595,11 @@ type EditMessageRequest struct {
// 否则返回 ErrMessageNotModified消息已删/已改/已解析)。
WebPageResolve bool
ExpectedWebPageID int64
// RetentionPurge 置位时为存储 retention 硬回收/主动淘汰的服务端内部编辑:
// 绕过 authorEdit/viaBotEdit/WebPageResolve 之外的普通作者校验(回收发生
// 在任意账号的历史消息上,不是消息真正作者的操作),仅由
// internal/app/files 的 retention purge 通知路径设置。
RetentionPurge bool
}
// EditedMessageForUser 描述一次编辑对某个 owner 视角造成的影响。