owpengram-server/internal/store/memory/message_reactions.go

255 lines
8 KiB
Go

package memory
import (
"context"
"encoding/binary"
"hash"
"sort"
"telesrv/internal/domain"
"time"
)
func (s *MessageStore) SetMessageReactions(_ context.Context, req domain.SetPrivateMessageReactionsRequest) (domain.PrivateMessageReactionsResult, error) {
if req.UserID == 0 || req.Peer.Type != domain.PeerTypeUser || req.Peer.ID == 0 || req.MessageID <= 0 || req.MessageID > domain.MaxMessageBoxID {
return domain.PrivateMessageReactionsResult{}, domain.ErrMessageIDInvalid
}
if len(req.Reactions) > domain.MaxChannelMessageReactionsPerUser {
return domain.PrivateMessageReactionsResult{}, domain.ErrMessageIDInvalid
}
req.Reactions = domain.TrimMessageReactionsToUserMax(req.Reactions, req.ReactionsPerUserMax)
if req.Date == 0 {
req.Date = int(time.Now().Unix())
}
s.mu.Lock()
defer s.mu.Unlock()
var target domain.Message
for _, msg := range s.m[req.UserID] {
if msg.ID == req.MessageID && msg.Peer == req.Peer {
target = msg
break
}
}
if target.ID == 0 || target.UID == 0 {
return domain.PrivateMessageReactionsResult{}, domain.ErrMessageIDInvalid
}
if _, ok := s.privateReactions[target.UID]; !ok {
s.privateReactions[target.UID] = make(map[int64][]domain.ChannelMessagePeerReaction)
}
rows := make([]domain.ChannelMessagePeerReaction, 0, len(req.Reactions))
for i, reaction := range req.Reactions {
if !reaction.Valid() {
return domain.PrivateMessageReactionsResult{}, domain.ErrMessageIDInvalid
}
rows = append(rows, domain.ChannelMessagePeerReaction{
UserID: req.UserID,
Reaction: reaction,
Big: req.Big,
My: true,
ChosenOrder: i + 1,
Date: req.Date,
})
}
if len(rows) == 0 {
delete(s.privateReactions[target.UID], req.UserID)
} else {
s.privateReactions[target.UID][req.UserID] = rows
}
if target.From.ID != 0 && target.From.ID != req.UserID {
for i := range s.m[target.From.ID] {
if s.m[target.From.ID][i].UID != target.UID {
continue
}
s.m[target.From.ID][i].ReactionUnread = len(rows) > 0
break
}
}
s.refreshPrivateReactionDialogSnapshotsLocked(target.UID)
return s.privateReactionResultLocked(target.UID), nil
}
func (s *MessageStore) GetMessageReactions(_ context.Context, req domain.PrivateMessageReactionsRequest) (domain.PrivateMessageReactionsResult, error) {
if req.OwnerUserID == 0 || req.Peer.Type != domain.PeerTypeUser || req.Peer.ID == 0 || len(req.IDs) > domain.MaxGetMessageIDs {
return domain.PrivateMessageReactionsResult{}, domain.ErrMessageIDInvalid
}
ids := make(map[int]struct{}, len(req.IDs))
for _, id := range req.IDs {
if id <= 0 || id > domain.MaxMessageBoxID {
return domain.PrivateMessageReactionsResult{}, domain.ErrMessageIDInvalid
}
ids[id] = struct{}{}
}
s.mu.RLock()
defer s.mu.RUnlock()
out := domain.PrivateMessageReactionsResult{}
for _, msg := range s.m[req.OwnerUserID] {
if msg.Peer != req.Peer {
continue
}
if _, ok := ids[msg.ID]; !ok {
continue
}
item := cloneMessage(msg)
reactions := s.privateMessageReactionsForMessageLocked(item)
item.Reactions = cloneChannelMessageReactionsPtr(&reactions)
out.Messages = append(out.Messages, item)
if len(out.Reactions.Results) == 0 && len(out.Reactions.Recent) == 0 {
out.Reactions = reactions
}
}
return out, nil
}
func (s *MessageStore) privateReactionResultLocked(uid int64) domain.PrivateMessageReactionsResult {
out := domain.PrivateMessageReactionsResult{}
for _, messages := range s.m {
for _, msg := range messages {
if msg.UID != uid {
continue
}
item := cloneMessage(msg)
reactions := s.privateMessageReactionsForMessageLocked(item)
item.Reactions = cloneChannelMessageReactionsPtr(&reactions)
out.Messages = append(out.Messages, item)
if len(out.Reactions.Results) == 0 && len(out.Reactions.Recent) == 0 {
out.Reactions = reactions
}
}
}
return out
}
func (s *MessageStore) privateMessageReactionsForMessageLocked(msg domain.Message) domain.ChannelMessageReactions {
reactions := s.privateMessageReactionsLocked(msg.UID, msg.OwnerUserID)
if len(reactions.Recent) == 0 || msg.From.ID == 0 {
return reactions
}
for i := range reactions.Recent {
reactions.Recent[i].SenderUserID = msg.From.ID
if msg.ReactionUnread && msg.From.ID == msg.OwnerUserID && reactions.Recent[i].UserID != msg.OwnerUserID {
reactions.Recent[i].Unread = true
}
}
return reactions
}
func (s *MessageStore) privateMessageReactionsLocked(uid, viewerUserID int64) domain.ChannelMessageReactions {
byUser := s.privateReactions[uid]
out := domain.ChannelMessageReactions{CanSeeList: true}
if len(byUser) == 0 {
return out
}
counts := make(map[string]int)
recent := make([]domain.ChannelMessagePeerReaction, 0, len(byUser))
for userID, rows := range byUser {
for _, row := range rows {
key := row.Reaction.Key()
index, ok := counts[key]
if !ok {
out.Results = append(out.Results, domain.ChannelMessageReactionCount{Reaction: row.Reaction})
index = len(out.Results) - 1
counts[key] = index
}
out.Results[index].Count++
if userID == viewerUserID && (out.Results[index].ChosenOrder == 0 || row.ChosenOrder < out.Results[index].ChosenOrder) {
out.Results[index].ChosenOrder = row.ChosenOrder
}
item := row
item.UserID = userID
item.My = userID == viewerUserID
recent = append(recent, item)
}
}
sort.Slice(out.Results, func(i, j int) bool {
if out.Results[i].Count != out.Results[j].Count {
return out.Results[i].Count > out.Results[j].Count
}
return out.Results[i].Reaction.Key() < out.Results[j].Reaction.Key()
})
sort.Slice(recent, func(i, j int) bool {
if recent[i].Date != recent[j].Date {
return recent[i].Date > recent[j].Date
}
return recent[i].UserID < recent[j].UserID
})
if len(recent) > domain.MaxChannelMessageReactionRecent {
recent = recent[:domain.MaxChannelMessageReactionRecent]
}
out.Recent = recent
return out
}
func (s *MessageStore) countPrivateUnreadReactionsLocked(ownerUserID int64, peer domain.Peer) int {
count := 0
for _, msg := range s.m[ownerUserID] {
if msg.Peer == peer && msg.ReactionUnread {
count++
}
}
return count
}
func (s *MessageStore) refreshPrivateReactionDialogSnapshotsLocked(uid int64) {
if s.dialogs == nil || uid == 0 {
return
}
s.dialogs.mu.Lock()
defer s.dialogs.mu.Unlock()
for ownerID, messages := range s.m {
list := s.dialogs.m[ownerID]
changed := false
for _, msg := range messages {
if msg.UID != uid {
continue
}
enriched := cloneMessage(msg)
reactions := s.privateMessageReactionsForMessageLocked(enriched)
if len(reactions.Results) > 0 || len(reactions.Recent) > 0 {
enriched.Reactions = cloneChannelMessageReactionsPtr(&reactions)
}
for i := range list.Messages {
if list.Messages[i].UID == uid && list.Messages[i].OwnerUserID == ownerID {
list.Messages[i] = enriched
changed = true
}
}
for i := range list.Dialogs {
if list.Dialogs[i].Peer == msg.Peer {
list.Dialogs[i].UnreadReactions = s.countPrivateUnreadReactionsLocked(ownerID, msg.Peer)
changed = true
break
}
}
}
if changed {
s.dialogs.m[ownerID] = list
}
}
}
func writeMessageReactionsHash(h hash.Hash64, reactions *domain.ChannelMessageReactions) {
if reactions == nil {
_, _ = h.Write([]byte{0})
return
}
var buf [16]byte
for _, item := range reactions.Results {
_, _ = h.Write([]byte(item.Reaction.Type))
_, _ = h.Write([]byte{0})
_, _ = h.Write([]byte(item.Reaction.Value()))
_, _ = h.Write([]byte{0})
binary.LittleEndian.PutUint32(buf[:4], uint32(item.Count))
binary.LittleEndian.PutUint32(buf[4:8], uint32(item.ChosenOrder))
_, _ = h.Write(buf[:8])
}
_, _ = h.Write([]byte{0xfe})
for _, item := range reactions.Recent {
_, _ = h.Write([]byte(item.Reaction.Type))
_, _ = h.Write([]byte{0})
_, _ = h.Write([]byte(item.Reaction.Value()))
_, _ = h.Write([]byte{0})
binary.LittleEndian.PutUint64(buf[:8], uint64(item.UserID))
binary.LittleEndian.PutUint32(buf[8:12], uint32(item.Date))
binary.LittleEndian.PutUint32(buf[12:16], uint32(item.ChosenOrder))
_, _ = h.Write(buf[:])
}
}