package discord import ( "context" "sync" "text/template" "time" "gitea.dwysokinski.me/twhelp/dcbot/internal/discord/internal/discordi18n" "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" "github.com/bwmarrin/discordgo" "github.com/nicksnyder/go-i18n/v2/i18n" "go.uber.org/zap" ) const ( executeMonitorsJobTimeout = 2 * time.Minute colorGreen = 0x00ff00 colorRed = 0xff0000 ) type executeMonitorsJob struct { s *discordgo.Session svc GroupService localizer *discordi18n.Localizer logger *zap.Logger } func (e *executeMonitorsJob) Run() { ctx, cancel := context.WithTimeout(context.Background(), executeMonitorsJobTimeout) defer cancel() start := time.Now() notifications, err := e.svc.Execute(ctx) if err != nil { e.logger.Error( "something went wrong while executing monitors", zap.Error(err), zap.Duration("duration", time.Since(start)), ) return } convertedNotifications, err := (ennoblementNotificationToMessageEmbedConverter{notifications, e.localizer}).convert() if err != nil { e.logger.Error("couldn't convert []domain.EnnoblementNotification to []*discordgo.MessageEmbed", zap.Error(err)) return } var wg sync.WaitGroup for ch, embeds := range convertedNotifications { wg.Add(1) go func(ch string, embeds []*discordgo.MessageEmbed) { defer wg.Done() for _, embed := range embeds { _, sendErr := e.s.ChannelMessageSendEmbed(ch, embed) if sendErr != nil { e.logger.Warn("couldn't send notification", zap.String("channel", ch), zap.Error(sendErr)) } } }(ch, embeds) } wg.Wait() e.logger.Info( "notifications sent", zap.Int("notifications", len(notifications)), zap.Duration("duration", time.Since(start)), ) } type ennoblementNotificationToMessageEmbedConverter struct { ns []domain.EnnoblementNotification localizer *discordi18n.Localizer } func (c ennoblementNotificationToMessageEmbedConverter) convert() (map[string][]*discordgo.MessageEmbed, error) { m := make(map[string]map[domain.EnnoblementNotificationType][]*discordgo.MessageEmbed) timestamp := formatTimestamp(time.Now()) for _, n := range c.ns { if _, ok := m[n.ChannelID]; !ok { m[n.ChannelID] = make(map[domain.EnnoblementNotificationType][]*discordgo.MessageEmbed) } embeds := m[n.ChannelID][n.Type] description, err := c.buildDescription(n) if err != nil { return nil, err } if l := len(embeds); l == 0 || len(embeds[l-1].Description)+len(description) > embedDescriptionCharLimit { embeds = append(embeds, &discordgo.MessageEmbed{ Color: c.mapNotificationTypeToColor(n.Type), Type: discordgo.EmbedTypeRich, Timestamp: timestamp, }) } if len(embeds[len(embeds)-1].Description) > 0 { description = "\n" + description } embeds[len(embeds)-1].Description += description m[n.ChannelID][n.Type] = embeds } res := make(map[string][]*discordgo.MessageEmbed, len(m)) for ch, innerMap := range m { cnt := 0 for _, embeds := range innerMap { cnt += len(embeds) } res[ch] = make([]*discordgo.MessageEmbed, 0, cnt) for _, embeds := range innerMap { res[ch] = append(res[ch], embeds...) } } return res, nil } func (c ennoblementNotificationToMessageEmbedConverter) buildDescription(n domain.EnnoblementNotification) (string, error) { return c.localizer.Localize(n.LanguageTag, &i18n.LocalizeConfig{ MessageID: "job.execute-monitors.embed.description", TemplateData: map[string]any{ "Ennoblement": n.Ennoblement, }, Funcs: template.FuncMap{ "buildPlayerMarkdown": buildPlayerMarkdown, "buildLink": buildLink, }, }) } func (c ennoblementNotificationToMessageEmbedConverter) mapNotificationTypeToColor(t domain.EnnoblementNotificationType) int { switch t { case domain.EnnoblementNotificationTypeGain: return colorGreen case domain.EnnoblementNotificationTypeLoss: fallthrough default: return colorRed } }