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

227 lines
7.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}