From 1153829a70649d705007a28e17b6ed0ed8f03475 Mon Sep 17 00:00:00 2001 From: Kichiyaki Date: Sun, 18 Jul 2021 10:28:28 +0200 Subject: [PATCH] refactor cron initialization --- cron/cron.go | 326 ++++++++++++++++++++++++++++++++++++++++++++++-- cron/handler.go | 316 ---------------------------------------------- main.go | 19 +-- 3 files changed, 323 insertions(+), 338 deletions(-) delete mode 100644 cron/handler.go diff --git a/cron/cron.go b/cron/cron.go index 1c73297..fabd007 100644 --- a/cron/cron.go +++ b/cron/cron.go @@ -1,7 +1,13 @@ package cron import ( + "context" + "fmt" "github.com/Kichiyaki/appmode" + "github.com/nicksnyder/go-i18n/v2/i18n" + "github.com/pkg/errors" + "github.com/robfig/cron/v3" + "github.com/tribalwarshelp/shared/tw/twmodel" "time" "github.com/sirupsen/logrus" @@ -9,10 +15,11 @@ import ( "github.com/tribalwarshelp/dcbot/discord" "github.com/tribalwarshelp/dcbot/group" + "github.com/tribalwarshelp/dcbot/message" + "github.com/tribalwarshelp/dcbot/model" "github.com/tribalwarshelp/dcbot/observation" "github.com/tribalwarshelp/dcbot/server" - - "github.com/robfig/cron/v3" + "github.com/tribalwarshelp/dcbot/util/twutil" ) var log = logrus.WithField("package", "cron") @@ -26,8 +33,26 @@ type Config struct { Status string } -func Attach(c *cron.Cron, cfg Config) { - h := &handler{ +type Cron struct { + *cron.Cron + lastEnnoblementAt map[string]time.Time + serverRepo server.Repository + observationRepo observation.Repository + groupRepo group.Repository + discord *discord.Session + api *sdk.SDK + status string +} + +func New(cfg Config) *Cron { + c := &Cron{ + Cron: cron.New( + cron.WithChain( + cron.SkipIfStillRunning( + cron.PrintfLogger(log), + ), + ), + ), lastEnnoblementAt: make(map[string]time.Time), serverRepo: cfg.ServerRepo, observationRepo: cfg.ObservationRepo, @@ -36,12 +61,13 @@ func Attach(c *cron.Cron, cfg Config) { api: cfg.API, status: cfg.Status, } - checkEnnoblements := trackDuration(log, h.checkEnnoblements, "checkEnnoblements") - checkBotServers := trackDuration(log, h.checkBotServers, "checkBotServers") + + checkEnnoblements := trackDuration(log, c.checkEnnoblements, "checkEnnoblements") + checkBotServers := trackDuration(log, c.checkBotServers, "checkBotServers") deleteClosedTribalWarsServers := trackDuration(log, - h.deleteClosedTWServers, + c.deleteClosedTWServers, "deleteClosedTWServers") - updateBotStatus := trackDuration(log, h.updateBotStatus, "updateBotStatus") + updateBotStatus := trackDuration(log, c.updateBotStatus, "updateBotStatus") c.AddFunc("@every 1m", checkEnnoblements) c.AddFunc("@every 30m", checkBotServers) c.AddFunc("@every 2h10m", deleteClosedTribalWarsServers) @@ -54,4 +80,288 @@ func Attach(c *cron.Cron, cfg Config) { checkEnnoblements() } }() + + return c +} + +func (c *Cron) loadEnnoblements(servers []string) (map[string]ennoblements, error) { + m := make(map[string]ennoblements) + + if len(servers) == 0 { + return m, nil + } + + query := "" + + for _, s := range servers { + lastEnnoblementAt, ok := c.lastEnnoblementAt[s] + if !ok { + lastEnnoblementAt = time.Now().Add(-1 * time.Minute) + c.lastEnnoblementAt[s] = lastEnnoblementAt + } + if appmode.Equals(appmode.DevelopmentMode) { + lastEnnoblementAt = time.Now().Add(-1 * time.Hour * 2) + } + lastEnnoblementAtJSON, err := lastEnnoblementAt.MarshalJSON() + if err != nil { + continue + } + query += fmt.Sprintf(` + %s: ennoblements(server: "%s", filter: { ennobledAtGT: %s }) { + items { + %s + ennobledAt + } + } + `, s, + s, + string(lastEnnoblementAtJSON), + sdk.EnnoblementInclude{ + NewOwner: true, + Village: true, + NewOwnerInclude: sdk.PlayerInclude{ + Tribe: true, + }, + OldOwner: true, + OldOwnerInclude: sdk.PlayerInclude{ + Tribe: true, + }, + }.String()) + } + + resp := make(map[string]*sdk.EnnoblementList) + if err := c.api.Post(fmt.Sprintf(`query { %s }`, query), &resp); err != nil { + return m, errors.Wrap(err, "loadEnnoblements") + } + + for s, singleServerResp := range resp { + if singleServerResp == nil { + continue + } + m[s] = singleServerResp.Items + lastEnnoblement := m[s].getLastEnnoblement() + if lastEnnoblement != nil { + c.lastEnnoblementAt[s] = lastEnnoblement.EnnobledAt + } + } + + return m, nil +} + +func (c *Cron) loadVersions(servers []string) ([]*twmodel.Version, error) { + var versionCodes []twmodel.VersionCode + cache := make(map[twmodel.VersionCode]bool) + for _, s := range servers { + versionCode := twmodel.VersionCodeFromServerKey(s) + if versionCode.IsValid() && !cache[versionCode] { + cache[versionCode] = true + versionCodes = append(versionCodes, versionCode) + } + } + + if len(versionCodes) == 0 { + return []*twmodel.Version{}, nil + } + + versionList, err := c.api.Version.Browse(0, 0, []string{"code ASC"}, &twmodel.VersionFilter{ + Code: versionCodes, + }) + if err != nil { + return nil, errors.Wrap(err, "couldn't load versions") + } + + return versionList.Items, nil +} + +func (c *Cron) checkEnnoblements() { + servers, err := c.observationRepo.FetchServers(context.Background()) + if err != nil { + log.Errorln("checkEnnoblements:", err.Error()) + return + } + log. + WithField("servers", servers). + Info("checkEnnoblements: servers have been loaded") + + groups, total, err := c.groupRepo.Fetch(context.Background(), nil) + if err != nil { + log.Errorln("checkEnnoblements:", err.Error()) + return + } + log. + WithField("numberOfGroups", total). + Info("checkEnnoblements: groups have been loaded") + + versions, err := c.loadVersions(servers) + if err != nil { + log.Errorln("checkEnnoblements:", err) + return + } + log. + WithField("numberOfVersions", len(versions)). + Info("checkEnnoblements: versions have been loaded") + + ennoblementsByServerKey, err := c.loadEnnoblements(servers) + if err != nil { + log.Errorln("checkEnnoblements:", err) + } + log.Info("checkEnnoblements: ennoblements have been loaded") + + for _, g := range groups { + if g.ConqueredVillagesChannelID == "" && g.LostVillagesChannelID == "" { + continue + } + localizer := message.NewLocalizer(g.Server.Lang) + lostVillagesBldr := &discord.MessageEmbedFieldBuilder{} + conqueredVillagesBldr := &discord.MessageEmbedFieldBuilder{} + for _, obs := range g.Observations { + enblmnts, ok := ennoblementsByServerKey[obs.Server] + version := twutil.FindVersionByCode(versions, twmodel.VersionCodeFromServerKey(obs.Server)) + if ok && version != nil && version.Host != "" { + if g.LostVillagesChannelID != "" { + for _, ennoblement := range enblmnts.getLostVillagesByTribe(obs.TribeID) { + if !twutil.IsPlayerTribeNil(ennoblement.NewOwner) && + g.Observations.Contains(obs.Server, ennoblement.NewOwner.Tribe.ID) { + continue + } + newMsgDataConfig := newMessageConfig{ + host: version.Host, + server: obs.Server, + ennoblement: ennoblement, + t: messageTypeLost, + localizer: localizer, + } + lostVillagesBldr.Append(newMessage(newMsgDataConfig).String()) + } + } + + if g.ConqueredVillagesChannelID != "" { + for _, ennoblement := range enblmnts.getConqueredVillagesByTribe(obs.TribeID, g.ShowInternals) { + isInTheSameGroup := !twutil.IsPlayerTribeNil(ennoblement.OldOwner) && + g.Observations.Contains(obs.Server, ennoblement.OldOwner.Tribe.ID) + if (!g.ShowInternals && isInTheSameGroup) || + (!g.ShowEnnobledBarbarians && isBarbarian(ennoblement.OldOwner)) { + continue + } + + newMsgDataConfig := newMessageConfig{ + host: version.Host, + server: obs.Server, + ennoblement: ennoblement, + t: messageTypeConquer, + localizer: localizer, + } + conqueredVillagesBldr.Append(newMessage(newMsgDataConfig).String()) + } + } + } + } + + timestamp := time.Now().Format(time.RFC3339) + if g.ConqueredVillagesChannelID != "" && !conqueredVillagesBldr.IsEmpty() { + title := localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.CronConqueredVillagesTitle, + }) + conqueredVillagesBldr.SetName(title) + go c.discord.SendEmbed(g.ConqueredVillagesChannelID, + discord. + NewEmbed(). + SetTitle(title). + SetColor(colorConqueredVillages). + SetFields(conqueredVillagesBldr.ToMessageEmbedFields()). + SetTimestamp(timestamp)) + } + + if g.LostVillagesChannelID != "" && !lostVillagesBldr.IsEmpty() { + title := localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.CronLostVillagesTitle, + }) + lostVillagesBldr.SetName(title) + go c.discord.SendEmbed(g.LostVillagesChannelID, + discord. + NewEmbed(). + SetTitle(title). + SetColor(colorLostVillages). + SetFields(lostVillagesBldr.ToMessageEmbedFields()). + SetTimestamp(timestamp)) + } + } +} + +func (c *Cron) checkBotServers() { + servers, total, err := c.serverRepo.Fetch(context.Background(), nil) + if err != nil { + log.Error("checkBotServers: " + err.Error()) + return + } + log. + WithField("numberOfServers", total). + Info("checkBotServers: loaded servers") + + var idsToDelete []string + for _, s := range servers { + if isGuildMember, _ := c.discord.IsGuildMember(s.ID); !isGuildMember { + idsToDelete = append(idsToDelete, s.ID) + } + } + + if len(idsToDelete) > 0 { + deleted, err := c.serverRepo.Delete(context.Background(), &model.ServerFilter{ + ID: idsToDelete, + }) + if err != nil { + log.Error("checkBotServers: " + err.Error()) + } else { + log. + WithField("numberOfDeletedServers", len(deleted)). + Info("checkBotServers: some of the servers have been deleted") + } + } +} + +func (c *Cron) deleteClosedTWServers() { + servers, err := c.observationRepo.FetchServers(context.Background()) + if err != nil { + log.Error("deleteClosedTWServers: " + err.Error()) + return + } + log. + WithField("servers", servers). + Info("deleteClosedTWServers: loaded servers") + + list, err := c.api.Server.Browse(0, 0, []string{"key ASC"}, &twmodel.ServerFilter{ + Key: servers, + Status: []twmodel.ServerStatus{twmodel.ServerStatusClosed}, + }, nil) + if err != nil { + log.Errorln("deleteClosedTWServers: " + err.Error()) + return + } + if list == nil || len(list.Items) <= 0 { + return + } + + var keys []string + for _, s := range list.Items { + keys = append(keys, s.Key) + } + + if len(keys) > 0 { + deleted, err := c.observationRepo.Delete(context.Background(), &model.ObservationFilter{ + Server: keys, + }) + if err != nil { + log.Errorln("deleteClosedTWServers: " + err.Error()) + } else { + log. + WithField("numberOfDeletedObservations", len(deleted)). + Infof("deleteClosedTWServers: some of the observations have been deleted") + } + } +} + +func (c *Cron) updateBotStatus() { + if err := c.discord.UpdateStatus(c.status); err != nil { + log.Error("updateBotStatus: " + err.Error()) + } } diff --git a/cron/handler.go b/cron/handler.go deleted file mode 100644 index d5e1589..0000000 --- a/cron/handler.go +++ /dev/null @@ -1,316 +0,0 @@ -package cron - -import ( - "context" - "fmt" - "github.com/Kichiyaki/appmode" - "github.com/tribalwarshelp/shared/tw/twmodel" - "time" - - "github.com/nicksnyder/go-i18n/v2/i18n" - - "github.com/pkg/errors" - - "github.com/tribalwarshelp/dcbot/message" - "github.com/tribalwarshelp/dcbot/util/twutil" - - "github.com/tribalwarshelp/golang-sdk/sdk" - - "github.com/tribalwarshelp/dcbot/discord" - "github.com/tribalwarshelp/dcbot/group" - "github.com/tribalwarshelp/dcbot/model" - "github.com/tribalwarshelp/dcbot/observation" - "github.com/tribalwarshelp/dcbot/server" -) - -type handler struct { - lastEnnoblementAt map[string]time.Time - serverRepo server.Repository - observationRepo observation.Repository - groupRepo group.Repository - discord *discord.Session - api *sdk.SDK - status string -} - -func (h *handler) loadEnnoblements(servers []string) (map[string]ennoblements, error) { - m := make(map[string]ennoblements) - - if len(servers) == 0 { - return m, nil - } - - query := "" - - for _, s := range servers { - lastEnnoblementAt, ok := h.lastEnnoblementAt[s] - if !ok { - lastEnnoblementAt = time.Now().Add(-1 * time.Minute) - h.lastEnnoblementAt[s] = lastEnnoblementAt - } - if appmode.Equals(appmode.DevelopmentMode) { - lastEnnoblementAt = time.Now().Add(-1 * time.Hour * 2) - } - lastEnnoblementAtJSON, err := lastEnnoblementAt.MarshalJSON() - if err != nil { - continue - } - query += fmt.Sprintf(` - %s: ennoblements(server: "%s", filter: { ennobledAtGT: %s }) { - items { - %s - ennobledAt - } - } - `, s, - s, - string(lastEnnoblementAtJSON), - sdk.EnnoblementInclude{ - NewOwner: true, - Village: true, - NewOwnerInclude: sdk.PlayerInclude{ - Tribe: true, - }, - OldOwner: true, - OldOwnerInclude: sdk.PlayerInclude{ - Tribe: true, - }, - }.String()) - } - - resp := make(map[string]*sdk.EnnoblementList) - if err := h.api.Post(fmt.Sprintf(`query { %s }`, query), &resp); err != nil { - return m, errors.Wrap(err, "loadEnnoblements") - } - - for s, singleServerResp := range resp { - if singleServerResp == nil { - continue - } - m[s] = singleServerResp.Items - lastEnnoblement := m[s].getLastEnnoblement() - if lastEnnoblement != nil { - h.lastEnnoblementAt[s] = lastEnnoblement.EnnobledAt - } - } - - return m, nil -} - -func (h *handler) loadVersions(servers []string) ([]*twmodel.Version, error) { - var versionCodes []twmodel.VersionCode - cache := make(map[twmodel.VersionCode]bool) - for _, s := range servers { - versionCode := twmodel.VersionCodeFromServerKey(s) - if versionCode.IsValid() && !cache[versionCode] { - cache[versionCode] = true - versionCodes = append(versionCodes, versionCode) - } - } - - if len(versionCodes) == 0 { - return []*twmodel.Version{}, nil - } - - versionList, err := h.api.Version.Browse(0, 0, []string{"code ASC"}, &twmodel.VersionFilter{ - Code: versionCodes, - }) - if err != nil { - return nil, errors.Wrap(err, "couldn't load versions") - } - - return versionList.Items, nil -} - -func (h *handler) checkEnnoblements() { - servers, err := h.observationRepo.FetchServers(context.Background()) - if err != nil { - log.Errorln("checkEnnoblements:", err.Error()) - return - } - log. - WithField("servers", servers). - Info("checkEnnoblements: servers have been loaded") - - groups, total, err := h.groupRepo.Fetch(context.Background(), nil) - if err != nil { - log.Errorln("checkEnnoblements:", err.Error()) - return - } - log. - WithField("numberOfGroups", total). - Info("checkEnnoblements: groups have been loaded") - - versions, err := h.loadVersions(servers) - if err != nil { - log.Errorln("checkEnnoblements:", err) - return - } - log. - WithField("numberOfVersions", len(versions)). - Info("checkEnnoblements: versions have been loaded") - - ennoblementsByServerKey, err := h.loadEnnoblements(servers) - if err != nil { - log.Errorln("checkEnnoblements:", err) - } - log.Info("checkEnnoblements: ennoblements have been loaded") - - for _, g := range groups { - if g.ConqueredVillagesChannelID == "" && g.LostVillagesChannelID == "" { - continue - } - localizer := message.NewLocalizer(g.Server.Lang) - lostVillagesBldr := &discord.MessageEmbedFieldBuilder{} - conqueredVillagesBldr := &discord.MessageEmbedFieldBuilder{} - for _, obs := range g.Observations { - enblmnts, ok := ennoblementsByServerKey[obs.Server] - version := twutil.FindVersionByCode(versions, twmodel.VersionCodeFromServerKey(obs.Server)) - if ok && version != nil && version.Host != "" { - if g.LostVillagesChannelID != "" { - for _, ennoblement := range enblmnts.getLostVillagesByTribe(obs.TribeID) { - if !twutil.IsPlayerTribeNil(ennoblement.NewOwner) && - g.Observations.Contains(obs.Server, ennoblement.NewOwner.Tribe.ID) { - continue - } - newMsgDataConfig := newMessageConfig{ - host: version.Host, - server: obs.Server, - ennoblement: ennoblement, - t: messageTypeLost, - localizer: localizer, - } - lostVillagesBldr.Append(newMessage(newMsgDataConfig).String()) - } - } - - if g.ConqueredVillagesChannelID != "" { - for _, ennoblement := range enblmnts.getConqueredVillagesByTribe(obs.TribeID, g.ShowInternals) { - isInTheSameGroup := !twutil.IsPlayerTribeNil(ennoblement.OldOwner) && - g.Observations.Contains(obs.Server, ennoblement.OldOwner.Tribe.ID) - if (!g.ShowInternals && isInTheSameGroup) || - (!g.ShowEnnobledBarbarians && isBarbarian(ennoblement.OldOwner)) { - continue - } - - newMsgDataConfig := newMessageConfig{ - host: version.Host, - server: obs.Server, - ennoblement: ennoblement, - t: messageTypeConquer, - localizer: localizer, - } - conqueredVillagesBldr.Append(newMessage(newMsgDataConfig).String()) - } - } - } - } - - timestamp := time.Now().Format(time.RFC3339) - if g.ConqueredVillagesChannelID != "" && !conqueredVillagesBldr.IsEmpty() { - title := localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.CronConqueredVillagesTitle, - }) - conqueredVillagesBldr.SetName(title) - go h.discord.SendEmbed(g.ConqueredVillagesChannelID, - discord. - NewEmbed(). - SetTitle(title). - SetColor(colorConqueredVillages). - SetFields(conqueredVillagesBldr.ToMessageEmbedFields()). - SetTimestamp(timestamp)) - } - - if g.LostVillagesChannelID != "" && !lostVillagesBldr.IsEmpty() { - title := localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.CronLostVillagesTitle, - }) - lostVillagesBldr.SetName(title) - go h.discord.SendEmbed(g.LostVillagesChannelID, - discord. - NewEmbed(). - SetTitle(title). - SetColor(colorLostVillages). - SetFields(lostVillagesBldr.ToMessageEmbedFields()). - SetTimestamp(timestamp)) - } - } -} - -func (h *handler) checkBotServers() { - servers, total, err := h.serverRepo.Fetch(context.Background(), nil) - if err != nil { - log.Error("checkBotServers: " + err.Error()) - return - } - log. - WithField("numberOfServers", total). - Info("checkBotServers: loaded servers") - - var idsToDelete []string - for _, s := range servers { - if isGuildMember, _ := h.discord.IsGuildMember(s.ID); !isGuildMember { - idsToDelete = append(idsToDelete, s.ID) - } - } - - if len(idsToDelete) > 0 { - deleted, err := h.serverRepo.Delete(context.Background(), &model.ServerFilter{ - ID: idsToDelete, - }) - if err != nil { - log.Error("checkBotServers: " + err.Error()) - } else { - log. - WithField("numberOfDeletedServers", len(deleted)). - Info("checkBotServers: some of the servers have been deleted") - } - } -} - -func (h *handler) deleteClosedTWServers() { - servers, err := h.observationRepo.FetchServers(context.Background()) - if err != nil { - log.Error("deleteClosedTWServers: " + err.Error()) - return - } - log. - WithField("servers", servers). - Info("deleteClosedTWServers: loaded servers") - - list, err := h.api.Server.Browse(0, 0, []string{"key ASC"}, &twmodel.ServerFilter{ - Key: servers, - Status: []twmodel.ServerStatus{twmodel.ServerStatusClosed}, - }, nil) - if err != nil { - log.Errorln("deleteClosedTWServers: " + err.Error()) - return - } - if list == nil || len(list.Items) <= 0 { - return - } - - var keys []string - for _, s := range list.Items { - keys = append(keys, s.Key) - } - - if len(keys) > 0 { - deleted, err := h.observationRepo.Delete(context.Background(), &model.ObservationFilter{ - Server: keys, - }) - if err != nil { - log.Errorln("deleteClosedTWServers: " + err.Error()) - } else { - log. - WithField("numberOfDeletedObservations", len(deleted)). - Infof("deleteClosedTWServers: some of the observations have been deleted") - } - } -} - -func (h *handler) updateBotStatus() { - if err := h.discord.UpdateStatus(h.status); err != nil { - log.Error("updateBotStatus: " + err.Error()) - } -} diff --git a/main.go b/main.go index 0770e0b..f5b50ee 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "os" "os/signal" "path" - "strings" "syscall" "github.com/sirupsen/logrus" @@ -15,7 +14,7 @@ import ( "github.com/tribalwarshelp/golang-sdk/sdk" - _cron "github.com/tribalwarshelp/dcbot/cron" + "github.com/tribalwarshelp/dcbot/cron" "github.com/tribalwarshelp/dcbot/discord" grouprepository "github.com/tribalwarshelp/dcbot/group/repository" observationrepository "github.com/tribalwarshelp/dcbot/observation/repository" @@ -24,7 +23,6 @@ import ( "github.com/Kichiyaki/go-pg-logrus-query-logger/v10" "github.com/go-pg/pg/v10" "github.com/joho/godotenv" - "github.com/robfig/cron/v3" ) const ( @@ -40,7 +38,7 @@ func init() { godotenv.Load(".env.local") } - setupLogger() + prepareLogger() } func main() { @@ -64,7 +62,7 @@ func main() { logrus.Fatalln(err) } }() - if strings.ToUpper(os.Getenv("LOG_DB_QUERIES")) == "TRUE" { + if envutil.GetenvBool("LOG_DB_QUERIES") { db.AddQueryHook(gopglogrusquerylogger.QueryLogger{ Log: logrus.NewEntry(logrus.StandardLogger()), MaxQueryLength: 5000, @@ -106,14 +104,7 @@ func main() { } defer sess.Close() - c := cron.New( - cron.WithChain( - cron.SkipIfStillRunning( - cron.PrintfLogger(logrus.StandardLogger()), - ), - ), - ) - _cron.Attach(c, _cron.Config{ + c := cron.New(cron.Config{ ServerRepo: serverRepo, ObservationRepo: observationRepo, Discord: sess, @@ -133,7 +124,7 @@ func main() { logrus.Info("shutting down...") } -func setupLogger() { +func prepareLogger() { if appmode.Equals(appmode.DevelopmentMode) { logrus.SetLevel(logrus.DebugLevel) }