owpengram-server/internal/store/postgres/dialog.go
2026-06-04 01:37:39 +08:00

722 lines
23 KiB
Go

package postgres
import (
"context"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"hash/fnv"
"github.com/jackc/pgx/v5"
"telesrv/internal/domain"
"telesrv/internal/store/postgres/sqlcgen"
)
// DialogStore 用 PostgreSQL 实现 store.DialogStore。
type DialogStore struct {
q *sqlcgen.Queries
}
// NewDialogStore 基于 pgx 连接池(或事务)创建 DialogStore。
func NewDialogStore(db sqlcgen.DBTX) *DialogStore {
return &DialogStore{q: sqlcgen.New(db)}
}
func (s *DialogStore) ListByUser(ctx context.Context, userID int64, filter domain.DialogFilter) (domain.DialogList, error) {
limit := filter.Limit
if limit <= 0 {
limit = 100
}
if limit > 500 {
limit = 500
}
offsetPeerID := int64(0)
if filter.HasOffsetPeer {
offsetPeerID = filter.OffsetPeer.ID
}
folderParams := dialogFolderQueryParams(filter.Folder)
summaryRows, err := s.q.ListDialogSummaryByUser(ctx, sqlcgen.ListDialogSummaryByUserParams{
UserID: userID,
HasFolderID: filter.HasFolderID,
FolderID: pgInt32NonNegative(filter.FolderID),
FolderExcludeArchived: folderParams.excludeArchived,
FolderExcludeRead: folderParams.excludeRead,
FolderExcludePeerTypes: folderParams.excludeTypes,
FolderExcludePeerIds: folderParams.excludeIDs,
FolderIncludePeerTypes: folderParams.includeTypes,
FolderIncludePeerIds: folderParams.includeIDs,
FolderPinnedPeerTypes: folderParams.pinnedTypes,
FolderPinnedPeerIds: folderParams.pinnedIDs,
FolderContacts: folderParams.contacts,
FolderNonContacts: folderParams.nonContacts,
PinnedOnly: filter.PinnedOnly,
ExcludePinned: filter.ExcludePinned,
})
if err != nil {
return domain.DialogList{}, fmt.Errorf("list dialog summary: %w", err)
}
summary := make([]domain.Dialog, 0, len(summaryRows))
for _, row := range summaryRows {
summary = append(summary, domain.Dialog{
Peer: domain.Peer{Type: domain.PeerType(row.PeerType), ID: row.PeerID},
FolderID: int(row.FolderID),
TopMessage: int(row.TopMessageID),
TopMessageDate: int(row.TopMessageDate),
ReadInboxMaxID: int(row.ReadInboxMaxID),
ReadOutboxMaxID: int(row.ReadOutboxMaxID),
UnreadCount: int(row.UnreadCount),
UnreadMentions: int(row.UnreadMentionsCount),
UnreadReactions: int(row.UnreadReactionsCount),
Pinned: row.Pinned,
PinnedOrder: int(row.PinnedOrder),
UnreadMark: row.UnreadMark,
PeerSettingsBarHidden: row.HiddenPeerSettingsBar,
})
}
out := domain.DialogList{
Dialogs: make([]domain.Dialog, 0, limit),
Count: len(summary),
Hash: dialogListHash(summary),
}
if len(summary) == 0 {
return out, nil
}
rows, err := s.q.ListDialogsByUser(ctx, sqlcgen.ListDialogsByUserParams{
UserID: userID,
LimitCount: int32(limit),
HasFolderID: filter.HasFolderID,
FolderID: pgInt32NonNegative(filter.FolderID),
FolderExcludeArchived: folderParams.excludeArchived,
FolderExcludeRead: folderParams.excludeRead,
FolderExcludePeerTypes: folderParams.excludeTypes,
FolderExcludePeerIds: folderParams.excludeIDs,
FolderIncludePeerTypes: folderParams.includeTypes,
FolderIncludePeerIds: folderParams.includeIDs,
FolderPinnedPeerTypes: folderParams.pinnedTypes,
FolderPinnedPeerIds: folderParams.pinnedIDs,
FolderContacts: folderParams.contacts,
FolderNonContacts: folderParams.nonContacts,
PinnedOnly: filter.PinnedOnly,
ExcludePinned: filter.ExcludePinned,
OffsetID: pgInt32NonNegative(filter.OffsetID),
OffsetDate: pgInt32NonNegative(filter.OffsetDate),
HasOffsetPeer: filter.HasOffsetPeer,
OffsetPeerID: offsetPeerID,
})
if err != nil {
return domain.DialogList{}, fmt.Errorf("list dialogs: %w", err)
}
out.Messages = make([]domain.Message, 0, len(rows))
out.Users = make([]domain.User, 0, len(rows))
seenUsers := map[int64]struct{}{}
for _, row := range rows {
dialog := domain.Dialog{
Peer: domain.Peer{
Type: domain.PeerType(row.PeerType),
ID: row.PeerID,
},
FolderID: int(row.FolderID),
TopMessage: int(row.TopMessageID),
TopMessageDate: int(row.TopMessageDate),
ReadInboxMaxID: int(row.ReadInboxMaxID),
ReadOutboxMaxID: int(row.ReadOutboxMaxID),
UnreadCount: int(row.UnreadCount),
UnreadMentions: int(row.UnreadMentionsCount),
UnreadReactions: int(row.UnreadReactionsCount),
Pinned: row.Pinned,
PinnedOrder: int(row.PinnedOrder),
UnreadMark: row.UnreadMark,
PeerSettingsBarHidden: row.HiddenPeerSettingsBar,
}
out.Dialogs = append(out.Dialogs, dialog)
if row.PeerUserID != 0 {
if _, ok := seenUsers[row.PeerUserID]; !ok {
seenUsers[row.PeerUserID] = struct{}{}
out.Users = append(out.Users, domain.User{
ID: row.PeerUserID,
AccessHash: row.PeerAccessHash,
Phone: row.PeerPhone,
FirstName: row.PeerFirstName,
LastName: row.PeerLastName,
Username: row.PeerUsername,
CountryCode: row.PeerCountryCode,
Verified: row.PeerVerified,
Support: row.PeerSupport,
LastSeenAt: int(row.PeerLastSeenAt),
Contact: row.PeerContact,
Mutual: row.PeerMutual,
})
}
}
if row.MessageID != 0 {
entities, err := decodeMessageEntities(row.MessageEntitiesJson)
if err != nil {
return domain.DialogList{}, fmt.Errorf("decode message entities: %w", err)
}
out.Messages = append(out.Messages, domain.Message{
ID: int(row.MessageID),
OwnerUserID: row.UserID,
Peer: dialog.Peer,
From: domain.Peer{Type: domain.PeerTypeUser, ID: row.MessageFromUserID},
Date: int(row.MessageDate),
Out: row.MessageOutgoing,
Body: row.MessageBody,
Entities: entities,
})
}
}
return out, nil
}
func (s *DialogStore) ListByPeers(ctx context.Context, userID int64, peers []domain.Peer) (domain.DialogList, error) {
if len(peers) == 0 {
return domain.DialogList{}, nil
}
peerTypes := make([]string, 0, len(peers))
peerIDs := make([]int64, 0, len(peers))
for _, peer := range peers {
if peer.Type == "" || peer.ID == 0 {
continue
}
peerTypes = append(peerTypes, string(peer.Type))
peerIDs = append(peerIDs, peer.ID)
}
if len(peerTypes) == 0 {
return domain.DialogList{}, nil
}
rows, err := s.q.ListDialogsByPeers(ctx, sqlcgen.ListDialogsByPeersParams{
UserID: userID,
PeerTypes: peerTypes,
PeerIds: peerIDs,
})
if err != nil {
return domain.DialogList{}, fmt.Errorf("list dialogs by peers: %w", err)
}
out := domain.DialogList{
Dialogs: make([]domain.Dialog, 0, len(rows)),
Messages: make([]domain.Message, 0, len(rows)),
Users: make([]domain.User, 0, len(rows)),
}
seenUsers := map[int64]struct{}{}
for _, row := range rows {
dialog := domain.Dialog{
Peer: domain.Peer{Type: domain.PeerType(row.PeerType), ID: row.PeerID},
FolderID: int(row.FolderID),
TopMessage: int(row.TopMessageID),
TopMessageDate: int(row.TopMessageDate),
ReadInboxMaxID: int(row.ReadInboxMaxID),
ReadOutboxMaxID: int(row.ReadOutboxMaxID),
UnreadCount: int(row.UnreadCount),
UnreadMentions: int(row.UnreadMentionsCount),
UnreadReactions: int(row.UnreadReactionsCount),
Pinned: row.Pinned,
PinnedOrder: int(row.PinnedOrder),
UnreadMark: row.UnreadMark,
PeerSettingsBarHidden: row.HiddenPeerSettingsBar,
}
out.Dialogs = append(out.Dialogs, dialog)
if row.PeerUserID != 0 {
if _, ok := seenUsers[row.PeerUserID]; !ok {
seenUsers[row.PeerUserID] = struct{}{}
out.Users = append(out.Users, domain.User{
ID: row.PeerUserID,
AccessHash: row.PeerAccessHash,
Phone: row.PeerPhone,
FirstName: row.PeerFirstName,
LastName: row.PeerLastName,
Username: row.PeerUsername,
CountryCode: row.PeerCountryCode,
Verified: row.PeerVerified,
Support: row.PeerSupport,
LastSeenAt: int(row.PeerLastSeenAt),
Contact: row.PeerContact,
Mutual: row.PeerMutual,
})
}
}
if row.MessageID != 0 {
entities, err := decodeMessageEntities(row.MessageEntitiesJson)
if err != nil {
return domain.DialogList{}, fmt.Errorf("decode message entities: %w", err)
}
out.Messages = append(out.Messages, domain.Message{
ID: int(row.MessageID),
OwnerUserID: row.UserID,
Peer: dialog.Peer,
From: domain.Peer{Type: domain.PeerTypeUser, ID: row.MessageFromUserID},
Date: int(row.MessageDate),
Out: row.MessageOutgoing,
Body: row.MessageBody,
Entities: entities,
})
}
}
out.Count = len(out.Dialogs)
out.Hash = dialogListHash(out.Dialogs)
return out, nil
}
func (s *DialogStore) Upsert(ctx context.Context, userID int64, dialog domain.Dialog) error {
if err := s.q.UpsertDialog(ctx, sqlcgen.UpsertDialogParams{
UserID: userID,
PeerType: string(dialog.Peer.Type),
PeerID: dialog.Peer.ID,
TopMessageID: int32(dialog.TopMessage),
TopMessageDate: int32(dialog.TopMessageDate),
ReadInboxMaxID: int32(dialog.ReadInboxMaxID),
ReadOutboxMaxID: int32(dialog.ReadOutboxMaxID),
UnreadCount: int32(dialog.UnreadCount),
UnreadMentionsCount: int32(dialog.UnreadMentions),
UnreadReactionsCount: int32(dialog.UnreadReactions),
Pinned: dialog.Pinned,
UnreadMark: dialog.UnreadMark,
}); err != nil {
return fmt.Errorf("upsert dialog: %w", err)
}
return nil
}
func (s *DialogStore) SaveDraft(ctx context.Context, userID int64, draft domain.DialogDraft) error {
data, err := json.Marshal(draft)
if err != nil {
return fmt.Errorf("marshal dialog draft: %w", err)
}
if err := s.q.UpsertDialogDraft(ctx, sqlcgen.UpsertDialogDraftParams{
UserID: userID,
PeerType: string(draft.Peer.Type),
PeerID: draft.Peer.ID,
TopMessageID: int32(draft.TopMessageID),
Date: int32(draft.Date),
DraftJson: data,
}); err != nil {
return fmt.Errorf("upsert dialog draft: %w", err)
}
return nil
}
func (s *DialogStore) DeleteDraft(ctx context.Context, userID int64, peer domain.Peer, topMessageID int) (bool, error) {
changed, err := s.q.DeleteDialogDraft(ctx, sqlcgen.DeleteDialogDraftParams{
UserID: userID,
PeerType: string(peer.Type),
PeerID: peer.ID,
TopMessageID: int32(topMessageID),
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return false, nil
}
return false, fmt.Errorf("delete dialog draft: %w", err)
}
return changed, nil
}
func (s *DialogStore) ListDrafts(ctx context.Context, userID int64, limit int) ([]domain.DialogDraft, error) {
rows, err := s.q.ListDialogDrafts(ctx, sqlcgen.ListDialogDraftsParams{
UserID: userID,
LimitCount: int32(clampDialogDraftLimit(limit)),
})
if err != nil {
return nil, fmt.Errorf("list dialog drafts: %w", err)
}
return decodeDialogDrafts(rows)
}
func (s *DialogStore) ClearDrafts(ctx context.Context, userID int64, limit int) ([]domain.DialogDraft, error) {
rows, err := s.q.ClearDialogDrafts(ctx, sqlcgen.ClearDialogDraftsParams{
UserID: userID,
LimitCount: int32(clampDialogDraftLimit(limit)),
})
if err != nil {
return nil, fmt.Errorf("clear dialog drafts: %w", err)
}
return decodeDialogDrafts(rows)
}
func (s *DialogStore) MarkRead(ctx context.Context, userID int64, peer domain.Peer, maxID int) (domain.ReadHistoryResult, error) {
row, err := s.q.MarkDialogRead(ctx, sqlcgen.MarkDialogReadParams{
UserID: userID,
PeerType: string(peer.Type),
PeerID: peer.ID,
MaxID: pgInt32NonNegative(maxID),
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.ReadHistoryResult{OwnerUserID: userID, Peer: peer, MaxID: maxID}, nil
}
return domain.ReadHistoryResult{}, fmt.Errorf("mark dialog read: %w", err)
}
return domain.ReadHistoryResult{
OwnerUserID: row.UserID,
Peer: domain.Peer{Type: domain.PeerType(row.PeerType), ID: row.PeerID},
MaxID: int(row.ReadInboxMaxID),
StillUnreadCount: int(row.UnreadCount),
Changed: row.Changed,
}, nil
}
func (s *DialogStore) SetPinned(ctx context.Context, userID int64, peer domain.Peer, pinned bool) (bool, error) {
changed, err := s.q.SetDialogPinned(ctx, sqlcgen.SetDialogPinnedParams{
UserID: userID,
PeerType: string(peer.Type),
PeerID: peer.ID,
Pinned: pinned,
})
if err != nil {
return false, fmt.Errorf("set dialog pinned: %w", err)
}
return changed, nil
}
func (s *DialogStore) ReorderPinned(ctx context.Context, userID int64, order []domain.Peer, force bool) error {
peerTypes, peerIDs := peerArrays(order)
if force {
if err := s.q.ClearPinnedDialogsNotInOrder(ctx, sqlcgen.ClearPinnedDialogsNotInOrderParams{
UserID: userID,
PeerTypes: peerTypes,
PeerIds: peerIDs,
}); err != nil {
return fmt.Errorf("clear pinned dialogs not in order: %w", err)
}
}
if len(peerTypes) == 0 {
return nil
}
if err := s.q.ReorderPinnedDialogs(ctx, sqlcgen.ReorderPinnedDialogsParams{
UserID: userID,
PeerTypes: peerTypes,
PeerIds: peerIDs,
}); err != nil {
return fmt.Errorf("reorder pinned dialogs: %w", err)
}
return nil
}
func (s *DialogStore) SetUnreadMark(ctx context.Context, userID int64, peer domain.Peer, unread bool) (bool, error) {
changed, err := s.q.SetDialogUnreadMark(ctx, sqlcgen.SetDialogUnreadMarkParams{
UserID: userID,
PeerType: string(peer.Type),
PeerID: peer.ID,
Unread: unread,
})
if err != nil {
return false, fmt.Errorf("set dialog unread mark: %w", err)
}
return changed, nil
}
func (s *DialogStore) ListUnreadMarked(ctx context.Context, userID int64) ([]domain.Peer, error) {
rows, err := s.q.ListDialogUnreadMarks(ctx, userID)
if err != nil {
return nil, fmt.Errorf("list dialog unread marks: %w", err)
}
out := make([]domain.Peer, 0, len(rows))
for _, row := range rows {
out = append(out, domain.Peer{Type: domain.PeerType(row.PeerType), ID: row.PeerID})
}
return out, nil
}
func (s *DialogStore) SetPeerSettingsBarHidden(ctx context.Context, userID int64, peer domain.Peer) (bool, error) {
changed, err := s.q.SetPeerSettingsBarHidden(ctx, sqlcgen.SetPeerSettingsBarHiddenParams{
UserID: userID,
PeerType: string(peer.Type),
PeerID: peer.ID,
})
if err != nil {
return false, fmt.Errorf("set peer settings bar hidden: %w", err)
}
return changed, nil
}
func (s *DialogStore) PeerSettingsBarHidden(ctx context.Context, userID int64, peer domain.Peer) (bool, error) {
hidden, err := s.q.GetPeerSettingsBarHidden(ctx, sqlcgen.GetPeerSettingsBarHiddenParams{
UserID: userID,
PeerType: string(peer.Type),
PeerID: peer.ID,
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return false, nil
}
return false, fmt.Errorf("get peer settings bar hidden: %w", err)
}
return hidden, nil
}
func (s *DialogStore) ListFolders(ctx context.Context, userID int64) (domain.DialogFolderList, error) {
rows, err := s.q.ListDialogFolders(ctx, userID)
if err != nil {
return domain.DialogFolderList{}, fmt.Errorf("list dialog folders: %w", err)
}
tagsEnabled := false
if enabled, err := s.q.GetDialogFolderTags(ctx, userID); err == nil {
tagsEnabled = enabled
} else if !errors.Is(err, pgx.ErrNoRows) {
return domain.DialogFolderList{}, fmt.Errorf("get dialog folder tags: %w", err)
}
out := domain.DialogFolderList{
TagsEnabled: tagsEnabled,
Folders: make([]domain.DialogFolder, 0, len(rows)),
}
for _, row := range rows {
folder, err := decodeDialogFolder(row.FilterJson)
if err != nil {
return domain.DialogFolderList{}, fmt.Errorf("decode dialog folder %d: %w", row.FilterID, err)
}
folder.ID = int(row.FilterID)
folder.IsChatlist = row.IsChatlist
out.Folders = append(out.Folders, folder)
}
return out, nil
}
func (s *DialogStore) GetFolder(ctx context.Context, userID int64, folderID int) (domain.DialogFolder, bool, error) {
row, err := s.q.GetDialogFolder(ctx, sqlcgen.GetDialogFolderParams{
UserID: userID,
FilterID: pgInt32NonNegative(folderID),
})
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return domain.DialogFolder{}, false, nil
}
return domain.DialogFolder{}, false, fmt.Errorf("get dialog folder: %w", err)
}
folder, err := decodeDialogFolder(row.FilterJson)
if err != nil {
return domain.DialogFolder{}, false, fmt.Errorf("decode dialog folder: %w", err)
}
folder.ID = int(row.FilterID)
folder.IsChatlist = row.IsChatlist
return folder, true, nil
}
func (s *DialogStore) UpsertFolder(ctx context.Context, userID int64, folder domain.DialogFolder) error {
data, err := json.Marshal(folder)
if err != nil {
return fmt.Errorf("marshal dialog folder: %w", err)
}
if err := s.q.UpsertDialogFolder(ctx, sqlcgen.UpsertDialogFolderParams{
UserID: userID,
FilterID: pgInt32NonNegative(folder.ID),
IsChatlist: folder.IsChatlist,
FilterJson: data,
}); err != nil {
return fmt.Errorf("upsert dialog folder: %w", err)
}
return nil
}
func (s *DialogStore) DeleteFolder(ctx context.Context, userID int64, folderID int) error {
if err := s.q.DeleteDialogFolder(ctx, sqlcgen.DeleteDialogFolderParams{
UserID: userID,
FilterID: pgInt32NonNegative(folderID),
}); err != nil {
return fmt.Errorf("delete dialog folder: %w", err)
}
return nil
}
func (s *DialogStore) ReorderFolders(ctx context.Context, userID int64, order []int) error {
if err := s.q.ReorderDialogFolders(ctx, sqlcgen.ReorderDialogFoldersParams{
UserID: userID,
FilterIds: int32s(order),
}); err != nil {
return fmt.Errorf("reorder dialog folders: %w", err)
}
return nil
}
func (s *DialogStore) SetFolderTagsEnabled(ctx context.Context, userID int64, enabled bool) error {
if err := s.q.SetDialogFolderTags(ctx, sqlcgen.SetDialogFolderTagsParams{
UserID: userID,
TagsEnabled: enabled,
}); err != nil {
return fmt.Errorf("set dialog folder tags: %w", err)
}
return nil
}
func (s *DialogStore) EditPeerFolders(ctx context.Context, userID int64, peers []domain.FolderPeerUpdate) error {
peerTypes := make([]string, 0, len(peers))
peerIDs := make([]int64, 0, len(peers))
folderIDs := make([]int32, 0, len(peers))
seen := make(map[domain.Peer]struct{}, len(peers))
for _, item := range peers {
if item.Peer.Type == "" || item.Peer.ID == 0 {
continue
}
if _, ok := seen[item.Peer]; ok {
continue
}
seen[item.Peer] = struct{}{}
peerTypes = append(peerTypes, string(item.Peer.Type))
peerIDs = append(peerIDs, item.Peer.ID)
folderIDs = append(folderIDs, pgInt32NonNegative(item.FolderID))
}
if len(peerTypes) == 0 {
return nil
}
if err := s.q.EditDialogPeerFolders(ctx, sqlcgen.EditDialogPeerFoldersParams{
UserID: userID,
PeerTypes: peerTypes,
PeerIds: peerIDs,
FolderIds: folderIDs,
}); err != nil {
return fmt.Errorf("edit dialog peer folders: %w", err)
}
return nil
}
type dialogFolderParams struct {
contacts bool
nonContacts bool
excludeArchived bool
excludeRead bool
includeTypes []string
includeIDs []int64
pinnedTypes []string
pinnedIDs []int64
excludeTypes []string
excludeIDs []int64
}
func dialogFolderQueryParams(folder *domain.DialogFolder) dialogFolderParams {
if folder == nil {
return dialogFolderParams{}
}
includeTypes, includeIDs := folderPeerArrays(folder.IncludePeers)
pinnedTypes, pinnedIDs := folderPeerArrays(folder.PinnedPeers)
excludeTypes, excludeIDs := folderPeerArrays(folder.ExcludePeers)
return dialogFolderParams{
contacts: folder.Contacts,
nonContacts: folder.NonContacts,
excludeArchived: folder.ExcludeArchived,
excludeRead: folder.ExcludeRead,
includeTypes: includeTypes,
includeIDs: includeIDs,
pinnedTypes: pinnedTypes,
pinnedIDs: pinnedIDs,
excludeTypes: excludeTypes,
excludeIDs: excludeIDs,
}
}
func folderPeerArrays(peers []domain.DialogFolderPeer) ([]string, []int64) {
peerTypes := make([]string, 0, len(peers))
peerIDs := make([]int64, 0, len(peers))
seen := make(map[domain.Peer]struct{}, len(peers))
for _, item := range peers {
peer := item.Peer
if peer.Type == "" || peer.ID == 0 {
continue
}
if _, ok := seen[peer]; ok {
continue
}
seen[peer] = struct{}{}
peerTypes = append(peerTypes, string(peer.Type))
peerIDs = append(peerIDs, peer.ID)
}
return peerTypes, peerIDs
}
func decodeDialogFolder(data string) (domain.DialogFolder, error) {
if data == "" {
return domain.DialogFolder{}, nil
}
var folder domain.DialogFolder
if err := json.Unmarshal([]byte(data), &folder); err != nil {
return domain.DialogFolder{}, err
}
return folder, nil
}
func decodeDialogDrafts(rows []string) ([]domain.DialogDraft, error) {
out := make([]domain.DialogDraft, 0, len(rows))
for _, row := range rows {
draft, err := decodeDialogDraft(row)
if err != nil {
return nil, err
}
out = append(out, draft)
}
return out, nil
}
func decodeDialogDraft(data string) (domain.DialogDraft, error) {
if data == "" {
return domain.DialogDraft{}, nil
}
var draft domain.DialogDraft
if err := json.Unmarshal([]byte(data), &draft); err != nil {
return domain.DialogDraft{}, fmt.Errorf("decode dialog draft: %w", err)
}
if draft.Entities == nil {
draft.Entities = []domain.MessageEntity{}
}
return draft, nil
}
func clampDialogDraftLimit(limit int) int {
if limit <= 0 || limit > domain.MaxDialogDraftsPerUser {
return domain.MaxDialogDraftsPerUser
}
return limit
}
func peerArrays(peers []domain.Peer) ([]string, []int64) {
peerTypes := make([]string, 0, len(peers))
peerIDs := make([]int64, 0, len(peers))
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{}{}
peerTypes = append(peerTypes, string(peer.Type))
peerIDs = append(peerIDs, peer.ID)
}
return peerTypes, peerIDs
}
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[:])
}
return int64(h.Sum64())
}