owpengram-server/internal/store/memory/message_helpers.go
2026-09-09 02:49:30 +03:00

286 lines
7.6 KiB
Go
Raw Permalink 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 memory
import (
"encoding/binary"
"hash/fnv"
"reflect"
"sort"
"telesrv/internal/domain"
)
func (s *MessageStore) deleteMemoryMessagesLocked(userID int64, limit int, match func(domain.Message) bool) ([]deletedMemoryMessage, map[int64]struct{}, bool) {
messages := s.m[userID]
kept := messages[:0]
deleted := make([]deletedMemoryMessage, 0)
revokeUIDs := make(map[int64]struct{})
more := false
for _, msg := range messages {
if match(msg) {
if limit > 0 && len(deleted) >= limit {
kept = append(kept, msg)
more = true
continue
}
deleted = append(deleted, deletedMemoryMessage{
userID: userID,
peer: msg.Peer,
id: msg.ID,
privateMessageID: msg.UID,
messageSenderID: msg.From.ID,
randomID: msg.RandomID,
})
if msg.UID != 0 {
revokeUIDs[msg.UID] = struct{}{}
}
continue
}
kept = append(kept, msg)
}
s.m[userID] = kept
return deleted, revokeUIDs, more
}
func (s *MessageStore) deleteMemoryMessagesByUIDLocked(uids map[int64]struct{}, excludeUserID int64) []deletedMemoryMessage {
if len(uids) == 0 {
return nil
}
deleted := make([]deletedMemoryMessage, 0)
for userID, messages := range s.m {
if userID == excludeUserID {
continue
}
kept := messages[:0]
for _, msg := range messages {
if _, ok := uids[msg.UID]; ok {
deleted = append(deleted, deletedMemoryMessage{
userID: userID,
peer: msg.Peer,
id: msg.ID,
privateMessageID: msg.UID,
messageSenderID: msg.From.ID,
randomID: msg.RandomID,
})
continue
}
kept = append(kept, msg)
}
s.m[userID] = kept
}
return deleted
}
func normalizeMemoryMessageIDs(ids []int) []int {
if len(ids) == 0 {
return nil
}
out := make([]int, 0, len(ids))
seen := make(map[int]struct{}, len(ids))
for _, id := range ids {
if id <= 0 || id > domain.MaxMessageBoxID {
continue
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
out = append(out, id)
}
sort.Ints(out)
return out
}
func cloneMessage(msg domain.Message) domain.Message {
msg.Entities = append([]domain.MessageEntity(nil), msg.Entities...)
msg.Media = cloneRequestedPeerMedia(msg.Media)
msg.ReplyTo = cloneMessageReply(msg.ReplyTo)
msg.Forward = cloneMessageForward(msg.Forward)
msg.Reactions = cloneChannelMessageReactionsPtr(msg.Reactions)
msg.ReplyMarkup = cloneReplyMarkup(msg.ReplyMarkup)
msg.RichMessage = cloneRichMessage(msg.RichMessage)
return msg
}
// cloneRequestedPeerMedia isolates the immutable disclosure snapshot carried by
// messageActionRequestedPeer. Other media payloads retain their established
// copy behavior; this helper only deep-copies the newly mutable peer/photo slices.
func cloneRequestedPeerMedia(media *domain.MessageMedia) *domain.MessageMedia {
if media == nil {
return nil
}
clone := *media
if media.LivePhotoVideo != nil {
video := *media.LivePhotoVideo
video.FileReference = append([]byte(nil), media.LivePhotoVideo.FileReference...)
video.Attributes = append([]domain.DocumentAttribute(nil), media.LivePhotoVideo.Attributes...)
clone.LivePhotoVideo = &video
}
if media.ServiceAction == nil {
return &clone
}
action := *media.ServiceAction
if media.ServiceAction.NoForwards != nil {
noForwards := *media.ServiceAction.NoForwards
action.NoForwards = &noForwards
}
if media.ServiceAction.RequestedPeer == nil {
clone.ServiceAction = &action
return &clone
}
requested := *media.ServiceAction.RequestedPeer
requested.Peers = append([]domain.Peer(nil), requested.Peers...)
requested.Details = append([]domain.MessageRequestedPeerDetails(nil), requested.Details...)
for i := range requested.Details {
requested.Details[i].Photo = domain.ClonePhotoPtr(requested.Details[i].Photo)
}
action.RequestedPeer = &requested
clone.ServiceAction = &action
return &clone
}
// cloneReplyMarkup 深拷 reply markup 快照:与 postgres 每盒独立 decode 对齐
// (双 store 行为一致),避免发送方/接收方两行共享底层 rows/Data 切片。
func cloneReplyMarkup(m *domain.MessageReplyMarkup) *domain.MessageReplyMarkup {
if m == nil {
return nil
}
clone := *m
if m.Inline != nil {
clone.Inline = make([][]domain.MarkupButton, len(m.Inline))
for i, row := range m.Inline {
cloneRow := make([]domain.MarkupButton, len(row))
for j, btn := range row {
cloneRow[j] = btn
cloneRow[j].Data = append([]byte(nil), btn.Data...)
}
clone.Inline[i] = cloneRow
}
}
if m.Keyboard != nil {
clone.Keyboard = make([][]domain.MarkupButton, len(m.Keyboard))
for i, row := range m.Keyboard {
clone.Keyboard[i] = append([]domain.MarkupButton(nil), row...)
}
}
return &clone
}
// cloneRichMessage 深拷 Layer 228 富文本快照:复制不透明 blocks、Bot API 投影与内嵌媒体切片,
// 避免发送方/接收方两行共享底层切片(与 postgres 每盒独立 decode 对齐)。
func cloneRichMessage(m *domain.MessageRichMessage) *domain.MessageRichMessage {
if m == nil {
return nil
}
clone := *m
clone.Blocks = append([]byte(nil), m.Blocks...)
clone.Photos = append([]domain.Photo(nil), m.Photos...)
clone.Documents = append([]domain.Document(nil), m.Documents...)
clone.BotAPIProjection = append([]byte(nil), m.BotAPIProjection...)
return &clone
}
func richMessagesEqual(a, b *domain.MessageRichMessage) bool {
if a.IsZero() && b.IsZero() {
return true
}
return reflect.DeepEqual(a, b)
}
func cloneMessageReply(reply *domain.MessageReply) *domain.MessageReply {
return domain.CloneMessageReply(reply)
}
func cloneMessageForward(forward *domain.MessageForward) *domain.MessageForward {
if forward == nil {
return nil
}
clone := *forward
return &clone
}
func newMessageEvent(msg domain.Message) domain.UpdateEvent {
if msg.ID == 0 {
return domain.UpdateEvent{}
}
return domain.UpdateEvent{
UserID: msg.OwnerUserID,
Type: domain.UpdateEventNewMessage,
Pts: msg.Pts,
PtsCount: 1,
Date: msg.Date,
Message: cloneMessage(msg),
}
}
func editMessageEvent(msg domain.Message) domain.UpdateEvent {
if msg.ID == 0 {
return domain.UpdateEvent{}
}
return domain.UpdateEvent{
UserID: msg.OwnerUserID,
Type: domain.UpdateEventEditMessage,
Pts: msg.Pts,
PtsCount: 1,
Date: msg.EditDate,
Message: cloneMessage(msg),
}
}
// webPageEvent 是链接预览就地替换事件Date 取消息发送时间,不引入 edit_date
func webPageEvent(msg domain.Message) domain.UpdateEvent {
if msg.ID == 0 {
return domain.UpdateEvent{}
}
return domain.UpdateEvent{
UserID: msg.OwnerUserID,
Type: domain.UpdateEventWebPage,
Pts: msg.Pts,
PtsCount: 1,
Date: msg.Date,
Message: cloneMessage(msg),
}
}
func equalMessageEntities(a, b []domain.MessageEntity) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func hasUser(users []domain.User, id int64) bool {
for _, u := range users {
if u.ID == id {
return true
}
}
return false
}
func messageListHash(messages []domain.Message) int64 {
if len(messages) == 0 {
return 0
}
h := fnv.New64a()
var buf [16]byte
for _, msg := range messages {
binary.LittleEndian.PutUint32(buf[:4], uint32(msg.ID))
binary.LittleEndian.PutUint32(buf[4:8], uint32(msg.Date))
binary.LittleEndian.PutUint64(buf[8:16], uint64(msg.From.ID))
_, _ = h.Write(buf[:])
writeMessageReactionsHash(h, msg.Reactions)
}
return int64(h.Sum64())
}
func cloneMessages(messages []domain.Message) []domain.Message {
out := append([]domain.Message(nil), messages...)
for i := range out {
out[i] = cloneMessage(out[i])
}
return out
}