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,924 @@
package memory
import (
"context"
"encoding/binary"
"hash/fnv"
"sort"
"sync"
"telesrv/internal/domain"
)
// DialogStore 是 store.DialogStore 的内存实现。
type DialogStore struct {
mu sync.RWMutex
m map[int64]domain.DialogList
drafts map[int64]map[dialogDraftKey]domain.DialogDraft
folders map[int64]map[int]domain.DialogFolder
folderOrder map[int64][]int
folderTags map[int64]bool
// archivePinned 记录 archive folder 行置顶状态;无记录时官方默认 true。
archivePinned map[int64]bool
}
type dialogDraftKey struct {
peerType domain.PeerType
peerID int64
topMessageID int
}
// NewDialogStore 创建内存 DialogStore。
func NewDialogStore() *DialogStore {
return &DialogStore{
m: make(map[int64]domain.DialogList),
drafts: make(map[int64]map[dialogDraftKey]domain.DialogDraft),
folders: make(map[int64]map[int]domain.DialogFolder),
folderOrder: make(map[int64][]int),
folderTags: make(map[int64]bool),
archivePinned: make(map[int64]bool),
}
}
func (s *DialogStore) ListByUser(_ context.Context, userID int64, filter domain.DialogFilter) (domain.DialogList, error) {
s.mu.RLock()
list := s.m[userID]
s.mu.RUnlock()
list.Dialogs = cloneDialogs(list.Dialogs)
list.Messages = cloneMessages(list.Messages)
list.Users = append([]domain.User(nil), list.Users...)
return filterDialogList(list, filter), nil
}
func (s *DialogStore) ListByPeers(_ context.Context, userID int64, peers []domain.Peer) (domain.DialogList, error) {
s.mu.RLock()
list := s.m[userID]
s.mu.RUnlock()
list.Dialogs = cloneDialogs(list.Dialogs)
list.Messages = cloneMessages(list.Messages)
list.Users = append([]domain.User(nil), list.Users...)
byPeer := make(map[domain.Peer]domain.Dialog, len(list.Dialogs))
for _, dialog := range list.Dialogs {
byPeer[dialog.Peer] = dialog
}
out := domain.DialogList{
Dialogs: make([]domain.Dialog, 0, len(peers)),
Users: make([]domain.User, 0, len(peers)),
}
seenPeers := make(map[domain.Peer]struct{}, len(peers))
seenUsers := map[int64]struct{}{}
for _, peer := range peers {
if _, ok := seenPeers[peer]; ok {
continue
}
seenPeers[peer] = struct{}{}
dialog := byPeer[peer]
if dialog.Peer.ID == 0 {
dialog.Peer = peer
}
out.Dialogs = append(out.Dialogs, dialog)
if peer.Type == domain.PeerTypeUser {
if user, ok := findDialogUser(list.Users, peer.ID); ok {
appendDialogUser(&out, seenUsers, user)
} else if u, ok := domain.SystemUserByID(peer.ID); ok {
appendDialogUser(&out, seenUsers, u)
}
}
}
out.Messages = keepDialogMessages(list.Messages, out.Dialogs)
out.Count = len(out.Dialogs)
out.Hash = dialogListHash(out.Dialogs)
return out, nil
}
// SaveList 保存一份用户会话列表,供测试和本地替身使用。
func (s *DialogStore) SaveList(_ context.Context, userID int64, list domain.DialogList) error {
list.Dialogs = cloneDialogs(list.Dialogs)
list.Messages = cloneMessages(list.Messages)
list.Users = append([]domain.User(nil), list.Users...)
s.mu.Lock()
s.m[userID] = list
s.mu.Unlock()
return nil
}
func (s *DialogStore) Upsert(_ context.Context, userID int64, dialog domain.Dialog) error {
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
for i, existing := range list.Dialogs {
if existing.Peer == dialog.Peer {
if dialog.FolderID == domain.DialogMainFolderID && existing.FolderID != domain.DialogMainFolderID {
dialog.FolderID = existing.FolderID
}
list.Dialogs[i] = dialog
s.m[userID] = list
return nil
}
}
list.Dialogs = append(list.Dialogs, dialog)
s.m[userID] = list
return nil
}
func (s *DialogStore) UpsertInbox(_ context.Context, userID int64, dialog domain.Dialog) error {
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
for i, existing := range list.Dialogs {
if existing.Peer != dialog.Peer {
continue
}
if dialog.TopMessage >= existing.TopMessage {
existing.TopMessage = dialog.TopMessage
existing.TopMessageDate = dialog.TopMessageDate
}
existing.UnreadCount = countInboxUnread(list, existing.Peer, existing.ReadInboxMaxID, existing.TopMessage)
list.Dialogs[i] = existing
s.m[userID] = list
return nil
}
dialog.UnreadCount = countInboxUnread(list, dialog.Peer, dialog.ReadInboxMaxID, dialog.TopMessage)
if dialog.UnreadCount == 0 {
dialog.UnreadCount = 1
}
list.Dialogs = append(list.Dialogs, dialog)
s.m[userID] = list
return nil
}
func countInboxUnread(list domain.DialogList, peer domain.Peer, readMax, topMessage int) int {
unread := 0
for _, msg := range list.Messages {
if msg.Peer == peer && !msg.Out && msg.ID > readMax && (topMessage <= 0 || msg.ID <= topMessage) {
unread++
}
}
return unread
}
func (s *DialogStore) SaveDraft(_ context.Context, userID int64, draft domain.DialogDraft) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.drafts[userID] == nil {
s.drafts[userID] = make(map[dialogDraftKey]domain.DialogDraft)
}
s.drafts[userID][draftKey(draft.Peer, draft.TopMessageID)] = cloneDialogDraft(draft)
return nil
}
func (s *DialogStore) GetDraft(_ context.Context, userID int64, peer domain.Peer, topMessageID int) (domain.DialogDraft, bool, error) {
s.mu.RLock()
defer s.mu.RUnlock()
draft, ok := s.drafts[userID][draftKey(peer, topMessageID)]
if !ok {
return domain.DialogDraft{}, false, nil
}
return cloneDialogDraft(draft), true, nil
}
func (s *DialogStore) DeleteDraft(_ context.Context, userID int64, peer domain.Peer, topMessageID int) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
items := s.drafts[userID]
if len(items) == 0 {
return false, nil
}
key := draftKey(peer, topMessageID)
if _, ok := items[key]; !ok {
return false, nil
}
delete(items, key)
return true, nil
}
func (s *DialogStore) ListDrafts(_ context.Context, userID int64, limit int) ([]domain.DialogDraft, error) {
s.mu.RLock()
items := s.drafts[userID]
out := make([]domain.DialogDraft, 0, len(items))
for _, draft := range items {
out = append(out, cloneDialogDraft(draft))
}
s.mu.RUnlock()
sortDialogDrafts(out)
if limit <= 0 || limit > domain.MaxDialogDraftsPerUser {
limit = domain.MaxDialogDraftsPerUser
}
if len(out) > limit {
out = out[:limit]
}
return out, nil
}
func (s *DialogStore) ClearDrafts(_ context.Context, userID int64, limit int) ([]domain.DialogDraft, error) {
if limit <= 0 || limit > domain.MaxDialogDraftsPerUser {
limit = domain.MaxDialogDraftsPerUser
}
s.mu.Lock()
defer s.mu.Unlock()
items := s.drafts[userID]
if len(items) == 0 {
return nil, nil
}
out := make([]domain.DialogDraft, 0, len(items))
for _, draft := range items {
out = append(out, cloneDialogDraft(draft))
}
sortDialogDrafts(out)
if len(out) > limit {
out = out[:limit]
}
for _, draft := range out {
delete(items, draftKey(draft.Peer, draft.TopMessageID))
}
return out, nil
}
func (s *DialogStore) MarkRead(_ context.Context, userID int64, peer domain.Peer, maxID int) (domain.ReadHistoryResult, error) {
result := domain.ReadHistoryResult{OwnerUserID: userID, Peer: peer, MaxID: maxID}
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
for i, dialog := range list.Dialogs {
if dialog.Peer != peer {
continue
}
readMax := maxID
if readMax <= 0 {
readMax = dialog.TopMessage
}
if readMax > dialog.TopMessage {
readMax = dialog.TopMessage
}
result.MaxID = readMax
result.Changed = dialog.UnreadCount > 0 || readMax > dialog.ReadInboxMaxID
if readMax > dialog.ReadInboxMaxID {
dialog.ReadInboxMaxID = readMax
}
if readMax >= dialog.TopMessage {
dialog.UnreadCount = 0
dialog.UnreadMentions = 0
dialog.UnreadReactions = 0
}
dialog.UnreadMark = false
result.StillUnreadCount = dialog.UnreadCount
list.Dialogs[i] = dialog
s.m[userID] = list
return result, nil
}
return result, nil
}
func (s *DialogStore) SetPinned(_ context.Context, userID int64, peer domain.Peer, pinned bool) (bool, int, error) {
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
targetFolderID := domain.DialogMainFolderID
for _, dialog := range list.Dialogs {
if dialog.Peer == peer {
targetFolderID = dialog.FolderID
break
}
}
// order 仅在目标会话所在 folder 内分配memory 双 store 互不可见,
// 与 postgres 跨表统一 order 空间相比此处只看私聊表reorder 会统一重排)。
nextOrder := 1
for _, dialog := range list.Dialogs {
if dialog.Pinned && dialog.FolderID == targetFolderID && dialog.PinnedOrder >= nextOrder {
nextOrder = dialog.PinnedOrder + 1
}
}
for i := range list.Dialogs {
if list.Dialogs[i].Peer != peer {
continue
}
list.Dialogs[i].Pinned = pinned
if pinned {
if list.Dialogs[i].PinnedOrder == 0 {
list.Dialogs[i].PinnedOrder = nextOrder
}
} else {
list.Dialogs[i].PinnedOrder = 0
}
s.m[userID] = list
return true, targetFolderID, nil
}
return false, 0, nil
}
func (s *DialogStore) ReorderPinned(_ context.Context, userID int64, folderID int, order []domain.Peer, force bool) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
changed := false
positions := make(map[domain.Peer]int, len(order))
for i, peer := range order {
if peer.Type == "" || peer.ID == 0 {
continue
}
if _, ok := positions[peer]; ok {
continue
}
positions[peer] = len(order) - i
}
for i := range list.Dialogs {
if list.Dialogs[i].FolderID != folderID {
continue
}
pos, ok := positions[list.Dialogs[i].Peer]
if ok {
if !list.Dialogs[i].Pinned || list.Dialogs[i].PinnedOrder != pos {
changed = true
}
list.Dialogs[i].Pinned = true
list.Dialogs[i].PinnedOrder = pos
continue
}
if force && list.Dialogs[i].Pinned {
changed = true
list.Dialogs[i].Pinned = false
list.Dialogs[i].PinnedOrder = 0
}
}
s.m[userID] = list
return changed, nil
}
func (s *DialogStore) SetUnreadMark(_ context.Context, userID int64, peer domain.Peer, unread bool) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
for i := range list.Dialogs {
if list.Dialogs[i].Peer != peer {
continue
}
// 值守卫对齐 postgres重复标记同值返回 changed=false避免幽灵事件。
if list.Dialogs[i].UnreadMark == unread {
return false, nil
}
list.Dialogs[i].UnreadMark = unread
s.m[userID] = list
return true, nil
}
return false, nil
}
func (s *DialogStore) ListUnreadMarked(_ context.Context, userID int64) ([]domain.Peer, error) {
s.mu.RLock()
list := s.m[userID]
s.mu.RUnlock()
out := make([]domain.Peer, 0, len(list.Dialogs))
for _, dialog := range list.Dialogs {
if dialog.UnreadMark {
out = append(out, dialog.Peer)
}
}
return out, nil
}
func (s *DialogStore) SetChatTheme(_ context.Context, userID int64, peer domain.Peer, emoticon string) (bool, error) {
if userID == 0 || peer.Type == "" || peer.ID == 0 {
return false, nil
}
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
for i := range list.Dialogs {
if list.Dialogs[i].Peer != peer {
continue
}
if list.Dialogs[i].ThemeEmoticon == emoticon {
return false, nil
}
list.Dialogs[i].ThemeEmoticon = emoticon
s.m[userID] = list
return true, nil
}
if emoticon == "" {
return false, nil
}
list.Dialogs = append(list.Dialogs, domain.Dialog{Peer: peer, ThemeEmoticon: emoticon})
s.m[userID] = list
return true, nil
}
func (s *DialogStore) SetPeerSettingsBarHidden(_ context.Context, userID int64, peer domain.Peer) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
for i := range list.Dialogs {
if list.Dialogs[i].Peer != peer {
continue
}
list.Dialogs[i].PeerSettingsBarHidden = true
s.m[userID] = list
return true, nil
}
return false, nil
}
func (s *DialogStore) PeerSettingsBarHidden(_ context.Context, userID int64, peer domain.Peer) (bool, error) {
s.mu.RLock()
list := s.m[userID]
s.mu.RUnlock()
for _, dialog := range list.Dialogs {
if dialog.Peer == peer {
return dialog.PeerSettingsBarHidden, nil
}
}
return false, nil
}
func (s *DialogStore) ListFolders(_ context.Context, userID int64) (domain.DialogFolderList, error) {
s.mu.RLock()
defer s.mu.RUnlock()
byID := s.folders[userID]
order := append([]int(nil), s.folderOrder[userID]...)
seen := make(map[int]struct{}, len(byID))
out := domain.DialogFolderList{
TagsEnabled: s.folderTags[userID],
Folders: make([]domain.DialogFolder, 0, len(byID)),
}
for _, id := range order {
folder, ok := byID[id]
if !ok {
continue
}
seen[id] = struct{}{}
out.Folders = append(out.Folders, cloneDialogFolder(folder))
}
remaining := make([]int, 0, len(byID))
for id := range byID {
if _, ok := seen[id]; !ok {
remaining = append(remaining, id)
}
}
sort.Ints(remaining)
for _, id := range remaining {
out.Folders = append(out.Folders, cloneDialogFolder(byID[id]))
}
return out, nil
}
func (s *DialogStore) GetFolder(_ context.Context, userID int64, folderID int) (domain.DialogFolder, bool, error) {
s.mu.RLock()
defer s.mu.RUnlock()
folder, ok := s.folders[userID][folderID]
if !ok {
return domain.DialogFolder{}, false, nil
}
return cloneDialogFolder(folder), true, nil
}
func (s *DialogStore) UpsertFolder(_ context.Context, userID int64, folder domain.DialogFolder) error {
s.mu.Lock()
defer s.mu.Unlock()
if s.folders[userID] == nil {
s.folders[userID] = make(map[int]domain.DialogFolder)
}
s.folders[userID][folder.ID] = cloneDialogFolder(folder)
if !containsInt(s.folderOrder[userID], folder.ID) {
s.folderOrder[userID] = append(s.folderOrder[userID], folder.ID)
}
return nil
}
func (s *DialogStore) DeleteFolder(_ context.Context, userID int64, folderID int) error {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.folders[userID], folderID)
s.folderOrder[userID] = removeInt(s.folderOrder[userID], folderID)
return nil
}
func (s *DialogStore) ReorderFolders(_ context.Context, userID int64, order []int) error {
s.mu.Lock()
defer s.mu.Unlock()
byID := s.folders[userID]
seen := make(map[int]struct{}, len(order))
next := make([]int, 0, len(byID))
for _, id := range order {
if id < domain.DialogCustomFolderMinID {
continue
}
if _, ok := byID[id]; !ok {
continue
}
if _, ok := seen[id]; ok {
continue
}
seen[id] = struct{}{}
next = append(next, id)
}
remaining := make([]int, 0, len(byID))
for id := range byID {
if _, ok := seen[id]; !ok {
remaining = append(remaining, id)
}
}
sort.Ints(remaining)
next = append(next, remaining...)
s.folderOrder[userID] = next
return nil
}
func (s *DialogStore) SetFolderTagsEnabled(_ context.Context, userID int64, enabled bool) error {
s.mu.Lock()
s.folderTags[userID] = enabled
s.mu.Unlock()
return nil
}
func (s *DialogStore) EditPeerFolders(_ context.Context, userID int64, peers []domain.FolderPeerUpdate) error {
s.mu.Lock()
defer s.mu.Unlock()
list := s.m[userID]
updates := make(map[domain.Peer]int, len(peers))
for _, item := range peers {
if item.Peer.Type == "" || item.Peer.ID == 0 {
continue
}
if item.FolderID != domain.DialogMainFolderID && item.FolderID != domain.DialogArchiveFolderID {
continue
}
updates[item.Peer] = item.FolderID
}
for i := range list.Dialogs {
folderID, ok := updates[list.Dialogs[i].Peer]
if !ok {
continue
}
// 换 folder 时清 pinnedTDesktop 在归档/还原时本地无条件 unpin
// 服务端保留旧 pin 会在下次 getDialogs 时把状态漂移回来。
if list.Dialogs[i].FolderID != folderID {
list.Dialogs[i].Pinned = false
list.Dialogs[i].PinnedOrder = 0
}
list.Dialogs[i].FolderID = folderID
}
s.m[userID] = list
return nil
}
func (s *DialogStore) SetArchivePinned(_ context.Context, userID int64, pinned bool) (bool, error) {
s.mu.Lock()
defer s.mu.Unlock()
current, ok := s.archivePinned[userID]
if !ok {
current = true
}
s.archivePinned[userID] = pinned
return current != pinned, nil
}
func (s *DialogStore) ArchivePinned(_ context.Context, userID int64) (bool, error) {
s.mu.RLock()
defer s.mu.RUnlock()
if pinned, ok := s.archivePinned[userID]; ok {
return pinned, nil
}
return true, nil
}
func (s *DialogStore) CountArchiveUnread(_ context.Context, userID int64) (int, int, error) {
s.mu.RLock()
list := s.m[userID]
s.mu.RUnlock()
peers, messages := 0, 0
for _, dialog := range list.Dialogs {
if dialog.FolderID != domain.DialogArchiveFolderID {
continue
}
if dialog.UnreadCount > 0 || dialog.UnreadMark {
peers++
}
messages += dialog.UnreadCount
}
return peers, messages, nil
}
func upsertMemoryDialog(list domain.DialogList, dialog domain.Dialog) domain.DialogList {
for i := range list.Dialogs {
if list.Dialogs[i].Peer == dialog.Peer {
if dialog.ReadInboxMaxID == 0 {
dialog.ReadInboxMaxID = list.Dialogs[i].ReadInboxMaxID
}
if dialog.ReadOutboxMaxID == 0 {
dialog.ReadOutboxMaxID = list.Dialogs[i].ReadOutboxMaxID
}
if dialog.FolderID == domain.DialogMainFolderID && list.Dialogs[i].FolderID != domain.DialogMainFolderID {
dialog.FolderID = list.Dialogs[i].FolderID
}
if dialog.UnreadCount == 0 {
dialog.UnreadCount = list.Dialogs[i].UnreadCount
}
if dialog.UnreadMentions == 0 {
dialog.UnreadMentions = list.Dialogs[i].UnreadMentions
}
if dialog.UnreadReactions == 0 {
dialog.UnreadReactions = list.Dialogs[i].UnreadReactions
}
if !dialog.UnreadMark {
dialog.UnreadMark = list.Dialogs[i].UnreadMark
}
// 与 postgres 的 ON CONFLICT 局部更新对齐:消息路径的 upsert
// 不得抹掉既有置顶/设置类状态。
if !dialog.Pinned && list.Dialogs[i].Pinned {
dialog.Pinned = list.Dialogs[i].Pinned
dialog.PinnedOrder = list.Dialogs[i].PinnedOrder
}
if dialog.TTLPeriod == 0 {
dialog.TTLPeriod = list.Dialogs[i].TTLPeriod
}
if dialog.ThemeEmoticon == "" {
dialog.ThemeEmoticon = list.Dialogs[i].ThemeEmoticon
}
if !dialog.HasScheduled {
dialog.HasScheduled = list.Dialogs[i].HasScheduled
}
if !dialog.PeerSettingsBarHidden {
dialog.PeerSettingsBarHidden = list.Dialogs[i].PeerSettingsBarHidden
}
if !dialog.ViewForumAsMessages {
dialog.ViewForumAsMessages = list.Dialogs[i].ViewForumAsMessages
}
if dialog.Draft == nil {
dialog.Draft = list.Dialogs[i].Draft
}
list.Dialogs[i] = dialog
return list
}
}
list.Dialogs = append(list.Dialogs, dialog)
return list
}
func filterDialogList(list domain.DialogList, filter domain.DialogFilter) domain.DialogList {
sort.SliceStable(list.Dialogs, func(i, j int) bool {
return dialogLess(list.Dialogs[i], list.Dialogs[j])
})
base := make([]domain.Dialog, 0, len(list.Dialogs))
for _, d := range list.Dialogs {
if !dialogMatchesFolder(d, list.Users, filter) {
continue
}
if filter.PinnedOnly && !d.Pinned {
continue
}
if filter.ExcludePinned && d.Pinned {
continue
}
base = append(base, d)
}
list.Count = len(base)
list.Hash = dialogListHash(base)
limit := filter.Limit
if limit <= 0 {
limit = 100
}
if limit > 500 {
limit = 500
}
page := make([]domain.Dialog, 0, len(base))
for _, d := range base {
if !afterDialogOffset(d, filter) {
continue
}
page = append(page, d)
if len(page) >= limit {
break
}
}
list.Dialogs = page
list.Messages = keepDialogMessages(list.Messages, page)
return list
}
func dialogMatchesFolder(d domain.Dialog, users []domain.User, filter domain.DialogFilter) bool {
if !filter.HasFolderID {
// 不带 folder_id 视为主列表folder 0归档对话只以 dialogFolder
// 聚合条目出现DrKLO Android 主列表请求不设 flag。
return d.FolderID == domain.DialogMainFolderID
}
if filter.FolderID < domain.DialogCustomFolderMinID {
return d.FolderID == filter.FolderID
}
if filter.Folder == nil {
return false
}
folder := filter.Folder
if folder.ExcludeArchived && d.FolderID == domain.DialogArchiveFolderID {
return false
}
if folder.ExcludeRead && d.UnreadCount == 0 && !d.UnreadMark {
return false
}
if hasFolderPeer(folder.ExcludePeers, d.Peer) {
return false
}
if hasFolderPeer(folder.IncludePeers, d.Peer) || hasFolderPeer(folder.PinnedPeers, d.Peer) {
return true
}
if d.Peer.Type == domain.PeerTypeUser {
user, ok := findDialogUser(users, d.Peer.ID)
if ok && user.Contact && folder.Contacts {
return true
}
if (!ok || !user.Contact) && folder.NonContacts {
return true
}
}
return false
}
func hasFolderPeer(peers []domain.DialogFolderPeer, peer domain.Peer) bool {
for _, item := range peers {
if item.Peer == peer {
return true
}
}
return false
}
func dialogLess(a, b domain.Dialog) bool {
if a.Pinned != b.Pinned {
return a.Pinned && !b.Pinned
}
if a.Pinned && b.Pinned && a.PinnedOrder != b.PinnedOrder {
if a.PinnedOrder == 0 {
return false
}
if b.PinnedOrder == 0 {
return true
}
return a.PinnedOrder > b.PinnedOrder
}
if a.TopMessageDate != b.TopMessageDate {
return a.TopMessageDate > b.TopMessageDate
}
if a.TopMessage != b.TopMessage {
return a.TopMessage > b.TopMessage
}
return a.Peer.ID > b.Peer.ID
}
func afterDialogOffset(d domain.Dialog, filter domain.DialogFilter) bool {
if filter.OffsetDate <= 0 && filter.OffsetID <= 0 {
return true
}
if filter.OffsetDate > 0 {
if d.TopMessageDate != filter.OffsetDate {
return d.TopMessageDate < filter.OffsetDate
}
if filter.OffsetID <= 0 {
return false
}
if d.TopMessage != filter.OffsetID {
return d.TopMessage < filter.OffsetID
}
if filter.HasOffsetPeer {
return d.Peer.ID < filter.OffsetPeer.ID
}
return false
}
return d.TopMessage < filter.OffsetID
}
func keepDialogMessages(messages []domain.Message, dialogs []domain.Dialog) []domain.Message {
want := make(map[int]struct{}, len(dialogs))
for _, d := range dialogs {
if d.TopMessage != 0 {
want[d.TopMessage] = struct{}{}
}
}
out := make([]domain.Message, 0, len(want))
for _, msg := range messages {
if _, ok := want[msg.ID]; ok {
out = append(out, msg)
}
}
return out
}
func findDialogUser(users []domain.User, id int64) (domain.User, bool) {
for _, user := range users {
if user.ID == id {
return user, true
}
}
return domain.User{}, false
}
func appendDialogUser(list *domain.DialogList, seen map[int64]struct{}, user domain.User) {
if user.ID == 0 {
return
}
if _, ok := seen[user.ID]; ok {
return
}
seen[user.ID] = struct{}{}
list.Users = append(list.Users, user)
}
func cloneDialogs(dialogs []domain.Dialog) []domain.Dialog {
out := append([]domain.Dialog(nil), dialogs...)
for i := range out {
if out[i].Draft != nil {
draft := cloneDialogDraft(*out[i].Draft)
out[i].Draft = &draft
}
}
return out
}
func cloneDialogDraft(draft domain.DialogDraft) domain.DialogDraft {
draft.Entities = append([]domain.MessageEntity(nil), draft.Entities...)
draft.ReplyTo = cloneMessageReply(draft.ReplyTo)
if draft.WebPage != nil {
webpage := *draft.WebPage
draft.WebPage = &webpage
}
return draft
}
func sortDialogDrafts(drafts []domain.DialogDraft) {
sort.SliceStable(drafts, func(i, j int) bool {
if drafts[i].Date != drafts[j].Date {
return drafts[i].Date > drafts[j].Date
}
if drafts[i].Peer.Type != drafts[j].Peer.Type {
return drafts[i].Peer.Type < drafts[j].Peer.Type
}
if drafts[i].Peer.ID != drafts[j].Peer.ID {
return drafts[i].Peer.ID > drafts[j].Peer.ID
}
return drafts[i].TopMessageID > drafts[j].TopMessageID
})
}
func cloneDialogFolder(folder domain.DialogFolder) domain.DialogFolder {
folder.TitleEntities = append([]domain.MessageEntity(nil), folder.TitleEntities...)
folder.PinnedPeers = append([]domain.DialogFolderPeer(nil), folder.PinnedPeers...)
folder.IncludePeers = append([]domain.DialogFolderPeer(nil), folder.IncludePeers...)
folder.ExcludePeers = append([]domain.DialogFolderPeer(nil), folder.ExcludePeers...)
return folder
}
func containsInt(items []int, value int) bool {
for _, item := range items {
if item == value {
return true
}
}
return false
}
func removeInt(items []int, value int) []int {
out := items[:0]
for _, item := range items {
if item != value {
out = append(out, item)
}
}
return out
}
func dialogListHash(dialogs []domain.Dialog) int64 {
if len(dialogs) == 0 {
return 0
}
h := fnv.New64a()
var buf [47]byte
for _, d := range dialogs {
binary.LittleEndian.PutUint64(buf[:8], uint64(d.Peer.ID))
binary.LittleEndian.PutUint32(buf[8:12], uint32(d.FolderID))
binary.LittleEndian.PutUint32(buf[12:16], uint32(d.TopMessage))
binary.LittleEndian.PutUint32(buf[16:20], uint32(d.TopMessageDate))
binary.LittleEndian.PutUint32(buf[20:24], uint32(d.ReadInboxMaxID))
binary.LittleEndian.PutUint32(buf[24:28], uint32(d.ReadOutboxMaxID))
binary.LittleEndian.PutUint32(buf[28:32], uint32(d.UnreadCount))
binary.LittleEndian.PutUint32(buf[32:36], uint32(d.UnreadMentions))
binary.LittleEndian.PutUint32(buf[36:40], uint32(d.UnreadReactions))
if d.Pinned {
buf[40] = 1
} else {
buf[40] = 0
}
binary.LittleEndian.PutUint32(buf[41:45], uint32(d.PinnedOrder))
if d.UnreadMark {
buf[45] = 1
} else {
buf[45] = 0
}
if d.PeerSettingsBarHidden {
buf[46] = 1
} else {
buf[46] = 0
}
_, _ = h.Write(buf[:])
_, _ = h.Write([]byte(d.ThemeEmoticon))
}
return int64(h.Sum64())
}