chore: refresh gramsrv public release
This commit is contained in:
parent
75cebe8dbf
commit
70b6820474
1274 changed files with 378751 additions and 59919 deletions
561
internal/store/memory/channel_message_history.go
Normal file
561
internal/store/memory/channel_message_history.go
Normal file
|
|
@ -0,0 +1,561 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"strings"
|
||||
"telesrv/internal/domain"
|
||||
)
|
||||
|
||||
func (s *ChannelStore) ListChannelHistory(_ context.Context, viewerUserID int64, filter domain.ChannelHistoryFilter) (domain.ChannelHistory, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
channel, member, _, err := s.channelForViewerLocked(viewerUserID, filter.ChannelID)
|
||||
if err != nil {
|
||||
return domain.ChannelHistory{}, err
|
||||
}
|
||||
limit := filter.Limit
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 100
|
||||
}
|
||||
items := append([]domain.ChannelMessage(nil), s.messages[filter.ChannelID]...)
|
||||
sort.Slice(items, func(i, j int) bool { return items[i].ID > items[j].ID })
|
||||
// 静态过滤(不含 offset 锚点的方向条件),结果保持 id 降序。
|
||||
query := strings.ToLower(strings.TrimSpace(filter.Query))
|
||||
matched := make([]domain.ChannelMessage, 0, len(items))
|
||||
for _, msg := range items {
|
||||
if msg.Deleted {
|
||||
continue
|
||||
}
|
||||
if channel.Monoforum && msg.SavedPeer.ID != 0 {
|
||||
continue
|
||||
}
|
||||
if msg.ID <= member.AvailableMinID {
|
||||
continue
|
||||
}
|
||||
if filter.PinnedOnly && !msg.Pinned {
|
||||
continue
|
||||
}
|
||||
if filter.MusicOnly && !msg.Media.IsMusic() {
|
||||
continue
|
||||
}
|
||||
if query != "" && !strings.Contains(strings.ToLower(msg.Body), query) {
|
||||
continue
|
||||
}
|
||||
if filter.SenderUserID != 0 && msg.SenderUserID != filter.SenderUserID {
|
||||
continue
|
||||
}
|
||||
if filter.MinDate > 0 && msg.Date <= filter.MinDate {
|
||||
continue
|
||||
}
|
||||
if filter.MaxDate > 0 && msg.Date >= filter.MaxDate {
|
||||
continue
|
||||
}
|
||||
if filter.MaxID > 0 && msg.ID > filter.MaxID {
|
||||
continue
|
||||
}
|
||||
if filter.MinID > 0 && msg.ID <= filter.MinID {
|
||||
continue
|
||||
}
|
||||
matched = append(matched, msg)
|
||||
}
|
||||
// add_offset 决定加载方向(对齐 postgres ListChannelHistory):
|
||||
// >= 0 backward:锚点更旧方向(不含锚点),先跳过 add_offset 条
|
||||
// < 0 且 +limit>0 around:以锚点为中心,向更新取 -add_offset 条 + 向更旧(含锚点)取 limit+add_offset 条
|
||||
// 否则 forward:仅锚点更新方向
|
||||
newerThanAnchor := func(msg domain.ChannelMessage) bool {
|
||||
if filter.OffsetDate > 0 {
|
||||
return msg.Date >= filter.OffsetDate
|
||||
}
|
||||
if filter.OffsetID > 0 {
|
||||
return msg.ID > filter.OffsetID
|
||||
}
|
||||
return false
|
||||
}
|
||||
olderThanAnchor := func(msg domain.ChannelMessage, includeAnchor bool) bool {
|
||||
if filter.OffsetDate > 0 {
|
||||
return msg.Date < filter.OffsetDate
|
||||
}
|
||||
if filter.OffsetID > 0 {
|
||||
if includeAnchor {
|
||||
return msg.ID <= filter.OffsetID
|
||||
}
|
||||
return msg.ID < filter.OffsetID
|
||||
}
|
||||
return true
|
||||
}
|
||||
takeNewer := func(limit int) []domain.ChannelMessage {
|
||||
if limit <= 0 {
|
||||
return nil
|
||||
}
|
||||
// 升序收集锚点更新方向最近的 limit 条,再反转回降序。
|
||||
asc := make([]domain.ChannelMessage, 0, limit)
|
||||
for i := len(matched) - 1; i >= 0; i-- {
|
||||
if !newerThanAnchor(matched[i]) {
|
||||
continue
|
||||
}
|
||||
asc = append(asc, matched[i])
|
||||
if len(asc) == limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
out := make([]domain.ChannelMessage, 0, len(asc))
|
||||
for i := len(asc) - 1; i >= 0; i-- {
|
||||
out = append(out, asc[i])
|
||||
}
|
||||
return out
|
||||
}
|
||||
takeOlder := func(skip, limit int, includeAnchor bool) ([]domain.ChannelMessage, bool) {
|
||||
if limit <= 0 {
|
||||
return nil, false
|
||||
}
|
||||
out := make([]domain.ChannelMessage, 0, limit)
|
||||
skipped := 0
|
||||
for _, msg := range matched {
|
||||
if !olderThanAnchor(msg, includeAnchor) {
|
||||
continue
|
||||
}
|
||||
if skipped < skip {
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
if len(out) == limit {
|
||||
return out, true
|
||||
}
|
||||
out = append(out, msg)
|
||||
}
|
||||
return out, false
|
||||
}
|
||||
addOffset := filter.AddOffset
|
||||
var page []domain.ChannelMessage
|
||||
hasMoreOlder := false
|
||||
switch {
|
||||
case addOffset < 0 && addOffset+limit > 0:
|
||||
fwdLimit := -addOffset
|
||||
if fwdLimit > limit {
|
||||
fwdLimit = limit
|
||||
}
|
||||
bwdLimit := limit + addOffset
|
||||
page = takeNewer(fwdLimit)
|
||||
older, more := takeOlder(0, bwdLimit, true)
|
||||
page = append(page, older...)
|
||||
hasMoreOlder = more
|
||||
case addOffset < 0:
|
||||
page = takeNewer(limit)
|
||||
default:
|
||||
page, hasMoreOlder = takeOlder(addOffset, limit, false)
|
||||
}
|
||||
out := make([]domain.ChannelMessage, 0, len(page))
|
||||
for _, msg := range page {
|
||||
out = append(out, cloneChannelMessage(msg))
|
||||
}
|
||||
count := len(out)
|
||||
if hasMoreOlder {
|
||||
count = len(out) + 1
|
||||
}
|
||||
s.populateChannelMessageRepliesLocked(viewerUserID, filter.ChannelID, out)
|
||||
s.populateChannelMessageReactionsLocked(viewerUserID, channel, out)
|
||||
extraChannels := []domain.Channel(nil)
|
||||
if channel.Monoforum && channel.LinkedMonoforumID != 0 {
|
||||
if parent, ok := s.channels[channel.LinkedMonoforumID]; ok && !parent.Deleted {
|
||||
extraChannels = append(extraChannels, cloneChannel(parent))
|
||||
}
|
||||
}
|
||||
return domain.ChannelHistory{
|
||||
Channel: channel,
|
||||
Self: member,
|
||||
Channels: extraChannels,
|
||||
Messages: out,
|
||||
Count: count,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) SearchJoinedMessages(_ context.Context, viewerUserID int64, req domain.ChannelGlobalSearchRequest) (domain.ChannelHistory, error) {
|
||||
query := strings.ToLower(strings.TrimSpace(req.Query))
|
||||
if viewerUserID == 0 || (query == "" && !req.MusicOnly) {
|
||||
return domain.ChannelHistory{}, domain.ErrChannelInvalid
|
||||
}
|
||||
if req.Limit <= 0 || req.Limit > domain.MaxChannelGlobalSearchLimit {
|
||||
req.Limit = domain.MaxChannelGlobalSearchLimit
|
||||
}
|
||||
type hit struct {
|
||||
channel domain.Channel
|
||||
message domain.ChannelMessage
|
||||
}
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
hits := make([]hit, 0, req.Limit+1)
|
||||
for channelID, channel := range s.channels {
|
||||
if channel.Deleted {
|
||||
continue
|
||||
}
|
||||
if req.BroadcastsOnly && (!channel.Broadcast || channel.Megagroup) {
|
||||
continue
|
||||
}
|
||||
if req.GroupsOnly && !channel.Megagroup {
|
||||
continue
|
||||
}
|
||||
member, ok := s.members[channelID][viewerUserID]
|
||||
if !ok || member.Status != domain.ChannelMemberActive || member.BannedRights.ViewMessages {
|
||||
continue
|
||||
}
|
||||
if req.HasFolderID {
|
||||
dialog, ok := s.dialogs[viewerUserID][channelID]
|
||||
if !ok || dialog.FolderID != req.FolderID {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, msg := range s.messages[channelID] {
|
||||
if msg.Deleted {
|
||||
continue
|
||||
}
|
||||
if query == "" && !req.MusicOnly || query != "" && strings.TrimSpace(msg.Body) == "" {
|
||||
continue
|
||||
}
|
||||
if member.AvailableMinID > 0 && msg.ID <= member.AvailableMinID {
|
||||
continue
|
||||
}
|
||||
if req.MinDate > 0 && msg.Date <= req.MinDate {
|
||||
continue
|
||||
}
|
||||
if req.MaxDate > 0 && msg.Date >= req.MaxDate {
|
||||
continue
|
||||
}
|
||||
if !channelGlobalSearchAfterCursor(msg, req) {
|
||||
continue
|
||||
}
|
||||
if req.MusicOnly && !msg.Media.IsMusic() {
|
||||
continue
|
||||
}
|
||||
if query != "" && !strings.Contains(strings.ToLower(msg.Body), query) {
|
||||
continue
|
||||
}
|
||||
hits = append(hits, hit{channel: channel, message: cloneChannelMessage(msg)})
|
||||
}
|
||||
}
|
||||
sort.Slice(hits, func(i, j int) bool {
|
||||
a, b := hits[i].message, hits[j].message
|
||||
if a.Date != b.Date {
|
||||
return a.Date > b.Date
|
||||
}
|
||||
if a.ChannelID != b.ChannelID {
|
||||
return a.ChannelID > b.ChannelID
|
||||
}
|
||||
return a.ID > b.ID
|
||||
})
|
||||
out := domain.ChannelHistory{Count: len(hits)}
|
||||
if out.Count > req.Limit {
|
||||
out.Count = req.Limit + 1
|
||||
hits = hits[:req.Limit]
|
||||
}
|
||||
channelSeen := make(map[int64]struct{}, len(hits))
|
||||
for _, h := range hits {
|
||||
out.Messages = append(out.Messages, h.message)
|
||||
if _, ok := channelSeen[h.channel.ID]; ok {
|
||||
continue
|
||||
}
|
||||
channelSeen[h.channel.ID] = struct{}{}
|
||||
out.Channels = append(out.Channels, h.channel)
|
||||
}
|
||||
s.populateChannelMessagesReactionsLocked(viewerUserID, out.Channels, out.Messages)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) GetChannelMessages(_ context.Context, viewerUserID, channelID int64, ids []int) (domain.ChannelHistory, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
// viewer 口径:公开频道非成员可预览读取(与 ListChannelHistory 一致 + 与 PG 实现对齐),
|
||||
// 否则查看他人公开「个人频道」时 channels.getMessages 被拒、资料页整块不显示。
|
||||
channel, member, _, err := s.channelForViewerLocked(viewerUserID, channelID)
|
||||
if err != nil {
|
||||
return domain.ChannelHistory{}, err
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return domain.ChannelHistory{Channel: channel, Self: member}, nil
|
||||
}
|
||||
if len(ids) > domain.MaxGetMessageIDs {
|
||||
return domain.ChannelHistory{}, domain.ErrChannelInvalid
|
||||
}
|
||||
wanted := make(map[int]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
if id <= 0 || id > domain.MaxMessageBoxID {
|
||||
return domain.ChannelHistory{}, domain.ErrMessageIDInvalid
|
||||
}
|
||||
wanted[id] = struct{}{}
|
||||
}
|
||||
messages := make([]domain.ChannelMessage, 0, len(wanted))
|
||||
for _, msg := range s.messages[channelID] {
|
||||
if _, ok := wanted[msg.ID]; !ok {
|
||||
continue
|
||||
}
|
||||
if msg.Deleted || msg.ID <= member.AvailableMinID {
|
||||
continue
|
||||
}
|
||||
messages = append(messages, cloneChannelMessage(msg))
|
||||
}
|
||||
sort.Slice(messages, func(i, j int) bool { return messages[i].ID > messages[j].ID })
|
||||
s.populateChannelMessageRepliesLocked(viewerUserID, channelID, messages)
|
||||
s.populateChannelMessageReactionsLocked(viewerUserID, channel, messages)
|
||||
return domain.ChannelHistory{Channel: channel, Self: member, Messages: messages, Count: len(messages)}, nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) ListStoryMessageForwards(_ context.Context, req domain.StoryMessageForwardListRequest) (domain.StoryMessageForwardList, error) {
|
||||
if req.ViewerUserID == 0 || req.Owner.ID == 0 || req.StoryID <= 0 || req.StoryID > domain.MaxStoryID {
|
||||
return domain.StoryMessageForwardList{}, domain.ErrStoryIDInvalid
|
||||
}
|
||||
if req.Owner.Type != domain.PeerTypeUser && req.Owner.Type != domain.PeerTypeChannel {
|
||||
return domain.StoryMessageForwardList{}, domain.ErrStoryPeerInvalid
|
||||
}
|
||||
if err := domain.ValidateStoryInteractionOffset(req.Offset, false); err != nil {
|
||||
return domain.StoryMessageForwardList{}, err
|
||||
}
|
||||
limit := clampStoryInteractionLimit(req.Limit)
|
||||
cursor := parseStoryInteractionCursor(req.Offset)
|
||||
s.mu.RLock()
|
||||
views := make([]domain.StoryView, 0)
|
||||
for channelID, channel := range s.channels {
|
||||
if channel.Deleted || strings.TrimSpace(channel.Username) == "" || (!channel.Broadcast && !channel.Megagroup) {
|
||||
continue
|
||||
}
|
||||
for _, msg := range s.messages[channelID] {
|
||||
if !messageSharesStory(msg, req.Owner, req.StoryID) {
|
||||
continue
|
||||
}
|
||||
views = append(views, domain.StoryView{
|
||||
Owner: req.Owner,
|
||||
StoryID: req.StoryID,
|
||||
Date: msg.Date,
|
||||
PublicForward: &domain.StoryPublicForward{
|
||||
Message: cloneChannelMessage(msg),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
sortStoryViewsForList(views, req.ReactionsFirst, req.ForwardsFirst)
|
||||
page, nextOffset := pageStoryViews(views, limit, cursor, req.ReactionsFirst, req.ForwardsFirst)
|
||||
return domain.StoryMessageForwardList{Count: len(views), Forwards: page, NextOffset: nextOffset}, nil
|
||||
}
|
||||
|
||||
func messageSharesStory(msg domain.ChannelMessage, owner domain.Peer, storyID int) bool {
|
||||
return !msg.Deleted &&
|
||||
msg.Media != nil &&
|
||||
msg.Media.Kind == domain.MessageMediaKindStory &&
|
||||
msg.Media.Story != nil &&
|
||||
msg.Media.Story.Peer == owner &&
|
||||
msg.Media.Story.ID == storyID
|
||||
}
|
||||
|
||||
func (s *ChannelStore) GetChannelMessageForInlineBot(_ context.Context, botID, channelID int64, id int) (domain.Channel, domain.ChannelMessage, bool, error) {
|
||||
if botID == 0 || channelID == 0 || id <= 0 || id > domain.MaxMessageBoxID {
|
||||
return domain.Channel{}, domain.ChannelMessage{}, false, nil
|
||||
}
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
channel, ok := s.channels[channelID]
|
||||
if !ok || channel.Deleted {
|
||||
return domain.Channel{}, domain.ChannelMessage{}, false, nil
|
||||
}
|
||||
msg, ok := s.findMessageLocked(channelID, id)
|
||||
if !ok || msg.Deleted || msg.Action != nil || msg.ViaBotID != botID {
|
||||
return domain.Channel{}, domain.ChannelMessage{}, false, nil
|
||||
}
|
||||
return channel, cloneChannelMessage(msg), true, nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) GetDiscussionMessage(_ context.Context, viewerUserID, channelID int64, msgID int) (domain.ChannelDiscussionMessage, error) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
source, member, err := s.channelAndMemberLocked(viewerUserID, channelID)
|
||||
if err != nil {
|
||||
return domain.ChannelDiscussionMessage{}, err
|
||||
}
|
||||
msg, ok := s.findMessageLocked(channelID, msgID)
|
||||
if !ok || msg.Deleted || msg.ID <= member.AvailableMinID {
|
||||
return domain.ChannelDiscussionMessage{}, domain.ErrMessageIDInvalid
|
||||
}
|
||||
result := domain.ChannelDiscussionMessage{PostChannel: source, DiscussionChannel: source, Channels: []domain.Channel{source}}
|
||||
targetChannel := source
|
||||
targetMsg := msg
|
||||
targetMember := member
|
||||
if source.Broadcast {
|
||||
if msg.Discussion == nil || msg.Discussion.ChannelID == 0 || msg.Discussion.MessageID == 0 {
|
||||
return result, nil
|
||||
}
|
||||
linked, ok := s.channels[msg.Discussion.ChannelID]
|
||||
if !ok || linked.Deleted {
|
||||
return result, nil
|
||||
}
|
||||
linkedMsg, ok := s.findMessageLocked(linked.ID, msg.Discussion.MessageID)
|
||||
if !ok || linkedMsg.Deleted {
|
||||
return result, nil
|
||||
}
|
||||
targetChannel = linked
|
||||
targetMsg = linkedMsg
|
||||
if linkedMember, ok := s.members[linked.ID][viewerUserID]; ok {
|
||||
targetMember = linkedMember
|
||||
} else {
|
||||
targetMember = domain.ChannelMember{}
|
||||
}
|
||||
result.DiscussionChannel = linked
|
||||
result.Channels = []domain.Channel{source, linked}
|
||||
}
|
||||
items := []domain.ChannelMessage{cloneChannelMessage(targetMsg)}
|
||||
s.populateChannelMessageRepliesLocked(viewerUserID, targetChannel.ID, items)
|
||||
s.populateChannelMessageReactionsLocked(viewerUserID, targetChannel, items)
|
||||
if stats := s.channelMessageRepliesLocked(viewerUserID, targetChannel.ID, targetMsg); stats != nil {
|
||||
result.MaxID = stats.MaxID
|
||||
}
|
||||
result.ReadInboxMaxID = targetMember.ReadInboxMaxID
|
||||
result.ReadOutboxMaxID = targetMember.ReadOutboxMaxID
|
||||
result.UnreadCount = s.channelThreadUnreadCountLocked(viewerUserID, targetChannel.ID, targetMsg.ID, targetMember.ReadInboxMaxID)
|
||||
result.Messages = items
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *ChannelStore) findMessageLocked(channelID int64, id int) (domain.ChannelMessage, bool) {
|
||||
for _, msg := range s.messages[channelID] {
|
||||
if msg.ID == id {
|
||||
return msg, true
|
||||
}
|
||||
}
|
||||
return domain.ChannelMessage{}, false
|
||||
}
|
||||
|
||||
func (s *ChannelStore) findMessageIndexLocked(channelID int64, id int) (int, bool) {
|
||||
for i, msg := range s.messages[channelID] {
|
||||
if msg.ID == id {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func pageChannelMessageHistory(base []domain.ChannelMessage, filter domain.ChannelRepliesFilter, limit int) []domain.ChannelMessage {
|
||||
if limit <= 0 || len(base) == 0 {
|
||||
return nil
|
||||
}
|
||||
switch messageHistoryLoadType(filter.AddOffset, limit) {
|
||||
case messageHistoryLoadForward:
|
||||
return forwardChannelMessageHistory(base, filter, limit)
|
||||
case messageHistoryLoadAround:
|
||||
forwardLimit := -filter.AddOffset
|
||||
if forwardLimit > limit {
|
||||
forwardLimit = limit
|
||||
}
|
||||
backwardLimit := limit + filter.AddOffset
|
||||
if backwardLimit < 0 {
|
||||
backwardLimit = 0
|
||||
}
|
||||
page := make([]domain.ChannelMessage, 0, limit)
|
||||
page = append(page, forwardChannelMessageHistory(base, filter, forwardLimit)...)
|
||||
page = append(page, backwardChannelMessageHistory(base, filter, backwardLimit, true)...)
|
||||
sort.SliceStable(page, func(i, j int) bool { return channelMessageLess(page[i], page[j]) })
|
||||
return page
|
||||
default:
|
||||
start := filter.AddOffset
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
candidates := backwardChannelMessageHistory(base, filter, limit+start, false)
|
||||
if start >= len(candidates) {
|
||||
return nil
|
||||
}
|
||||
return candidates[start:]
|
||||
}
|
||||
}
|
||||
|
||||
func backwardChannelMessageHistory(base []domain.ChannelMessage, filter domain.ChannelRepliesFilter, limit int, includeOffset bool) []domain.ChannelMessage {
|
||||
if limit <= 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]domain.ChannelMessage, 0, limit)
|
||||
for _, msg := range base {
|
||||
if !channelMessageBeforeHistoryOffset(msg, filter, includeOffset) {
|
||||
continue
|
||||
}
|
||||
out = append(out, msg)
|
||||
if len(out) == limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func forwardChannelMessageHistory(base []domain.ChannelMessage, filter domain.ChannelRepliesFilter, limit int) []domain.ChannelMessage {
|
||||
if limit <= 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]domain.ChannelMessage, 0, limit)
|
||||
for i := len(base) - 1; i >= 0; i-- {
|
||||
msg := base[i]
|
||||
if !channelMessageAfterHistoryOffset(msg, filter) {
|
||||
continue
|
||||
}
|
||||
out = append(out, msg)
|
||||
if len(out) == limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
sort.SliceStable(out, func(i, j int) bool { return channelMessageLess(out[i], out[j]) })
|
||||
return out
|
||||
}
|
||||
|
||||
func channelMessageBeforeHistoryOffset(msg domain.ChannelMessage, filter domain.ChannelRepliesFilter, includeOffset bool) bool {
|
||||
if filter.OffsetDate > 0 {
|
||||
if includeOffset {
|
||||
return msg.Date <= filter.OffsetDate
|
||||
}
|
||||
return msg.Date < filter.OffsetDate
|
||||
}
|
||||
if filter.OffsetID <= 0 {
|
||||
return true
|
||||
}
|
||||
if includeOffset {
|
||||
return msg.ID <= filter.OffsetID
|
||||
}
|
||||
return msg.ID < filter.OffsetID
|
||||
}
|
||||
|
||||
func channelMessageAfterHistoryOffset(msg domain.ChannelMessage, filter domain.ChannelRepliesFilter) bool {
|
||||
if filter.OffsetDate > 0 {
|
||||
return msg.Date >= filter.OffsetDate
|
||||
}
|
||||
if filter.OffsetID <= 0 {
|
||||
return false
|
||||
}
|
||||
return msg.ID > filter.OffsetID
|
||||
}
|
||||
|
||||
func channelMessageLess(a, b domain.ChannelMessage) bool {
|
||||
if a.Date != b.Date {
|
||||
return a.Date > b.Date
|
||||
}
|
||||
return a.ID > b.ID
|
||||
}
|
||||
|
||||
func (s *ChannelStore) visibleTopMessageIDLocked(userID int64, channel domain.Channel) int {
|
||||
return s.visibleTopMessageIDForMemberLocked(channel, s.members[channel.ID][userID])
|
||||
}
|
||||
|
||||
func (s *ChannelStore) visibleTopMessageIDForMemberLocked(channel domain.Channel, member domain.ChannelMember) int {
|
||||
for i := len(s.messages[channel.ID]) - 1; i >= 0; i-- {
|
||||
msg := s.messages[channel.ID][i]
|
||||
if !msg.Deleted && msg.ID > member.AvailableMinID {
|
||||
return msg.ID
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *ChannelStore) topicHasVisibleMessagesLocked(channelID int64, topicID int) bool {
|
||||
for _, msg := range s.messages[channelID] {
|
||||
if msg.Deleted {
|
||||
continue
|
||||
}
|
||||
if msg.ID == topicID || (msg.ReplyTo != nil && msg.ReplyTo.TopMessageID == topicID) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue