mirror of
https://github.com/diamondburned/arikawa.git
synced 2024-11-20 05:43:21 +00:00
a808b52f00
* Store,State: Add update param to all store.XXXStore.XXXSet methods * State: add paginating Messages * Store: Fix test error * store: merge shouldPrependMessage and shouldAppendMessage into single messageInsertPosition
237 lines
5.9 KiB
Go
237 lines
5.9 KiB
Go
package defaultstore
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/diamondburned/arikawa/v3/discord"
|
|
"github.com/diamondburned/arikawa/v3/internal/moreatomic"
|
|
"github.com/diamondburned/arikawa/v3/state/store"
|
|
)
|
|
|
|
type Message struct {
|
|
channels moreatomic.Map
|
|
maxMsgs int
|
|
}
|
|
|
|
var _ store.MessageStore = (*Message)(nil)
|
|
|
|
type messages struct {
|
|
mut sync.Mutex
|
|
messages []discord.Message
|
|
}
|
|
|
|
func NewMessage(maxMsgs int) *Message {
|
|
return &Message{
|
|
channels: *moreatomic.NewMap(func() interface{} {
|
|
return &messages{
|
|
messages: []discord.Message{}, // never use a nil slice
|
|
}
|
|
}),
|
|
maxMsgs: maxMsgs,
|
|
}
|
|
}
|
|
|
|
func (s *Message) Reset() error {
|
|
return s.channels.Reset()
|
|
}
|
|
|
|
func (s *Message) Message(chID discord.ChannelID, mID discord.MessageID) (*discord.Message, error) {
|
|
iv, ok := s.channels.Load(chID)
|
|
if !ok {
|
|
return nil, store.ErrNotFound
|
|
}
|
|
|
|
msgs := iv.(*messages)
|
|
|
|
msgs.mut.Lock()
|
|
defer msgs.mut.Unlock()
|
|
|
|
for _, m := range msgs.messages {
|
|
if m.ID == mID {
|
|
return &m, nil
|
|
}
|
|
}
|
|
|
|
return nil, store.ErrNotFound
|
|
}
|
|
|
|
func (s *Message) Messages(channelID discord.ChannelID) ([]discord.Message, error) {
|
|
iv, ok := s.channels.Load(channelID)
|
|
if !ok {
|
|
return nil, store.ErrNotFound
|
|
}
|
|
|
|
msgs := iv.(*messages)
|
|
|
|
msgs.mut.Lock()
|
|
defer msgs.mut.Unlock()
|
|
|
|
return append([]discord.Message(nil), msgs.messages...), nil
|
|
}
|
|
|
|
func (s *Message) MaxMessages() int {
|
|
return s.maxMsgs
|
|
}
|
|
|
|
func (s *Message) MessageSet(message discord.Message, update bool) error {
|
|
if s.maxMsgs <= 0 {
|
|
return nil
|
|
}
|
|
|
|
iv, _ := s.channels.LoadOrStore(message.ChannelID)
|
|
|
|
msgs := iv.(*messages)
|
|
|
|
msgs.mut.Lock()
|
|
defer msgs.mut.Unlock()
|
|
|
|
if update {
|
|
// Opt for a linear latest-to-oldest search in favor of something like
|
|
// sort.Search, since more recent messages are more likely to be edited
|
|
// than older ones.
|
|
for i, oldMessage := range msgs.messages {
|
|
// We found a match, update it.
|
|
if oldMessage.ID == message.ID {
|
|
DiffMessage(message, &oldMessage)
|
|
msgs.messages[i] = oldMessage // Now updated.
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
if len(msgs.messages) == 0 {
|
|
msgs.messages = []discord.Message{message}
|
|
}
|
|
|
|
if pos := messageInsertPosition(message, msgs.messages); pos < 0 {
|
|
// Messages are full, drop the oldest messages to make room.
|
|
if len(msgs.messages) == s.maxMsgs {
|
|
copy(msgs.messages[1:], msgs.messages)
|
|
msgs.messages[0] = message
|
|
} else {
|
|
msgs.messages = append([]discord.Message{message}, msgs.messages...)
|
|
}
|
|
} else if pos > 0 && len(msgs.messages) < s.maxMsgs {
|
|
msgs.messages = append(msgs.messages, message)
|
|
}
|
|
|
|
// We already have this message or we can't append any more messages.
|
|
return nil
|
|
}
|
|
|
|
// messageInsertPosition checks if the message should be appended or prepended
|
|
// into the passed messages, ordered by time of creation from latest to oldest.
|
|
// If the message should be prepended, messageInsertPosition returns -1, and if
|
|
// the message should be appended it returns 1. As a third option it returns 0,
|
|
// if the message should not be added to the slice, because it would disrupt
|
|
// the order.
|
|
//
|
|
// messageInsertPosition is biased as it will recommend adding the message even
|
|
// if timestamps just match, even though the true order cannot be determined in
|
|
// that case.
|
|
func messageInsertPosition(target discord.Message, messages []discord.Message) int8 {
|
|
var (
|
|
targetTime = target.ID.Time()
|
|
firstTime = messages[0].ID.Time()
|
|
lastTime = messages[len(messages)-1].ID.Time()
|
|
)
|
|
|
|
if targetTime.After(firstTime) {
|
|
return -1
|
|
} else if targetTime.Before(lastTime) {
|
|
return 1
|
|
}
|
|
|
|
// Two cases remain, the timestamp is equal to either the latest or oldest
|
|
// message, or the message is already contained in message.
|
|
// So we compare timestamps. If they are equal, make sure messages doesn't
|
|
// contain a message with the same id, in order to prevent insertion of a
|
|
// duplicate. If they are not equal, we return 0 as the message would
|
|
// violate the order of messages.
|
|
// ID timestamps are used, as they provide millisecond accuracy in contrast
|
|
// to the second accuracy of discord.Message.Timestamp.
|
|
if targetTime.Equal(firstTime) {
|
|
// Only iterate as long as timestamps are equal, or there are no more
|
|
// messages.
|
|
for i := 0; i < len(messages) && targetTime.Equal(messages[i].ID.Time()); i++ {
|
|
// Duplicate, don't insert.
|
|
if messages[i].ID == target.ID {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// No duplicate of message found, so safe to prepend.
|
|
return -1
|
|
} else if targetTime.Equal(lastTime) {
|
|
// Only iterate as long as timestamps are equal, or there are no more
|
|
// messages.
|
|
for i := len(messages) - 1; i >= 0 && targetTime.Equal(messages[i].ID.Time()); i-- {
|
|
// Duplicate, don't insert.
|
|
if messages[i].ID == target.ID {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// No duplicate of message found, so safe to append.
|
|
return 1
|
|
}
|
|
|
|
// Message would violate the order of messages, don't add it.
|
|
return 0
|
|
}
|
|
|
|
// DiffMessage fills non-empty fields from src to dst.
|
|
func DiffMessage(src discord.Message, dst *discord.Message) {
|
|
// Thanks, Discord.
|
|
if src.Content != "" {
|
|
dst.Content = src.Content
|
|
}
|
|
if src.EditedTimestamp.IsValid() {
|
|
dst.EditedTimestamp = src.EditedTimestamp
|
|
}
|
|
if src.Mentions != nil {
|
|
dst.Mentions = src.Mentions
|
|
}
|
|
if src.Embeds != nil {
|
|
dst.Embeds = src.Embeds
|
|
}
|
|
if src.Attachments != nil {
|
|
dst.Attachments = src.Attachments
|
|
}
|
|
if src.Timestamp.IsValid() {
|
|
dst.Timestamp = src.Timestamp
|
|
}
|
|
if src.Author.ID.IsValid() {
|
|
dst.Author = src.Author
|
|
}
|
|
if src.Reactions != nil {
|
|
dst.Reactions = src.Reactions
|
|
}
|
|
if src.Components != nil {
|
|
dst.Components = src.Components
|
|
}
|
|
}
|
|
|
|
func (s *Message) MessageRemove(channelID discord.ChannelID, messageID discord.MessageID) error {
|
|
iv, ok := s.channels.Load(channelID)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
msgs := iv.(*messages)
|
|
|
|
msgs.mut.Lock()
|
|
defer msgs.mut.Unlock()
|
|
|
|
for i, m := range msgs.messages {
|
|
if m.ID == messageID {
|
|
msgs.messages = append(msgs.messages[:i], msgs.messages[i+1:]...)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|