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

116 lines
4.7 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"
"time"
"telesrv/internal/domain"
)
// 频道/超级群消息 poll 投票与关闭:成员资格与消息可见性沿用 reaction 同款校验,
// poll 级语义委托共享 PollStore。
func (s *ChannelStore) VoteChannelMessagePoll(_ context.Context, req domain.VoteChannelMessagePollRequest) (domain.ChannelMessagePollResult, error) {
if req.Date == 0 {
req.Date = int(time.Now().Unix())
}
channel, msg, err := s.pollChannelMessageTarget(req.UserID, req.ChannelID, req.MessageID)
if err != nil {
return domain.ChannelMessagePollResult{}, err
}
if err := s.polls.Vote(msg.Media.Poll.ID, req.UserID, req.Options, req.Date); err != nil {
return domain.ChannelMessagePollResult{}, err
}
return s.channelPollResult(channel, msg, req.UserID, req.Date), nil
}
func (s *ChannelStore) CloseChannelMessagePoll(_ context.Context, req domain.CloseChannelMessagePollRequest) (domain.ChannelMessagePollResult, error) {
if req.Date == 0 {
req.Date = int(time.Now().Unix())
}
channel, msg, err := s.pollChannelMessageTarget(req.UserID, req.ChannelID, req.MessageID)
if err != nil {
return domain.ChannelMessagePollResult{}, err
}
if err := s.polls.Close(msg.Media.Poll.ID, req.UserID); err != nil {
return domain.ChannelMessagePollResult{}, err
}
return s.channelPollResult(channel, msg, req.UserID, req.Date), nil
}
func (s *ChannelStore) pollChannelMessageTarget(userID, channelID int64, messageID int) (domain.Channel, domain.ChannelMessage, error) {
if s == nil || s.polls == nil {
return domain.Channel{}, domain.ChannelMessage{}, domain.ErrMessageIDInvalid
}
if userID == 0 || channelID == 0 || messageID <= 0 || messageID > domain.MaxMessageBoxID {
return domain.Channel{}, domain.ChannelMessage{}, domain.ErrChannelInvalid
}
s.mu.RLock()
defer s.mu.RUnlock()
channel, member, err := s.channelAndMemberLocked(userID, channelID)
if err != nil {
return domain.Channel{}, domain.ChannelMessage{}, err
}
msg, ok := s.findMessageLocked(channelID, messageID)
if !ok || msg.Deleted || msg.Action != nil || msg.ID <= member.AvailableMinID {
return domain.Channel{}, domain.ChannelMessage{}, domain.ErrMessageIDInvalid
}
if msg.Media == nil || msg.Media.Kind != domain.MessageMediaKindPoll || msg.Media.Poll == nil || msg.Media.Poll.ID == 0 {
return domain.Channel{}, domain.ChannelMessage{}, domain.ErrMessageIDInvalid
}
return cloneChannel(channel), cloneChannelMessage(msg), nil
}
// ChannelPollFanoutViews 批量加载一条 poll 消息对一组 viewer 的 per-viewer enrich与 postgres 同口径,
// 消除 fan-out 逐 viewer 重载 N+1成员/AvailableMinID 可见性复刻 channelAndMemberLocked +
// pollChannelMessageTargetpoll enrich 委托 PollStore.EnrichPollForViewers模板一次。bot 历史过滤
// 在 app 层叠加。Pollskey 存在=已评估nil=不可见;非 nil=可见 enrich poll。
func (s *ChannelStore) ChannelPollFanoutViews(_ context.Context, channelID int64, msgID int, viewers []int64, now int) (domain.ChannelPollFanoutViews, error) {
out := domain.ChannelPollFanoutViews{Polls: map[int64]*domain.MessagePoll{}}
if s == nil || s.polls == nil || channelID == 0 || msgID <= 0 || len(viewers) == 0 {
return out, nil
}
if now == 0 {
now = int(time.Now().Unix())
}
s.mu.RLock()
defer s.mu.RUnlock()
channel, ok := s.channels[channelID]
if !ok || channel.Deleted {
return out, nil
}
msg, ok := s.findMessageLocked(channelID, msgID)
if !ok || msg.Deleted || msg.Action != nil || msg.Media == nil || msg.Media.Kind != domain.MessageMediaKindPoll || msg.Media.Poll == nil || msg.Media.Poll.ID == 0 {
return out, nil
}
out.Found = true
out.Message = cloneChannelMessage(msg)
visible := make([]int64, 0, len(viewers))
for _, viewer := range viewers {
if viewer == 0 {
continue
}
member, ok := s.members[channelID][viewer]
if !ok || member.Status != domain.ChannelMemberActive || member.BannedRights.ViewMessages || (member.AvailableMinID > 0 && msgID <= member.AvailableMinID) {
out.Polls[viewer] = nil // 已评估但不可见
continue
}
visible = append(visible, viewer)
}
enriched := s.polls.EnrichPollForViewers(msg.Media.Poll, visible, now)
for viewer, poll := range enriched {
out.Polls[viewer] = poll
}
return out, nil
}
// channelPollResult 为投票者视角组装结果;实时 fan-out 与 reaction 同款由 rpc 层按 viewer 重建。
func (s *ChannelStore) channelPollResult(channel domain.Channel, msg domain.ChannelMessage, viewerUserID int64, now int) domain.ChannelMessagePollResult {
msg.Media = enrichPollMediaForViewer(s.polls, msg.Media, viewerUserID, now)
return domain.ChannelMessagePollResult{
PollID: msg.Media.Poll.ID,
Channel: channel,
Message: msg,
Recipients: []int64{viewerUserID, msg.SenderUserID},
}
}