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,270 @@
package memory
import (
"context"
"fmt"
"sort"
"time"
"telesrv/internal/domain"
)
// savedDialogTopsLocked 聚合 self-chat 按 saved_peer 分组的 top message。
// 返回按 top box id 降序。
func (s *MessageStore) savedDialogTopsLocked(userID int64) []domain.Message {
tops := make(map[domain.Peer]domain.Message)
selfPeer := domain.Peer{Type: domain.PeerTypeUser, ID: userID}
for _, msg := range s.m[userID] {
if msg.Peer != selfPeer || msg.SavedPeer.ID == 0 {
continue
}
if cur, ok := tops[msg.SavedPeer]; !ok || msg.ID > cur.ID {
tops[msg.SavedPeer] = msg
}
}
out := make([]domain.Message, 0, len(tops))
for _, msg := range tops {
out = append(out, msg)
}
sort.Slice(out, func(i, j int) bool { return out[i].ID > out[j].ID })
return out
}
func (s *MessageStore) savedPinIndexLocked(userID int64, peer domain.Peer) int {
for i, p := range s.savedPins[userID] {
if p == peer {
return i
}
}
return -1
}
func (s *MessageStore) appendSavedDialogLocked(out *domain.SavedDialogList, msg domain.Message, pinned bool) {
out.Dialogs = append(out.Dialogs, domain.SavedDialog{
Peer: msg.SavedPeer,
TopMessage: msg.ID,
Pinned: pinned,
})
out.Messages = append(out.Messages, cloneMessage(msg))
}
func (s *MessageStore) ListSavedDialogs(_ context.Context, userID int64, filter domain.SavedDialogsFilter) (domain.SavedDialogList, error) {
out := domain.SavedDialogList{}
if userID == 0 {
return out, fmt.Errorf("list saved dialogs: missing user id")
}
limit := filter.Limit
if limit <= 0 || limit > domain.MaxSavedDialogsLimit {
limit = domain.MaxSavedDialogsLimit
}
offsetID := filter.OffsetID
// DrKLO Android 首页发 offset_id = MaxMessageBoxID(int32 max),用 >= 命中首页
// 分支,避免被误判为续页跳过置顶块(与 postgres 对齐)。
firstPage := offsetID <= 0 || offsetID >= domain.MaxMessageBoxID
s.mu.RLock()
defer s.mu.RUnlock()
tops := s.savedDialogTopsLocked(userID)
topByPeer := make(map[domain.Peer]domain.Message, len(tops))
for _, msg := range tops {
topByPeer[msg.SavedPeer] = msg
}
// 首页且不排除置顶:置顶块按 pinned_order 在前。
if firstPage && !filter.ExcludePinned {
for _, peer := range s.savedPins[userID] {
if len(out.Dialogs) >= limit {
break
}
if msg, ok := topByPeer[peer]; ok {
s.appendSavedDialogLocked(&out, msg, true)
}
}
}
// 普通块恒排除置顶(置顶只随首页返回)。
hasMore := false
for _, msg := range tops {
if len(out.Dialogs) >= limit {
if s.savedPinIndexLocked(userID, msg.SavedPeer) < 0 &&
(firstPage || msg.ID < offsetID) {
hasMore = true
}
continue
}
if s.savedPinIndexLocked(userID, msg.SavedPeer) >= 0 {
continue
}
if !firstPage && msg.ID >= offsetID {
continue
}
s.appendSavedDialogLocked(&out, msg, false)
}
total := 0
for _, msg := range tops {
if filter.ExcludePinned && s.savedPinIndexLocked(userID, msg.SavedPeer) >= 0 {
continue
}
total++
}
out.Count = total
out.Full = !hasMore
return out, nil
}
func (s *MessageStore) ListPinnedSavedDialogs(_ context.Context, userID int64) (domain.SavedDialogList, error) {
out := domain.SavedDialogList{Full: true}
if userID == 0 {
return out, fmt.Errorf("list pinned saved dialogs: missing user id")
}
s.mu.RLock()
defer s.mu.RUnlock()
tops := s.savedDialogTopsLocked(userID)
topByPeer := make(map[domain.Peer]domain.Message, len(tops))
for _, msg := range tops {
topByPeer[msg.SavedPeer] = msg
}
for _, peer := range s.savedPins[userID] {
if msg, ok := topByPeer[peer]; ok {
s.appendSavedDialogLocked(&out, msg, true)
}
}
out.Count = len(out.Dialogs)
return out, nil
}
func (s *MessageStore) ListSavedDialogsByPeers(_ context.Context, userID int64, peers []domain.Peer) (domain.SavedDialogList, error) {
out := domain.SavedDialogList{Full: true}
if userID == 0 {
return out, fmt.Errorf("list saved dialogs by peers: missing user id")
}
s.mu.RLock()
defer s.mu.RUnlock()
tops := s.savedDialogTopsLocked(userID)
topByPeer := make(map[domain.Peer]domain.Message, len(tops))
for _, msg := range tops {
topByPeer[msg.SavedPeer] = msg
}
seen := make(map[domain.Peer]struct{}, len(peers))
for _, peer := range peers {
if peer.Type == "" || peer.ID == 0 {
continue
}
if _, ok := seen[peer]; ok {
continue
}
seen[peer] = struct{}{}
if msg, ok := topByPeer[peer]; ok {
s.appendSavedDialogLocked(&out, msg, s.savedPinIndexLocked(userID, peer) >= 0)
}
}
out.Count = len(out.Dialogs)
return out, nil
}
func (s *MessageStore) ToggleSavedDialogPin(_ context.Context, userID int64, peer domain.Peer, pinned bool) (bool, error) {
if userID == 0 || peer.Type == "" || peer.ID == 0 {
return false, fmt.Errorf("toggle saved dialog pin: invalid input")
}
s.mu.Lock()
defer s.mu.Unlock()
idx := s.savedPinIndexLocked(userID, peer)
if !pinned {
if idx < 0 {
return false, nil
}
s.savedPins[userID] = append(s.savedPins[userID][:idx], s.savedPins[userID][idx+1:]...)
return true, nil
}
if idx >= 0 {
return false, nil
}
if len(s.savedPins[userID]) >= domain.MaxPinnedSavedDialogs {
return false, domain.ErrPinnedSavedDialogsTooMuch
}
s.savedPins[userID] = append([]domain.Peer{peer}, s.savedPins[userID]...)
return true, nil
}
func (s *MessageStore) ReorderPinnedSavedDialogs(_ context.Context, userID int64, order []domain.Peer, force bool) error {
if userID == 0 {
return fmt.Errorf("reorder pinned saved dialogs: missing user id")
}
if len(order) > domain.MaxPinnedSavedDialogs {
return domain.ErrPinnedSavedDialogsTooMuch
}
s.mu.Lock()
defer s.mu.Unlock()
next := make([]domain.Peer, 0, len(order))
seen := make(map[domain.Peer]struct{}, len(order))
for _, peer := range order {
if peer.Type == "" || peer.ID == 0 {
continue
}
if _, ok := seen[peer]; ok {
continue
}
seen[peer] = struct{}{}
next = append(next, peer)
}
if !force {
// 非 force:order 之外的既有置顶保持原相对顺序排在后面。
for _, peer := range s.savedPins[userID] {
if _, ok := seen[peer]; !ok {
next = append(next, peer)
}
}
}
s.savedPins[userID] = next
return nil
}
func (s *MessageStore) DeleteSavedHistory(_ context.Context, req domain.DeleteSavedHistoryRequest) (domain.DeleteSavedHistoryResult, error) {
res := domain.DeleteSavedHistoryResult{}
if req.OwnerUserID == 0 || req.SavedPeer.Type == "" || req.SavedPeer.ID == 0 {
return res, fmt.Errorf("delete saved history: invalid input")
}
if req.Date == 0 {
req.Date = int(time.Now().Unix())
}
selfPeer := domain.Peer{Type: domain.PeerTypeUser, ID: req.OwnerUserID}
s.mu.Lock()
defer s.mu.Unlock()
match := func(msg domain.Message) bool {
if msg.Peer != selfPeer || msg.SavedPeer != req.SavedPeer {
return false
}
if req.MaxID > 0 && msg.ID > req.MaxID {
return false
}
if req.MinDate > 0 && msg.Date < req.MinDate {
return false
}
if req.MaxDate > 0 && msg.Date > req.MaxDate {
return false
}
return true
}
deleted, _, more := s.deleteMemoryMessagesLocked(req.OwnerUserID, domain.MaxDeleteHistoryBatch, match)
delRes := s.finishMemoryDeleteLocked(domain.DeleteMessagesResult{OwnerUserID: req.OwnerUserID}, deleted, req.Date, false)
res.More = more
for _, d := range delRes.Deleted {
if d.UserID == req.OwnerUserID {
res.MessageIDs = d.MessageIDs
res.Event = d.Event
}
}
if len(deleted) > 0 && !more {
// 子会话删空时清掉它的置顶行(与 PG 实现同语义)。
alive := false
for _, msg := range s.m[req.OwnerUserID] {
if msg.Peer == selfPeer && msg.SavedPeer == req.SavedPeer {
alive = true
break
}
}
if !alive {
if idx := s.savedPinIndexLocked(req.OwnerUserID, req.SavedPeer); idx >= 0 {
s.savedPins[req.OwnerUserID] = append(s.savedPins[req.OwnerUserID][:idx], s.savedPins[req.OwnerUserID][idx+1:]...)
}
}
}
return res, nil
}