Files
dislate/bot/events/messages.go

441 lines
11 KiB
Go

package events
import (
e "errors"
"log/slog"
"slices"
"sync"
"forge.capytal.company/capytal/dislate/bot/events/errors"
"forge.capytal.company/capytal/dislate/bot/gconf"
"forge.capytal.company/capytal/dislate/guilddb"
"forge.capytal.company/capytal/dislate/translator"
dgo "github.com/bwmarrin/discordgo"
)
type MessageCreate struct {
db gconf.DB
translator translator.Translator
}
func NewMessageCreate(db gconf.DB, t translator.Translator) MessageCreate {
return MessageCreate{db, t}
}
func (h MessageCreate) Serve(
s *dgo.Session,
ev *dgo.MessageCreate,
) errors.EventErr {
if ev.Message.Author.Bot || ev.Type != dgo.MessageTypeDefault {
return nil
}
log := gconf.GetLogger(ev.Message.GuildID, s, h.db)
return h.sendMessage(log, s, ev.Message)
}
func (h MessageCreate) sendMessage(
log *slog.Logger,
s *dgo.Session,
msg *dgo.Message,
) errors.EventErr {
everr := errors.NewMessageErr[*dgo.MessageCreate](s, msg, log)
ch, err := h.db.Channel(msg.GuildID, msg.ChannelID)
if e.Is(err, guilddb.ErrNotFound) {
log.Debug("Channel is not in database, ignoring.",
slog.String("guild", msg.GuildID),
slog.String("channel", msg.ChannelID),
slog.String("message", msg.ID),
)
return nil
} else if err != nil {
return everr.Join(e.New("Failed to get channel from database"), err)
}
gc, err := h.db.ChannelGroup(ch.GuildID, ch.ID)
if e.Is(err, guilddb.ErrNotFound) {
log.Debug("Channel is not in a group, ignoring.",
slog.String("guild", msg.GuildID),
slog.String("channel", msg.ChannelID),
slog.String("message", msg.ID),
)
return nil
} else if err != nil {
return everr.Join(e.New("Failed to get channel group from database"), err)
}
_, err = getMessage(h.db, msg, ch.Language)
if err != nil {
return everr.Join(e.New("Failed to get/add message to database"), err)
}
var wg sync.WaitGroup
errs := make(chan errors.EventErr)
for _, c := range gc {
if c.ID == ch.ID && c.GuildID == ch.GuildID {
continue
}
wg.Add(1)
go func(c guilddb.Channel, errs chan<- errors.EventErr) {
defer wg.Done()
everr := errors.NewMessageErr[*dgo.MessageCreate](s, msg, log)
everr.AddData("TranslatedChannelID", c.ID)
dch, err := s.Channel(c.ID)
var channelID string
if err != nil {
errs <- everr.Join(e.New("Failed to get information about translated channel"), err)
return
} else if dch.IsThread() {
channelID = dch.ParentID
} else {
channelID = dch.ID
}
uw, err := getUserWebhook(s, channelID, msg.Author)
if err != nil {
errs <- everr.Join(e.New("Failed to get/set user webhook for translated channel"), err)
return
}
t, err := h.translator.Translate(ch.Language, c.Language, msg.Content)
if err != nil {
errs <- everr.Join(e.New("Error while trying to translate message"), err)
return
}
var tdm *dgo.Message
if dch.IsThread() {
tdm, err = s.WebhookThreadExecute(uw.ID, uw.Token, true, dch.ID, &dgo.WebhookParams{
AvatarURL: msg.Author.AvatarURL(""),
Username: msg.Author.GlobalName,
Content: t,
})
} else {
tdm, err = s.WebhookExecute(uw.ID, uw.Token, true, &dgo.WebhookParams{
AvatarURL: msg.Author.AvatarURL(""),
Username: msg.Author.GlobalName,
Content: t,
})
}
if err != nil {
everr.AddData("WebhookID", uw.ID)
errs <- everr.Join(e.New("Error while trying to execute user webhook"), err)
return
}
if tdm.GuildID == "" {
tdm.GuildID = msg.GuildID
}
_, err = getTranslatedMessage(h.db, tdm, msg, c.Language)
if err != nil {
everr.AddData("WebhookID", uw.ID)
everr.AddData("TranslatedMessageID", uw.ID)
errs <- everr.Join(e.New("Error while trying to add translated message to dabase"), err)
return
}
}(c, errs)
}
wg.Wait()
for err := range errs {
everr.Join(err)
}
if len(errs) > 0 {
return everr
}
return nil
}
type MessageUpdate struct {
db gconf.DB
translator translator.Translator
}
func NewMessageUpdate(db gconf.DB, t translator.Translator) MessageUpdate {
return MessageUpdate{db, t}
}
func (h MessageUpdate) Serve(s *dgo.Session, ev *dgo.MessageUpdate) errors.EventErr {
if ev.Message.Author.Bot || ev.Type != dgo.MessageTypeDefault {
return nil
}
log := gconf.GetLogger(ev.Message.GuildID, s, h.db)
everr := errors.NewMessageErr[*dgo.MessageUpdate](s, ev.Message, log)
msg, err := h.db.Message(ev.Message.GuildID, ev.Message.ChannelID, ev.Message.ID)
if e.Is(err, guilddb.ErrNotFound) {
log.Debug("Message is not in database, ignoring.",
slog.String("guild", ev.Message.GuildID),
slog.String("channel", ev.Message.ChannelID),
)
return nil
} else if err != nil {
return everr.Join(e.New("Failed to get message from database"), err)
}
tmsgs, err := h.db.MessagesWithOrigin(msg.GuildID, msg.ChannelID, msg.ID)
if e.Is(err, guilddb.ErrNotFound) {
log.Debug("No translated message found, ignoring.",
slog.String("guild", ev.GuildID),
slog.String("channel", ev.ChannelID),
)
return nil
} else if err != nil {
return everr.Join(e.New("Failed to get translated messages from database"), err)
}
var wg sync.WaitGroup
errs := make(chan errors.EventErr)
for _, m := range tmsgs {
if m.ID == msg.ID && m.GuildID == msg.GuildID {
continue
}
wg.Add(1)
go func(m guilddb.Message, errs chan<- errors.EventErr) {
defer wg.Done()
everr := errors.NewMessageErr[*dgo.MessageUpdate](s, ev.Message, log)
everr.AddData("TranslatedMessageID", m.ID)
everr.AddData("TranslatedChannelID", m.ChannelID)
var channelID string
if dch, err := s.Channel(m.ChannelID); err != nil {
errs <- everr.Join(e.New("Failed to get information about translated channel"), err)
return
} else if dch.IsThread() {
channelID = dch.ParentID
} else {
channelID = dch.ID
}
uw, err := getUserWebhook(s, channelID, ev.Message.Author)
if err != nil {
errs <- everr.Join(e.New("Failed to get/set user webhook for translated channel"), err)
return
}
t, err := h.translator.Translate(msg.Language, m.Language, ev.Message.Content)
if err != nil {
errs <- everr.Join(e.New("Error while trying to translate message"), err)
return
}
_, err = s.WebhookMessageEdit(uw.ID, uw.Token, m.ID, &dgo.WebhookEdit{
Content: &t,
})
if err != nil {
everr.AddData("WebhookID", uw.ID)
errs <- everr.Join(e.New("Error while trying to execute user webhook"), err)
return
}
}(m, errs)
}
wg.Wait()
for err := range errs {
everr.Join(err)
}
if len(errs) > 0 {
return everr
}
return nil
}
type MessageDelete struct {
db gconf.DB
}
func NewMessageDelete(db gconf.DB) MessageDelete {
return MessageDelete{db}
}
func (h MessageDelete) Serve(s *dgo.Session, ev *dgo.MessageDelete) errors.EventErr {
if ev.Type != dgo.MessageTypeDefault {
return nil
}
log := gconf.GetLogger(ev.Message.GuildID, s, h.db)
everr := errors.NewMessageErr[*dgo.MessageUpdate](s, ev.Message, log)
msg, err := h.db.Message(ev.Message.GuildID, ev.Message.ChannelID, ev.Message.ID)
if e.Is(err, guilddb.ErrNotFound) {
log.Debug("Message is not in database, ignoring.",
slog.String("guild", ev.Message.GuildID),
slog.String("channel", ev.Message.ChannelID),
)
return nil
} else if err != nil {
return everr.Join(e.New("Failed to get message from database"), err)
}
var originChannelID, originID string
if msg.OriginID != nil && msg.OriginChannelID != nil {
oMsg, err := h.db.Message(ev.Message.GuildID, *msg.OriginChannelID, *msg.OriginID)
if err != nil {
originChannelID = *msg.OriginChannelID
originID = *msg.OriginID
} else {
msg = oMsg
originChannelID = oMsg.ChannelID
originID = oMsg.ID
}
} else {
originChannelID = msg.ChannelID
originID = msg.ID
}
tmsgs, err := h.db.MessagesWithOrigin(msg.GuildID, originChannelID, originID)
if e.Is(err, guilddb.ErrNotFound) {
log.Debug("No translated message found, ignoring.",
slog.String("guild", ev.GuildID),
slog.String("channel", ev.ChannelID),
)
return nil
} else if err != nil {
return everr.Join(e.New("Failed to get translated messages from database"), err)
}
for _, m := range tmsgs {
if m.ID == msg.ID && m.ChannelID == msg.ChannelID && m.GuildID == msg.GuildID {
continue
}
go func(m guilddb.Message) {
if err := s.ChannelMessageDelete(m.ChannelID, m.ID); err != nil {
log.Warn("Failed to delete message",
slog.String("channel", m.ChannelID),
slog.String("message", m.ID),
slog.String("err", err.Error()),
)
}
}(m)
}
if err := s.ChannelMessageDelete(msg.ChannelID, msg.ID); err != nil {
log.Warn("Failed to delete message",
slog.String("channel", msg.ChannelID),
slog.String("message", msg.ID),
slog.String("err", err.Error()),
)
}
var wg sync.WaitGroup
errs := make(chan errors.EventErr)
for _, m := range append(tmsgs, msg) {
go func(m guilddb.Message, errs chan<- errors.EventErr) {
everr := errors.NewMessageErr[*dgo.MessageUpdate](s, ev.Message, log)
everr.AddData("TranslatedMessageID", m.ID)
everr.AddData("TranslatedChannelID", m.ChannelID)
err := h.db.MessageDeleteFromChannel(guilddb.NewChannel(m.GuildID, m.ID, translator.EN))
if err != nil && !e.Is(err, guilddb.ErrNoAffect) {
errs <- everr.Join(e.New("Failed to delete message from channel"), err)
return
}
err = h.db.ChannelDelete(guilddb.NewChannel(m.GuildID, m.ID, translator.EN))
if err != nil && !e.Is(err, guilddb.ErrNoAffect) {
errs <- everr.Join(e.New("Failed to delete message thread from channel"), err)
return
}
}(m, errs)
}
wg.Wait()
everrs := make([]error, 0, len(errs))
for err := range errs {
everrs = append(everrs, err)
}
if len(errs) > 0 {
return everr.Join(everrs...)
}
if err := h.db.MessageDelete(guilddb.NewMessage(msg.GuildID, msg.ChannelID, msg.ID, translator.EN)); err != nil {
return everr.Join(e.New("Failed to delete message from database"), err)
}
return nil
}
func getUserWebhook(s *dgo.Session, channelID string, user *dgo.User) (*dgo.Webhook, error) {
whName := "DISLATE_USER_WEBHOOK_" + user.ID
ws, err := s.ChannelWebhooks(channelID)
if err != nil {
return &dgo.Webhook{}, err
}
wi := slices.IndexFunc(ws, func(w *dgo.Webhook) bool {
return w.Name == whName
})
if wi > -1 {
return ws[wi], nil
}
w, err := s.WebhookCreate(channelID, whName, user.AvatarURL(""))
if err != nil {
return &dgo.Webhook{}, err
}
return w, nil
}
func getMessage(db gconf.DB, m *dgo.Message, lang translator.Language) (guilddb.Message, error) {
msg, err := db.Message(m.GuildID, m.ChannelID, m.ID)
if e.Is(err, guilddb.ErrNotFound) {
if err := db.MessageInsert(guilddb.NewMessage(m.GuildID, m.ChannelID, m.ID, lang)); err != nil {
return guilddb.Message{}, err
}
msg, err = db.Message(m.GuildID, m.ChannelID, m.ID)
if err != nil {
return guilddb.Message{}, err
}
}
return msg, nil
}
func getTranslatedMessage(
db gconf.DB,
m, original *dgo.Message,
lang translator.Language,
) (guilddb.Message, error) {
msg, err := db.Message(m.GuildID, m.ChannelID, m.ID)
if e.Is(err, guilddb.ErrNotFound) {
if err := db.MessageInsert(guilddb.NewTranslatedMessage(
m.GuildID,
m.ChannelID,
m.ID,
lang,
original.ChannelID,
original.ID,
)); err != nil {
return guilddb.Message{}, err
}
msg, err = db.Message(m.GuildID, m.ChannelID, m.ID)
if err != nil {
return guilddb.Message{}, err
}
} else if err != nil {
return guilddb.Message{}, err
}
return msg, nil
}