chore: refresh gramsrv public release

This commit is contained in:
A 2026-06-30 14:37:43 +08:00
parent 75cebe8dbf
commit 70b6820474
1274 changed files with 378751 additions and 59919 deletions

View file

@ -0,0 +1,423 @@
package memory
import (
"context"
"sort"
"strings"
"telesrv/internal/domain"
"time"
)
func (s *MessageStore) GetByIDs(_ context.Context, userID int64, ids []int) (domain.MessageList, error) {
if userID == 0 || len(ids) == 0 {
return domain.MessageList{}, nil
}
s.mu.RLock()
byID := make(map[int]domain.Message, len(s.m[userID]))
for _, msg := range s.m[userID] {
item := cloneMessage(msg)
reactions := s.privateMessageReactionsForMessageLocked(item)
if len(reactions.Results) > 0 || len(reactions.Recent) > 0 {
item.Reactions = cloneChannelMessageReactionsPtr(&reactions)
}
byID[msg.ID] = item
}
s.mu.RUnlock()
out := domain.MessageList{Messages: make([]domain.Message, 0, len(ids))}
for _, id := range ids {
if msg, ok := byID[id]; ok {
out.Messages = append(out.Messages, msg)
}
}
s.enrichPrivateMessagePolls(out.Messages, int(time.Now().Unix()))
out.Users = usersForMessages(out.Messages)
out.Hash = messageListHash(out.Messages)
return out, nil
}
func (s *MessageStore) ListByUser(_ context.Context, userID int64, filter domain.MessageFilter) (domain.MessageList, error) {
s.mu.RLock()
messages := cloneMessages(s.m[userID])
for i := range messages {
reactions := s.privateMessageReactionsForMessageLocked(messages[i])
if len(reactions.Results) > 0 || len(reactions.Recent) > 0 {
messages[i].Reactions = cloneChannelMessageReactionsPtr(&reactions)
}
}
s.mu.RUnlock()
s.enrichPrivateMessagePolls(messages, int(time.Now().Unix()))
return filterMessageList(messages, filter), nil
}
func (s *MessageStore) ReadHistory(_ context.Context, req domain.ReadHistoryRequest) (domain.ReadHistoryResult, error) {
res := domain.ReadHistoryResult{OwnerUserID: req.OwnerUserID, Peer: req.Peer, MaxID: req.MaxID}
if req.OwnerUserID == 0 || req.Peer.ID == 0 {
return res, nil
}
if req.Date == 0 {
req.Date = int(time.Now().Unix())
}
s.mu.Lock()
defer s.mu.Unlock()
if s.dialogs == nil {
return res, nil
}
s.dialogs.mu.Lock()
defer s.dialogs.mu.Unlock()
list := s.dialogs.m[req.OwnerUserID]
for i, dialog := range list.Dialogs {
if dialog.Peer != req.Peer {
continue
}
readMax := req.MaxID
if readMax <= 0 {
readMax = dialog.TopMessage
}
if readMax > dialog.TopMessage {
readMax = dialog.TopMessage
}
oldRead := dialog.ReadInboxMaxID
res.MaxID = readMax
advancesRead := readMax > oldRead
if !advancesRead {
if dialog.UnreadCount > 0 {
unread := 0
for _, msg := range s.m[req.OwnerUserID] {
if msg.Peer == req.Peer && !msg.Out && msg.ID > oldRead {
unread++
}
}
dialog.UnreadCount = unread
dialog.UnreadMentions = 0
// readHistory 不清 reaction 角标(与 PG 一致;reaction 未读由
// readReactions/readMessageContents 单独清,否则角标数与 getUnreadReactions
// 跳转列表对不上)。
dialog.UnreadMark = false
res.MaxID = dialog.ReadInboxMaxID
res.StillUnreadCount = unread
list.Dialogs[i] = dialog
s.dialogs.m[req.OwnerUserID] = list
}
return res, nil
}
res.Changed = true
var latestIncoming domain.Message
unread := 0
for _, msg := range s.m[req.OwnerUserID] {
if msg.Peer != req.Peer || msg.Out {
continue
}
if msg.ID > readMax {
unread++
continue
}
if msg.ID > oldRead && msg.ID > latestIncoming.ID {
latestIncoming = msg
}
}
if readMax > dialog.ReadInboxMaxID {
dialog.ReadInboxMaxID = readMax
}
dialog.UnreadCount = unread
dialog.UnreadMentions = 0
// readHistory 不清 reaction 角标(与 PG 一致,见上)。
dialog.UnreadMark = false
res.StillUnreadCount = unread
pts := s.nextPtsLocked(req.OwnerUserID)
res.InboxEvent = domain.UpdateEvent{
UserID: req.OwnerUserID,
Type: domain.UpdateEventReadHistoryInbox,
Pts: pts,
PtsCount: 1,
Date: req.Date,
Peer: req.Peer,
MaxID: readMax,
StillUnreadCount: unread,
}
list.Dialogs[i] = dialog
s.dialogs.m[req.OwnerUserID] = list
if latestIncoming.ID != 0 && latestIncoming.From.ID != 0 && latestIncoming.From.ID != req.OwnerUserID {
senderUserID := latestIncoming.From.ID
senderBoxID := 0
for _, msg := range s.m[senderUserID] {
if msg.UID == latestIncoming.UID && msg.Out {
senderBoxID = msg.ID
break
}
}
if senderBoxID > 0 {
senderList := s.dialogs.m[senderUserID]
for j, senderDialog := range senderList.Dialogs {
if senderDialog.Peer != (domain.Peer{Type: domain.PeerTypeUser, ID: req.OwnerUserID}) {
continue
}
if senderBoxID <= senderDialog.ReadOutboxMaxID {
break
}
oldOutbox := senderDialog.ReadOutboxMaxID
senderDialog.ReadOutboxMaxID = senderBoxID
senderList.Dialogs[j] = senderDialog
s.dialogs.m[senderUserID] = senderList
for _, msg := range s.m[senderUserID] {
if msg.Peer == (domain.Peer{Type: domain.PeerTypeUser, ID: req.OwnerUserID}) && msg.Out && msg.ID > oldOutbox && msg.ID <= senderBoxID {
s.readOutboxDates[readOutboxDateKey{ownerUserID: senderUserID, peerID: req.OwnerUserID, msgID: msg.ID}] = req.Date
}
}
outPts := s.nextPtsLocked(senderUserID)
res.OutboxChanged = true
res.OutboxUserID = senderUserID
res.OutboxEvent = domain.UpdateEvent{
UserID: senderUserID,
Type: domain.UpdateEventReadHistoryOutbox,
Pts: outPts,
PtsCount: 1,
Date: req.Date,
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: req.OwnerUserID},
MaxID: senderBoxID,
}
break
}
}
}
return res, nil
}
return res, nil
}
func (s *MessageStore) DeleteHistory(_ context.Context, req domain.DeleteHistoryRequest) (domain.DeleteMessagesResult, error) {
res := domain.DeleteMessagesResult{OwnerUserID: req.OwnerUserID}
if req.OwnerUserID == 0 || req.Peer.ID == 0 {
return res, nil
}
if req.Date == 0 {
req.Date = int(time.Now().Unix())
}
s.mu.Lock()
defer s.mu.Unlock()
inDateRange := func(msg domain.Message) bool {
if req.MinDate > 0 && msg.Date < req.MinDate {
return false
}
if req.MaxDate > 0 && msg.Date > req.MaxDate {
return false
}
return true
}
deleted, revokeUIDs, more := s.deleteMemoryMessagesLocked(req.OwnerUserID, domain.MaxDeleteHistoryBatch, func(msg domain.Message) bool {
return msg.Peer == req.Peer && (req.MaxID <= 0 || msg.ID <= req.MaxID) && inDateRange(msg)
})
if req.Revoke {
if len(revokeUIDs) > 0 {
deleted = append(deleted, s.deleteMemoryMessagesByUIDLocked(revokeUIDs, req.OwnerUserID)...)
}
// 与 PG 同语义:全量/按日期的双向清史直扫对端残余,我方早已
// 单向删除的消息不能在对端残留。
if req.MaxID <= 0 && req.Peer.ID != req.OwnerUserID {
peerDeleted, _, peerMore := s.deleteMemoryMessagesLocked(req.Peer.ID, domain.MaxDeleteHistoryBatch, func(msg domain.Message) bool {
return msg.Peer == (domain.Peer{Type: domain.PeerTypeUser, ID: req.OwnerUserID}) && inDateRange(msg)
})
deleted = append(deleted, peerDeleted...)
more = more || peerMore
}
}
res = s.finishMemoryDeleteLocked(res, deleted, req.Date, req.JustClear)
if more {
res.Offset = 1
}
return res, nil
}
func filterMessageList(messages []domain.Message, filter domain.MessageFilter) domain.MessageList {
filter.AddOffset = domain.ClampMessageHistoryAddOffset(filter.AddOffset)
sort.SliceStable(messages, func(i, j int) bool {
return messageLess(messages[i], messages[j])
})
query := strings.ToLower(filter.Query)
base := make([]domain.Message, 0, len(messages))
for _, msg := range messages {
if filter.HasPeer && msg.Peer != filter.Peer {
continue
}
if query != "" && !strings.Contains(strings.ToLower(msg.Body), query) {
continue
}
if filter.MaxID > 0 && msg.ID >= filter.MaxID {
continue
}
if filter.MinID > 0 && msg.ID <= filter.MinID {
continue
}
if filter.PinnedOnly && !msg.Pinned {
continue
}
if filter.MusicOnly && !msg.Media.IsMusic() {
continue
}
if filter.SavedPeer.ID != 0 && msg.SavedPeer != filter.SavedPeer {
continue
}
base = append(base, msg)
}
limit := filter.Limit
if limit <= 0 {
limit = 100
}
if limit > 500 {
limit = 500
}
page := pageMessageHistory(base, filter, limit)
return domain.MessageList{
Messages: page,
Users: usersForMessages(page),
Count: len(base),
Hash: messageListHash(base),
}
}
func pageMessageHistory(base []domain.Message, filter domain.MessageFilter, limit int) []domain.Message {
if limit <= 0 || len(base) == 0 {
return nil
}
switch messageHistoryLoadType(filter.AddOffset, limit) {
case messageHistoryLoadForward:
return cloneMessages(forwardMessageHistory(base, filter, limit))
case messageHistoryLoadAround:
forwardLimit := -filter.AddOffset
if forwardLimit > limit {
forwardLimit = limit
}
backwardLimit := limit + filter.AddOffset
if backwardLimit < 0 {
backwardLimit = 0
}
page := make([]domain.Message, 0, limit)
page = append(page, forwardMessageHistory(base, filter, forwardLimit)...)
page = append(page, backwardMessageHistory(base, filter, backwardLimit, true)...)
sort.SliceStable(page, func(i, j int) bool {
return messageLess(page[i], page[j])
})
return cloneMessages(page)
default:
start := filter.AddOffset
if start < 0 {
start = 0
}
candidates := backwardMessageHistory(base, filter, limit+start, false)
if start >= len(candidates) {
return nil
}
return cloneMessages(candidates[start:])
}
}
type messageHistoryLoad int
const (
messageHistoryLoadBackward messageHistoryLoad = iota
messageHistoryLoadForward
messageHistoryLoadAround
)
func messageHistoryLoadType(addOffset, limit int) messageHistoryLoad {
if addOffset >= 0 {
return messageHistoryLoadBackward
}
if addOffset+limit > 0 {
return messageHistoryLoadAround
}
return messageHistoryLoadForward
}
func backwardMessageHistory(base []domain.Message, filter domain.MessageFilter, limit int, includeOffset bool) []domain.Message {
if limit <= 0 {
return nil
}
out := make([]domain.Message, 0, limit)
for _, msg := range base {
if !messageBeforeHistoryOffset(msg, filter, includeOffset) {
continue
}
out = append(out, msg)
if len(out) == limit {
break
}
}
return out
}
func forwardMessageHistory(base []domain.Message, filter domain.MessageFilter, limit int) []domain.Message {
if limit <= 0 {
return nil
}
out := make([]domain.Message, 0, limit)
for i := len(base) - 1; i >= 0; i-- {
msg := base[i]
if !messageAfterHistoryOffset(msg, filter) {
continue
}
out = append(out, msg)
if len(out) == limit {
break
}
}
sort.SliceStable(out, func(i, j int) bool {
return messageLess(out[i], out[j])
})
return out
}
func messageBeforeHistoryOffset(msg domain.Message, filter domain.MessageFilter, includeOffset bool) bool {
if filter.OffsetDate > 0 {
if includeOffset {
return msg.Date <= filter.OffsetDate
}
return msg.Date < filter.OffsetDate
}
if filter.OffsetID <= 0 {
return true
}
if includeOffset {
return msg.ID <= filter.OffsetID
}
return msg.ID < filter.OffsetID
}
func messageAfterHistoryOffset(msg domain.Message, filter domain.MessageFilter) bool {
if filter.OffsetDate > 0 {
return msg.Date >= filter.OffsetDate
}
if filter.OffsetID <= 0 {
return false
}
return msg.ID > filter.OffsetID
}
func messageLess(a, b domain.Message) bool {
if a.Date != b.Date {
return a.Date > b.Date
}
return a.ID > b.ID
}
func usersForMessages(messages []domain.Message) []domain.User {
seen := map[int64]struct{}{}
users := make([]domain.User, 0, 1)
for _, msg := range messages {
for _, peer := range []domain.Peer{msg.Peer, msg.From} {
if peer.Type != domain.PeerTypeUser {
continue
}
if _, ok := seen[peer.ID]; ok {
continue
}
seen[peer.ID] = struct{}{}
if u, ok := domain.SystemUserByID(peer.ID); ok {
users = append(users, u)
}
}
}
return users
}