cchat-discord/internal/discord/private/hub/messages.go

204 lines
5.0 KiB
Go

package hub
import (
"context"
"sync"
"github.com/diamondburned/arikawa/v2/discord"
"github.com/diamondburned/arikawa/v2/gateway"
"github.com/diamondburned/cchat"
"github.com/diamondburned/cchat-discord/internal/discord/channel/message/send/complete"
"github.com/diamondburned/cchat-discord/internal/discord/channel/shared"
"github.com/diamondburned/cchat-discord/internal/discord/message"
"github.com/diamondburned/cchat-discord/internal/discord/state"
"github.com/diamondburned/cchat-discord/internal/discord/state/nonce"
"github.com/diamondburned/cchat-discord/internal/funcutil"
"github.com/diamondburned/cchat/utils/empty"
)
const maxMessages = 100
type messageList []discord.Message
func (list messageList) idx(id discord.MessageID) int {
for i, msg := range list {
if msg.ID == id {
return i
}
}
return -1
}
func (list *messageList) append(msg discord.Message) {
*list = append(*list, msg)
// cap the length
if len(*list) > maxMessages {
copy(*list, (*list)[1:]) // shift left once
(*list)[len(*list)-1] = discord.Message{} // nil out last to not memory leak
*list = (*list)[:len(*list)-1] // slice it away
}
}
func (list *messageList) swap(newMsg discord.Message) {
if idx := list.idx(newMsg.ID); idx > -1 {
(*list)[idx] = newMsg
}
}
func (list *messageList) delete(id discord.MessageID) {
if idx := list.idx(id); idx > -1 {
*list = append((*list)[:idx], (*list)[idx+1:]...)
}
}
type Messages struct {
empty.Messenger
state *state.Instance
acList *activeList
sentMsgs *nonce.Set
sender *Sender
msgMutex sync.Mutex
messages messageList
cancel func()
}
func NewMessages(s *state.Instance, acList *activeList, adder ChannelAdder) *Messages {
var sentMsgs nonce.Set
hubServer := &Messages{
state: s,
acList: acList,
sentMsgs: &sentMsgs,
sender: &Sender{
adder: adder,
acList: acList,
sentMsgs: &sentMsgs,
state: s,
},
messages: make(messageList, 0, 100),
}
hubServer.sender.completers.Prefixes = complete.CompleterPrefixes{
':': func(word string) []cchat.CompletionEntry {
return complete.Emojis(s, 0, word)
},
'@': func(word string) []cchat.CompletionEntry {
if word != "" {
return complete.AllUsers(s, word)
}
hubServer.msgMutex.Lock()
defer hubServer.msgMutex.Unlock()
return complete.MessageMentions(hubServer.messages)
},
'#': func(word string) []cchat.CompletionEntry {
return complete.DMChannels(s, word)
},
}
hubServer.cancel = funcutil.JoinCancels(
s.AddHandler(func(msg *gateway.MessageCreateEvent) {
if msg.GuildID.IsValid() || acList.isActive(msg.ChannelID) {
return
}
// We're not adding back messages we sent here, since we already
// have a separate channel for that.
hubServer.msgMutex.Lock()
hubServer.messages.append(msg.Message)
hubServer.msgMutex.Unlock()
}),
s.AddHandler(func(update *gateway.MessageUpdateEvent) {
if update.GuildID.IsValid() || acList.isActive(update.ChannelID) {
return
}
// The event itself is unreliable, so we must rely on the state.
m, err := hubServer.state.Message(update.ChannelID, update.ID)
if err != nil {
return
}
hubServer.msgMutex.Lock()
hubServer.messages.swap(*m)
hubServer.msgMutex.Unlock()
}),
s.AddHandler(func(del *gateway.MessageDeleteEvent) {
if del.GuildID.IsValid() || acList.isActive(del.ChannelID) {
return
}
hubServer.msgMutex.Lock()
hubServer.messages.delete(del.ID)
hubServer.msgMutex.Unlock()
}),
)
return hubServer
}
func (msgs *Messages) JoinServer(ctx context.Context, ct cchat.MessagesContainer) (func(), error) {
msgs.msgMutex.Lock()
for _, msg := range msgs.messages {
ct.CreateMessage(message.NewDirectMessage(msg, msgs.state))
}
msgs.msgMutex.Unlock()
// Bind the handler.
return funcutil.JoinCancels(
msgs.state.AddHandler(func(msg *gateway.MessageCreateEvent) {
if msg.GuildID.IsValid() {
return
}
var isReply = false
if msgs.acList.isActive(msg.ChannelID) {
if !msgs.sentMsgs.HasAndDelete(msg.Nonce) {
return
}
isReply = true
}
var author = message.NewUser(msg.Author, msgs.state)
if isReply {
c, err := msgs.state.Channel(msg.ChannelID)
if err == nil {
switch c.Type {
case discord.DirectMessage:
author.AddUserReply(c.DMRecipients[0], msgs.state)
case discord.GroupDM:
author.AddReply(shared.ChannelName(*c))
}
}
}
ct.CreateMessage(message.NewMessage(msg.Message, msgs.state, author))
msgs.state.ReadState.MarkRead(msg.ChannelID, msg.ID)
}),
msgs.state.AddHandler(func(update *gateway.MessageUpdateEvent) {
if update.GuildID.IsValid() || msgs.acList.isActive(update.ChannelID) {
return
}
ct.UpdateMessage(message.NewMessageUpdateContent(update.Message, msgs.state))
}),
msgs.state.AddHandler(func(del *gateway.MessageDeleteEvent) {
if del.GuildID.IsValid() || msgs.acList.isActive(del.ChannelID) {
return
}
ct.DeleteMessage(message.NewHeaderDelete(del))
}),
), nil
}
func (msgs *Messages) AsSender() cchat.Sender { return msgs.sender }