refactor cron initialization

This commit is contained in:
Dawid Wysokiński 2021-07-18 10:28:28 +02:00
parent f11d4b061e
commit 1153829a70
3 changed files with 323 additions and 338 deletions

View File

@ -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())
}
}

View File

@ -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())
}
}

19
main.go
View File

@ -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)
}