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 }