523 lines
16 KiB
Go
523 lines
16 KiB
Go
package memory
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"sync"
|
|
"telesrv/internal/domain"
|
|
)
|
|
|
|
// PasswordStore 是 store.PasswordStore 的内存实现。
|
|
type PasswordStore struct {
|
|
mu sync.RWMutex
|
|
m map[int64]domain.PasswordSettings
|
|
reactions map[int64]domain.AccountReactionSettings
|
|
accountSettings map[int64]domain.AccountSettings
|
|
notifySettings map[notifySettingsKey]domain.PeerNotifySettings
|
|
stickerCollections map[stickerCollectionKey][]domain.StickerCollectionItem
|
|
userStickerSets map[int64]map[int64]domain.UserStickerSet
|
|
savedMusic map[int64][]domain.Document
|
|
businessProfiles map[int64]domain.BusinessProfile
|
|
businessChatLinks map[string]domain.BusinessChatLink
|
|
businessChatLinkSlugs map[int64][]string
|
|
quickReplies map[int64]map[int]domain.QuickReply
|
|
quickReplyByShortcut map[int64]map[string]int
|
|
quickReplyMessages map[int64]map[int]map[int]domain.QuickReplyMessage
|
|
businessDeliveries map[businessAutomationDeliveryKey]domain.BusinessAutomationDelivery
|
|
connectedBusinessBots map[int64]domain.ConnectedBusinessBot
|
|
connectedBusinessBotPeerStates map[connectedBusinessBotPeerKey]domain.ConnectedBusinessBotPeerState
|
|
nextQuickReplyID map[int64]int
|
|
nextQuickReplyMessageID map[int64]int
|
|
}
|
|
|
|
type businessAutomationDeliveryKey struct {
|
|
ownerUserID int64
|
|
peerUserID int64
|
|
kind domain.BusinessAutomationKind
|
|
triggerMessageID int
|
|
}
|
|
|
|
type connectedBusinessBotPeerKey struct {
|
|
ownerUserID int64
|
|
peerUserID int64
|
|
}
|
|
|
|
// NewPasswordStore 创建内存 PasswordStore。
|
|
func NewPasswordStore() *PasswordStore {
|
|
return &PasswordStore{
|
|
m: make(map[int64]domain.PasswordSettings),
|
|
reactions: make(map[int64]domain.AccountReactionSettings),
|
|
accountSettings: make(map[int64]domain.AccountSettings),
|
|
notifySettings: make(map[notifySettingsKey]domain.PeerNotifySettings),
|
|
stickerCollections: make(map[stickerCollectionKey][]domain.StickerCollectionItem),
|
|
userStickerSets: make(map[int64]map[int64]domain.UserStickerSet),
|
|
savedMusic: make(map[int64][]domain.Document),
|
|
businessProfiles: make(map[int64]domain.BusinessProfile),
|
|
businessChatLinks: make(map[string]domain.BusinessChatLink),
|
|
businessChatLinkSlugs: make(map[int64][]string),
|
|
quickReplies: make(map[int64]map[int]domain.QuickReply),
|
|
quickReplyByShortcut: make(map[int64]map[string]int),
|
|
quickReplyMessages: make(map[int64]map[int]map[int]domain.QuickReplyMessage),
|
|
businessDeliveries: make(map[businessAutomationDeliveryKey]domain.BusinessAutomationDelivery),
|
|
connectedBusinessBots: make(map[int64]domain.ConnectedBusinessBot),
|
|
connectedBusinessBotPeerStates: make(map[connectedBusinessBotPeerKey]domain.ConnectedBusinessBotPeerState),
|
|
nextQuickReplyID: make(map[int64]int),
|
|
nextQuickReplyMessageID: make(map[int64]int),
|
|
}
|
|
}
|
|
|
|
func (s *PasswordStore) GetByUser(_ context.Context, userID int64) (domain.PasswordSettings, bool, error) {
|
|
s.mu.RLock()
|
|
settings, ok := s.m[userID]
|
|
s.mu.RUnlock()
|
|
return clonePasswordSettings(settings), ok, nil
|
|
}
|
|
|
|
func (s *PasswordStore) Save(_ context.Context, userID int64, settings domain.PasswordSettings) error {
|
|
s.mu.Lock()
|
|
s.m[userID] = clonePasswordSettings(settings)
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func clonePasswordSettings(in domain.PasswordSettings) domain.PasswordSettings {
|
|
out := in
|
|
if in.CurrentAlgo != nil {
|
|
algo := *in.CurrentAlgo
|
|
algo.Salt1 = append([]byte(nil), algo.Salt1...)
|
|
algo.Salt2 = append([]byte(nil), algo.Salt2...)
|
|
algo.P = append([]byte(nil), algo.P...)
|
|
out.CurrentAlgo = &algo
|
|
}
|
|
out.SRPB = append([]byte(nil), in.SRPB...)
|
|
out.NewAlgo.Salt1 = append([]byte(nil), in.NewAlgo.Salt1...)
|
|
out.NewAlgo.Salt2 = append([]byte(nil), in.NewAlgo.Salt2...)
|
|
out.NewAlgo.P = append([]byte(nil), in.NewAlgo.P...)
|
|
out.NewSecureAlgo.Salt = append([]byte(nil), in.NewSecureAlgo.Salt...)
|
|
out.SecureRandom = append([]byte(nil), in.SecureRandom...)
|
|
out.SRPVerifier = append([]byte(nil), in.SRPVerifier...)
|
|
out.SRPBSecret = append([]byte(nil), in.SRPBSecret...)
|
|
return out
|
|
}
|
|
|
|
func (s *PasswordStore) GetReactionSettings(_ context.Context, userID int64) (domain.AccountReactionSettings, bool, error) {
|
|
s.mu.RLock()
|
|
settings, ok := s.reactions[userID]
|
|
s.mu.RUnlock()
|
|
return cloneAccountReactionSettings(settings), ok, nil
|
|
}
|
|
|
|
func (s *PasswordStore) SaveReactionSettings(_ context.Context, userID int64, settings domain.AccountReactionSettings) error {
|
|
s.mu.Lock()
|
|
s.reactions[userID] = cloneAccountReactionSettings(settings)
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func cloneAccountReactionSettings(in domain.AccountReactionSettings) domain.AccountReactionSettings {
|
|
out := in
|
|
if in.PaidPrivacy.Peer != nil {
|
|
peer := *in.PaidPrivacy.Peer
|
|
out.PaidPrivacy.Peer = &peer
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (s *PasswordStore) GetAccountSettings(_ context.Context, userID int64) (domain.AccountSettings, bool, error) {
|
|
s.mu.RLock()
|
|
settings, ok := s.accountSettings[userID]
|
|
s.mu.RUnlock()
|
|
return settings, ok, nil // AccountSettings 全是值类型,无需深拷贝
|
|
}
|
|
|
|
func (s *PasswordStore) SaveAccountSettings(_ context.Context, userID int64, settings domain.AccountSettings) error {
|
|
s.mu.Lock()
|
|
s.accountSettings[userID] = settings
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
type notifySettingsKey struct {
|
|
owner int64
|
|
kind domain.NotifyScopeKind
|
|
peerType domain.PeerType
|
|
peerID int64
|
|
topicID int
|
|
}
|
|
|
|
func notifySettingsKeyOf(owner int64, scope domain.NotifyScope) notifySettingsKey {
|
|
key := notifySettingsKey{owner: owner, kind: scope.Kind}
|
|
if scope.Kind == domain.NotifyScopePeer {
|
|
key.peerType = scope.Peer.Type
|
|
key.peerID = scope.Peer.ID
|
|
key.topicID = scope.TopicID
|
|
}
|
|
return key
|
|
}
|
|
|
|
func (s *PasswordStore) GetNotifySettings(_ context.Context, ownerUserID int64, scope domain.NotifyScope) (domain.PeerNotifySettings, bool, error) {
|
|
s.mu.RLock()
|
|
settings, ok := s.notifySettings[notifySettingsKeyOf(ownerUserID, scope)]
|
|
s.mu.RUnlock()
|
|
return settings.Clone(), ok, nil
|
|
}
|
|
|
|
func (s *PasswordStore) SaveNotifySettings(_ context.Context, ownerUserID int64, scope domain.NotifyScope, settings domain.PeerNotifySettings) error {
|
|
s.mu.Lock()
|
|
s.notifySettings[notifySettingsKeyOf(ownerUserID, scope)] = settings.Clone()
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (s *PasswordStore) ResetNotifySettings(_ context.Context, ownerUserID int64) error {
|
|
s.mu.Lock()
|
|
for key := range s.notifySettings {
|
|
if key.owner == ownerUserID {
|
|
delete(s.notifySettings, key)
|
|
}
|
|
}
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
type stickerCollectionKey struct {
|
|
owner int64
|
|
kind domain.StickerCollectionKind
|
|
}
|
|
|
|
func (s *PasswordStore) SaveStickerCollectionItem(_ context.Context, userID int64, kind domain.StickerCollectionKind, documentID int64, unsave bool, now, max int) error {
|
|
if userID == 0 || documentID == 0 {
|
|
return domain.ErrStickerInvalid
|
|
}
|
|
if max <= 0 {
|
|
max = domain.MaxStickerCollectionItems(kind)
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
key := stickerCollectionKey{owner: userID, kind: kind}
|
|
cur := s.stickerCollections[key]
|
|
// 移除既有同 id 项。
|
|
next := make([]domain.StickerCollectionItem, 0, len(cur)+1)
|
|
for _, it := range cur {
|
|
if it.DocumentID == documentID {
|
|
continue
|
|
}
|
|
next = append(next, it)
|
|
}
|
|
if unsave {
|
|
s.stickerCollections[key] = next
|
|
return nil
|
|
}
|
|
// 最新置顶 + 截断。
|
|
next = append([]domain.StickerCollectionItem{{DocumentID: documentID, Date: now}}, next...)
|
|
if len(next) > max {
|
|
next = next[:max]
|
|
}
|
|
s.stickerCollections[key] = next
|
|
return nil
|
|
}
|
|
|
|
func (s *PasswordStore) ListStickerCollection(_ context.Context, userID int64, kind domain.StickerCollectionKind, limit int) ([]domain.StickerCollectionItem, error) {
|
|
if limit <= 0 || limit > domain.MaxStickerCollectionItems(kind) {
|
|
limit = domain.MaxStickerCollectionItems(kind)
|
|
}
|
|
s.mu.RLock()
|
|
cur := s.stickerCollections[stickerCollectionKey{owner: userID, kind: kind}]
|
|
s.mu.RUnlock()
|
|
if len(cur) > limit {
|
|
cur = cur[:limit]
|
|
}
|
|
return append([]domain.StickerCollectionItem(nil), cur...), nil
|
|
}
|
|
|
|
func (s *PasswordStore) ClearStickerCollection(_ context.Context, userID int64, kind domain.StickerCollectionKind) error {
|
|
s.mu.Lock()
|
|
delete(s.stickerCollections, stickerCollectionKey{owner: userID, kind: kind})
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (s *PasswordStore) InstallUserStickerSet(_ context.Context, userID int64, setID int64, kind domain.StickerSetKind, archived bool, installedDate int) error {
|
|
if userID == 0 || setID == 0 {
|
|
return domain.ErrStickerInvalid
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
sets := s.userStickerSets[userID]
|
|
if sets == nil {
|
|
sets = make(map[int64]domain.UserStickerSet)
|
|
s.userStickerSets[userID] = sets
|
|
}
|
|
order := int64(installedDate) << 32
|
|
sets[setID] = domain.UserStickerSet{
|
|
OwnerUserID: userID,
|
|
StickerSetID: setID,
|
|
Kind: kind,
|
|
Archived: archived,
|
|
InstalledDate: installedDate,
|
|
OrderValue: order,
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *PasswordStore) UninstallUserStickerSet(_ context.Context, userID int64, setID int64) error {
|
|
s.mu.Lock()
|
|
if sets := s.userStickerSets[userID]; sets != nil {
|
|
delete(sets, setID)
|
|
}
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (s *PasswordStore) SetUserStickerSetArchived(_ context.Context, userID int64, setID int64, archived bool, now int) error {
|
|
s.mu.Lock()
|
|
if sets := s.userStickerSets[userID]; sets != nil {
|
|
if item, ok := sets[setID]; ok {
|
|
item.Archived = archived
|
|
if !archived && now > 0 {
|
|
item.OrderValue = int64(now) << 32
|
|
}
|
|
sets[setID] = item
|
|
}
|
|
}
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func (s *PasswordStore) ReorderUserStickerSets(_ context.Context, userID int64, kind domain.StickerSetKind, order []int64, now int) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
sets := s.userStickerSets[userID]
|
|
if sets == nil {
|
|
return nil
|
|
}
|
|
orderValue := int64(now) << 32
|
|
for _, id := range order {
|
|
item, ok := sets[id]
|
|
if !ok || item.Kind != kind {
|
|
continue
|
|
}
|
|
item.OrderValue = orderValue
|
|
sets[id] = item
|
|
orderValue--
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *PasswordStore) ListUserStickerSets(_ context.Context, userID int64, kind domain.StickerSetKind, archived *bool, offsetID int64, limit int) ([]domain.UserStickerSet, int, error) {
|
|
if limit <= 0 || limit > domain.MaxInstalledStickerSets {
|
|
limit = domain.MaxInstalledStickerSets
|
|
}
|
|
s.mu.RLock()
|
|
raw := s.userStickerSets[userID]
|
|
items := make([]domain.UserStickerSet, 0, len(raw))
|
|
for _, item := range raw {
|
|
if item.Kind != kind {
|
|
continue
|
|
}
|
|
if archived != nil && item.Archived != *archived {
|
|
continue
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
s.mu.RUnlock()
|
|
sort.SliceStable(items, func(i, j int) bool {
|
|
if items[i].OrderValue == items[j].OrderValue {
|
|
return items[i].StickerSetID > items[j].StickerSetID
|
|
}
|
|
return items[i].OrderValue > items[j].OrderValue
|
|
})
|
|
total := len(items)
|
|
if offsetID != 0 {
|
|
start := 0
|
|
for i, item := range items {
|
|
if item.StickerSetID == offsetID {
|
|
start = i + 1
|
|
break
|
|
}
|
|
}
|
|
items = items[start:]
|
|
}
|
|
if len(items) > limit {
|
|
items = items[:limit]
|
|
}
|
|
return append([]domain.UserStickerSet(nil), items...), total, nil
|
|
}
|
|
|
|
func (s *PasswordStore) ListNotifyExceptions(_ context.Context, ownerUserID int64) ([]domain.NotifyException, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
out := make([]domain.NotifyException, 0)
|
|
for key, settings := range s.notifySettings {
|
|
if key.owner != ownerUserID || key.kind != domain.NotifyScopePeer || settings.IsZero() {
|
|
continue
|
|
}
|
|
out = append(out, domain.NotifyException{
|
|
Peer: domain.Peer{Type: key.peerType, ID: key.peerID},
|
|
TopicID: key.topicID,
|
|
Settings: settings.Clone(),
|
|
})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *PasswordStore) AllPeerNotifySettings(_ context.Context, ownerUserID int64) (map[domain.Peer]domain.PeerNotifySettings, error) {
|
|
out := make(map[domain.Peer]domain.PeerNotifySettings)
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
for key, settings := range s.notifySettings {
|
|
if key.owner != ownerUserID || key.kind != domain.NotifyScopePeer || key.topicID != 0 {
|
|
continue
|
|
}
|
|
out[domain.Peer{Type: key.peerType, ID: key.peerID}] = settings.Clone()
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *PasswordStore) GetPeerNotifySettings(_ context.Context, ownerUserID int64, peers []domain.Peer) (map[domain.Peer]domain.PeerNotifySettings, error) {
|
|
out := make(map[domain.Peer]domain.PeerNotifySettings, len(peers))
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
for _, p := range peers {
|
|
key := notifySettingsKey{owner: ownerUserID, kind: domain.NotifyScopePeer, peerType: p.Type, peerID: p.ID, topicID: 0}
|
|
if settings, ok := s.notifySettings[key]; ok {
|
|
out[p] = settings.Clone()
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *PasswordStore) SaveMusic(_ context.Context, req domain.SaveMusicRequest) error {
|
|
if req.UserID == 0 || req.Document.ID == 0 || !req.Document.IsMusic() {
|
|
return domain.ErrDocumentInvalid
|
|
}
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
current := s.savedMusic[req.UserID]
|
|
if req.Unsave {
|
|
s.savedMusic[req.UserID] = removeSavedMusicDocument(current, req.Document.ID)
|
|
return nil
|
|
}
|
|
if req.AfterDocumentID == req.Document.ID {
|
|
for _, doc := range current {
|
|
if doc.ID == req.Document.ID {
|
|
return nil
|
|
}
|
|
}
|
|
return domain.ErrDocumentInvalid
|
|
}
|
|
next := make([]domain.Document, 0, len(current)+1)
|
|
afterIndex := -1
|
|
for _, doc := range current {
|
|
if doc.ID == req.Document.ID {
|
|
continue
|
|
}
|
|
if doc.ID == req.AfterDocumentID {
|
|
afterIndex = len(next)
|
|
}
|
|
next = append(next, cloneDocument(doc))
|
|
}
|
|
insert := cloneDocument(req.Document)
|
|
if req.AfterDocumentID != 0 {
|
|
if afterIndex < 0 {
|
|
return domain.ErrDocumentInvalid
|
|
}
|
|
next = append(next, domain.Document{})
|
|
copy(next[afterIndex+2:], next[afterIndex+1:])
|
|
next[afterIndex+1] = insert
|
|
} else {
|
|
next = append([]domain.Document{insert}, next...)
|
|
}
|
|
if len(next) > domain.MaxSavedMusicItems {
|
|
next = next[:domain.MaxSavedMusicItems]
|
|
}
|
|
s.savedMusic[req.UserID] = next
|
|
return nil
|
|
}
|
|
|
|
func (s *PasswordStore) ListSavedMusicIDs(_ context.Context, userID int64, limit int) ([]int64, error) {
|
|
s.mu.RLock()
|
|
current := s.savedMusic[userID]
|
|
s.mu.RUnlock()
|
|
if limit <= 0 || limit > domain.MaxSavedMusicItems {
|
|
limit = domain.MaxSavedMusicItems
|
|
}
|
|
if len(current) > limit {
|
|
current = current[:limit]
|
|
}
|
|
out := make([]int64, 0, len(current))
|
|
for _, doc := range current {
|
|
out = append(out, doc.ID)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *PasswordStore) ListSavedMusic(_ context.Context, userID int64, offset, limit int) (domain.SavedMusicList, error) {
|
|
s.mu.RLock()
|
|
current := cloneDocuments(s.savedMusic[userID])
|
|
s.mu.RUnlock()
|
|
out := domain.SavedMusicList{UserID: userID, Count: len(current)}
|
|
if offset < 0 || offset >= len(current) || limit <= 0 {
|
|
return out, nil
|
|
}
|
|
end := offset + limit
|
|
if end > len(current) {
|
|
end = len(current)
|
|
}
|
|
out.Documents = cloneDocuments(current[offset:end])
|
|
return out, nil
|
|
}
|
|
|
|
func (s *PasswordStore) GetSavedMusicByIDs(_ context.Context, userID int64, ids []int64) (domain.SavedMusicList, error) {
|
|
s.mu.RLock()
|
|
current := cloneDocuments(s.savedMusic[userID])
|
|
s.mu.RUnlock()
|
|
byID := make(map[int64]domain.Document, len(current))
|
|
for _, doc := range current {
|
|
byID[doc.ID] = doc
|
|
}
|
|
out := domain.SavedMusicList{UserID: userID, Count: len(current)}
|
|
seen := make(map[int64]struct{}, len(ids))
|
|
for _, id := range ids {
|
|
if id == 0 {
|
|
continue
|
|
}
|
|
if _, ok := seen[id]; ok {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
if doc, ok := byID[id]; ok {
|
|
out.Documents = append(out.Documents, cloneDocument(doc))
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func removeSavedMusicDocument(in []domain.Document, id int64) []domain.Document {
|
|
if len(in) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]domain.Document, 0, len(in))
|
|
for _, doc := range in {
|
|
if doc.ID != id {
|
|
out = append(out, cloneDocument(doc))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func cloneDocument(in domain.Document) domain.Document {
|
|
out := in
|
|
out.FileReference = append([]byte(nil), in.FileReference...)
|
|
out.Attributes = append([]domain.DocumentAttribute(nil), in.Attributes...)
|
|
out.Thumbs = append([]domain.PhotoSize(nil), in.Thumbs...)
|
|
return out
|
|
}
|
|
|
|
func cloneDocuments(in []domain.Document) []domain.Document {
|
|
out := make([]domain.Document, 0, len(in))
|
|
for _, doc := range in {
|
|
out = append(out, cloneDocument(doc))
|
|
}
|
|
return out
|
|
}
|