227 lines
7.6 KiB
Go
227 lines
7.6 KiB
Go
package memory
|
||
|
||
import (
|
||
"context"
|
||
"telesrv/internal/domain"
|
||
"time"
|
||
)
|
||
|
||
func (s *MessageStore) Create(_ context.Context, msg domain.Message) (domain.Message, error) {
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
msg.ID = s.nextBoxIDLocked(msg.OwnerUserID)
|
||
msg.UID = s.nextUID
|
||
s.nextUID++
|
||
msg.Entities = append([]domain.MessageEntity(nil), msg.Entities...)
|
||
s.m[msg.OwnerUserID] = append(s.m[msg.OwnerUserID], msg)
|
||
if s.dialogs != nil {
|
||
s.dialogs.mu.Lock()
|
||
list := s.dialogs.m[msg.OwnerUserID]
|
||
list.Messages = append(list.Messages, msg)
|
||
if msg.Peer.Type == domain.PeerTypeUser && !hasUser(list.Users, msg.Peer.ID) {
|
||
if u, ok := domain.SystemUserByID(msg.Peer.ID); ok {
|
||
list.Users = append(list.Users, u)
|
||
}
|
||
}
|
||
s.dialogs.m[msg.OwnerUserID] = list
|
||
s.dialogs.mu.Unlock()
|
||
}
|
||
return msg, nil
|
||
}
|
||
|
||
func (s *MessageStore) SendPrivateText(_ context.Context, req domain.SendPrivateTextRequest) (domain.SendPrivateTextResult, error) {
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
for _, msg := range s.m[req.SenderUserID] {
|
||
if msg.RandomID != 0 && msg.RandomID == req.RandomID {
|
||
recipient := domain.Message{}
|
||
if req.SenderUserID != req.RecipientUserID {
|
||
for _, peerMsg := range s.m[req.RecipientUserID] {
|
||
if peerMsg.UID == msg.UID {
|
||
recipient = peerMsg
|
||
break
|
||
}
|
||
}
|
||
} else {
|
||
recipient = msg
|
||
}
|
||
return domain.SendPrivateTextResult{
|
||
SenderMessage: cloneMessage(msg),
|
||
RecipientMessage: cloneMessage(recipient),
|
||
SenderEvent: newMessageEvent(msg),
|
||
RecipientEvent: newMessageEvent(recipient),
|
||
Duplicate: true,
|
||
}, nil
|
||
}
|
||
}
|
||
if req.Date == 0 {
|
||
req.Date = int(time.Now().Unix())
|
||
}
|
||
senderReply, recipientReply, err := s.resolveMemoryReplyLocked(req)
|
||
if err != nil {
|
||
return domain.SendPrivateTextResult{}, err
|
||
}
|
||
uid := s.nextUID
|
||
s.nextUID++
|
||
sender := domain.Message{
|
||
ID: s.nextBoxIDLocked(req.SenderUserID),
|
||
UID: uid,
|
||
RandomID: req.RandomID,
|
||
OwnerUserID: req.SenderUserID,
|
||
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: req.RecipientUserID},
|
||
From: domain.Peer{Type: domain.PeerTypeUser, ID: req.SenderUserID},
|
||
Date: req.Date,
|
||
Out: true,
|
||
Silent: req.Silent,
|
||
NoForwards: req.NoForwards,
|
||
Body: req.Message,
|
||
Entities: append([]domain.MessageEntity(nil), req.Entities...),
|
||
Media: req.Media,
|
||
ViaBotID: req.ViaBotID,
|
||
GroupedID: req.GroupedID,
|
||
Effect: req.Effect,
|
||
ReplyMarkup: cloneReplyMarkup(req.ReplyMarkup),
|
||
RichMessage: cloneRichMessage(req.RichMessage),
|
||
ReplyTo: cloneMessageReply(senderReply),
|
||
Forward: cloneMessageForward(req.Forward),
|
||
Pts: s.nextPtsLocked(req.SenderUserID),
|
||
// voice/round 在发送者副本上同样保持"未听",由对端内容已读清除。
|
||
MediaUnread: req.Media.HasUnreadPayload() && req.SenderUserID != req.RecipientUserID,
|
||
}
|
||
if req.SenderUserID == req.RecipientUserID {
|
||
sender.SavedPeer = domain.SavedPeerForSelfChat(req.SenderUserID, req.Forward)
|
||
}
|
||
recipient := domain.Message{}
|
||
if req.SenderUserID == req.RecipientUserID {
|
||
recipient = sender
|
||
}
|
||
if req.SenderUserID != req.RecipientUserID && !req.RecipientBlocked {
|
||
recipient = sender
|
||
recipient.ID = s.nextBoxIDLocked(req.RecipientUserID)
|
||
recipient.OwnerUserID = req.RecipientUserID
|
||
recipient.Peer = domain.Peer{Type: domain.PeerTypeUser, ID: req.SenderUserID}
|
||
recipient.Out = false
|
||
recipient.ReplyTo = cloneMessageReply(recipientReply)
|
||
// recipient = sender 是值拷贝,共享 sender.ReplyMarkup 指针/Data 切片——深拷
|
||
// 让双盒各持独立快照(与 postgres 每盒独立 decode 对齐,I3/I2)。
|
||
recipient.ReplyMarkup = cloneReplyMarkup(sender.ReplyMarkup)
|
||
recipient.RichMessage = cloneRichMessage(sender.RichMessage)
|
||
recipient.Pts = s.nextPtsLocked(req.RecipientUserID)
|
||
recipient.MediaUnread = req.Media.HasUnreadPayload()
|
||
}
|
||
s.m[req.SenderUserID] = append(s.m[req.SenderUserID], sender)
|
||
if req.SenderUserID != req.RecipientUserID && !req.RecipientBlocked {
|
||
s.m[req.RecipientUserID] = append(s.m[req.RecipientUserID], recipient)
|
||
}
|
||
if s.dialogs != nil {
|
||
if recipient.ID != 0 {
|
||
s.upsertMemoryDialogsLocked(sender, recipient)
|
||
} else {
|
||
s.upsertMemoryDialogsLocked(sender, sender)
|
||
}
|
||
}
|
||
return domain.SendPrivateTextResult{
|
||
SenderMessage: cloneMessage(sender),
|
||
RecipientMessage: cloneMessage(recipient),
|
||
SenderEvent: newMessageEvent(sender),
|
||
RecipientEvent: newMessageEvent(recipient),
|
||
}, nil
|
||
}
|
||
|
||
func (s *MessageStore) resolveMemoryReplyLocked(req domain.SendPrivateTextRequest) (*domain.MessageReply, *domain.MessageReply, error) {
|
||
if req.ReplyTo == nil {
|
||
return nil, nil, nil
|
||
}
|
||
if err := domain.ValidateMessageReplyBounds(req.ReplyTo); err != nil {
|
||
return nil, nil, err
|
||
}
|
||
if req.ReplyTo.StoryID > 0 {
|
||
// story 回复(评论):无源消息可查;story 作者就是会话对端,双盒同持。
|
||
reply := &domain.MessageReply{
|
||
StoryID: req.ReplyTo.StoryID,
|
||
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: req.RecipientUserID},
|
||
}
|
||
return cloneMessageReply(reply), cloneMessageReply(reply), nil
|
||
}
|
||
peer := req.ReplyTo.Peer
|
||
if peer.ID == 0 {
|
||
peer = domain.Peer{Type: domain.PeerTypeUser, ID: req.RecipientUserID}
|
||
}
|
||
if peer.Type != domain.PeerTypeUser || peer.ID != req.RecipientUserID {
|
||
return nil, nil, domain.ErrReplyMessageIDInvalid
|
||
}
|
||
var target domain.Message
|
||
for _, msg := range s.m[req.SenderUserID] {
|
||
if msg.Peer == peer && msg.ID == req.ReplyTo.MessageID {
|
||
target = msg
|
||
break
|
||
}
|
||
}
|
||
if target.ID == 0 {
|
||
return nil, nil, domain.ErrReplyMessageIDInvalid
|
||
}
|
||
senderReply := cloneMessageReply(req.ReplyTo)
|
||
senderReply.MessageID = target.ID
|
||
senderReply.Peer = peer
|
||
if req.SenderUserID == req.RecipientUserID {
|
||
return senderReply, cloneMessageReply(senderReply), nil
|
||
}
|
||
for _, msg := range s.m[req.RecipientUserID] {
|
||
if msg.UID == target.UID {
|
||
recipientReply := cloneMessageReply(senderReply)
|
||
recipientReply.MessageID = msg.ID
|
||
recipientReply.Peer = domain.Peer{Type: domain.PeerTypeUser, ID: req.SenderUserID}
|
||
return senderReply, recipientReply, nil
|
||
}
|
||
}
|
||
return senderReply, nil, nil
|
||
}
|
||
|
||
func (s *MessageStore) upsertMemoryDialogsLocked(sender, recipient domain.Message) {
|
||
s.dialogs.mu.Lock()
|
||
defer s.dialogs.mu.Unlock()
|
||
list := s.dialogs.m[sender.OwnerUserID]
|
||
list = upsertMemoryDialog(list, domain.Dialog{Peer: sender.Peer, TopMessage: sender.ID, TopMessageDate: sender.Date})
|
||
// 发送方向清手动未读标记(对齐 postgres UpsertOutboxDialog 与
|
||
// channel 发送路径:向会话发出消息即视为已知晓内容)。
|
||
for i := range list.Dialogs {
|
||
if list.Dialogs[i].Peer == sender.Peer {
|
||
list.Dialogs[i].UnreadMark = false
|
||
break
|
||
}
|
||
}
|
||
list.Messages = append(list.Messages, sender)
|
||
s.dialogs.m[sender.OwnerUserID] = list
|
||
if recipient.OwnerUserID != sender.OwnerUserID {
|
||
peerList := s.dialogs.m[recipient.OwnerUserID]
|
||
peerList = upsertMemoryDialog(peerList, domain.Dialog{
|
||
Peer: recipient.Peer,
|
||
TopMessage: recipient.ID,
|
||
TopMessageDate: recipient.Date,
|
||
UnreadCount: s.privateUnreadCountLocked(recipient.OwnerUserID, recipient.Peer),
|
||
})
|
||
peerList.Messages = append(peerList.Messages, recipient)
|
||
s.dialogs.m[recipient.OwnerUserID] = peerList
|
||
}
|
||
}
|
||
|
||
func (s *MessageStore) privateUnreadCountLocked(ownerUserID int64, peer domain.Peer) int {
|
||
readMax := 0
|
||
if s.dialogs != nil {
|
||
if list, ok := s.dialogs.m[ownerUserID]; ok {
|
||
for _, dialog := range list.Dialogs {
|
||
if dialog.Peer == peer {
|
||
readMax = dialog.ReadInboxMaxID
|
||
break
|
||
}
|
||
}
|
||
}
|
||
}
|
||
unread := 0
|
||
for _, msg := range s.m[ownerUserID] {
|
||
if msg.Peer == peer && !msg.Out && msg.ID > readMax {
|
||
unread++
|
||
}
|
||
}
|
||
return unread
|
||
}
|