diff --git a/cron/cron.go b/cron/cron.go index 1c73297..1696269 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,16 @@ 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/tribalwarshelp/dcbot/util/twutil" +) - "github.com/robfig/cron/v3" +const ( + colorLostVillage = 0xff0000 + colorConqueredVillage = 0x00ff00 ) var log = logrus.WithField("package", "cron") @@ -26,8 +38,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 +66,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 +85,286 @@ 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 := newEnnoblementMsgConfig{ + host: version.Host, + server: obs.Server, + ennoblement: ennoblement, + localizer: localizer, + } + lostVillagesBldr.Append(newEnnoblementMsg(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 := newEnnoblementMsgConfig{ + host: version.Host, + server: obs.Server, + ennoblement: ennoblement, + localizer: localizer, + } + conqueredVillagesBldr.Append(newEnnoblementMsg(newMsgDataConfig).String()) + } + } + } + } + + timestamp := time.Now().Format(time.RFC3339) + if !conqueredVillagesBldr.IsEmpty() { + title := localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.CronConqueredVillagesTitle, + }) + conqueredVillagesBldr.SetName(title) + go c.discord.SendEmbed(g.ConqueredVillagesChannelID, + discord. + NewEmbed(). + SetTitle(title). + SetColor(colorConqueredVillage). + SetFields(conqueredVillagesBldr.ToMessageEmbedFields()). + SetTimestamp(timestamp)) + } + + if !lostVillagesBldr.IsEmpty() { + title := localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.CronLostVillagesTitle, + }) + lostVillagesBldr.SetName(title) + go c.discord.SendEmbed(g.LostVillagesChannelID, + discord. + NewEmbed(). + SetTitle(title). + SetColor(colorLostVillage). + 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/message.go b/cron/ennoblement_msg.go similarity index 52% rename from cron/message.go rename to cron/ennoblement_msg.go index 1ec3b6d..1047781 100644 --- a/cron/message.go +++ b/cron/ennoblement_msg.go @@ -7,20 +7,10 @@ import ( "github.com/tribalwarshelp/dcbot/discord" "github.com/tribalwarshelp/dcbot/message" - "github.com/tribalwarshelp/dcbot/tw/twutil" + "github.com/tribalwarshelp/dcbot/util/twutil" ) -type messageType string - -const ( - messageTypeConquer messageType = "conquer" - messageTypeLost messageType = "lost" - colorLostVillages = 0xff0000 - colorConqueredVillages = 0x00ff00 -) - -type checkEnnoblementsMsg struct { - t messageType +type ennoblementMsg struct { server string village string villageURL string @@ -35,17 +25,15 @@ type checkEnnoblementsMsg struct { localizer *i18n.Localizer } -type newMessageConfig struct { - t messageType +type newEnnoblementMsgConfig struct { host string server string ennoblement *twmodel.Ennoblement localizer *i18n.Localizer } -func newMessage(cfg newMessageConfig) checkEnnoblementsMsg { - data := checkEnnoblementsMsg{ - t: cfg.t, +func newEnnoblementMsg(cfg newEnnoblementMsgConfig) ennoblementMsg { + msg := ennoblementMsg{ server: cfg.server, village: "-", oldOwnerName: "-", @@ -55,34 +43,32 @@ func newMessage(cfg newMessageConfig) checkEnnoblementsMsg { localizer: cfg.localizer, } if !twutil.IsVillageNil(cfg.ennoblement.Village) { - data.village = cfg.ennoblement.Village.FullName() - data.villageURL = twurlbuilder.BuildVillageURL(cfg.server, cfg.host, cfg.ennoblement.Village.ID) + msg.village = cfg.ennoblement.Village.FullName() + msg.villageURL = twurlbuilder.BuildVillageURL(cfg.server, cfg.host, cfg.ennoblement.Village.ID) } if !twutil.IsPlayerNil(cfg.ennoblement.OldOwner) { - data.oldOwnerName = cfg.ennoblement.OldOwner.Name - data.oldOwnerURL = twurlbuilder.BuildPlayerURL(cfg.server, cfg.host, cfg.ennoblement.OldOwner.ID) + msg.oldOwnerName = cfg.ennoblement.OldOwner.Name + msg.oldOwnerURL = twurlbuilder.BuildPlayerURL(cfg.server, cfg.host, cfg.ennoblement.OldOwner.ID) } if !twutil.IsPlayerTribeNil(cfg.ennoblement.OldOwner) { - data.oldOwnerTribeTag = cfg.ennoblement.OldOwner.Tribe.Tag - data.oldOwnerTribeURL = twurlbuilder.BuildTribeURL(cfg.server, cfg.host, cfg.ennoblement.OldOwner.Tribe.ID) + msg.oldOwnerTribeTag = cfg.ennoblement.OldOwner.Tribe.Tag + msg.oldOwnerTribeURL = twurlbuilder.BuildTribeURL(cfg.server, cfg.host, cfg.ennoblement.OldOwner.Tribe.ID) } if !twutil.IsPlayerNil(cfg.ennoblement.NewOwner) { - data.newOwnerName = cfg.ennoblement.NewOwner.Name - data.newOwnerURL = twurlbuilder.BuildPlayerURL(cfg.server, cfg.host, cfg.ennoblement.NewOwner.ID) + msg.newOwnerName = cfg.ennoblement.NewOwner.Name + msg.newOwnerURL = twurlbuilder.BuildPlayerURL(cfg.server, cfg.host, cfg.ennoblement.NewOwner.ID) } if !twutil.IsPlayerTribeNil(cfg.ennoblement.NewOwner) { - data.newOwnerTribeTag = cfg.ennoblement.NewOwner.Tribe.Tag - data.newOwnerTribeURL = twurlbuilder.BuildTribeURL(cfg.server, cfg.host, cfg.ennoblement.NewOwner.Tribe.ID) + msg.newOwnerTribeTag = cfg.ennoblement.NewOwner.Tribe.Tag + msg.newOwnerTribeURL = twurlbuilder.BuildTribeURL(cfg.server, cfg.host, cfg.ennoblement.NewOwner.Tribe.ID) } - return data + return msg } -func (msg checkEnnoblementsMsg) String() string { +func (msg ennoblementMsg) String() string { return msg.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.CronCheckEnnoblementsMsgLine, - DefaultMessage: message.FallbackMsg(message.CronCheckEnnoblementsMsgLine, - "{{.NewOwner}} ({{.NewOwnerTribe}}) has conquered {{.Village}} (Old owner: {{.OldOwner}} ({{.OldOwnerTribe}}))"), TemplateData: map[string]interface{}{ "NewOwner": discord.BuildLink(msg.newOwnerName, msg.newOwnerURL), "NewOwnerTribe": discord.BuildLink(msg.newOwnerTribeTag, msg.newOwnerTribeURL), diff --git a/cron/ennoblements.go b/cron/ennoblements.go index c970cd5..7cfbb57 100644 --- a/cron/ennoblements.go +++ b/cron/ennoblements.go @@ -3,7 +3,7 @@ package cron import ( "github.com/tribalwarshelp/shared/tw/twmodel" - "github.com/tribalwarshelp/dcbot/tw/twutil" + "github.com/tribalwarshelp/dcbot/util/twutil" ) type ennoblements []*twmodel.Ennoblement diff --git a/cron/handler.go b/cron/handler.go deleted file mode 100644 index bfb98fc..0000000 --- a/cron/handler.go +++ /dev/null @@ -1,320 +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/tw/twutil" - - "github.com/tribalwarshelp/golang-sdk/sdk" - - "github.com/tribalwarshelp/dcbot/discord" - "github.com/tribalwarshelp/dcbot/group" - "github.com/tribalwarshelp/dcbot/models" - "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) - lostVillagesMsg := &discord.MessageEmbed{} - conqueredVillagesMsg := &discord.MessageEmbed{} - 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, - } - lostVillagesMsg.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, - } - conqueredVillagesMsg.Append(newMessage(newMsgDataConfig).String()) - } - } - } - } - - timestamp := time.Now().Format(time.RFC3339) - if g.ConqueredVillagesChannelID != "" && !conqueredVillagesMsg.IsEmpty() { - title := localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.CronConqueredVillagesTitle, - DefaultMessage: message.FallbackMsg(message.CronConqueredVillagesTitle, - "Conquered villages"), - }) - go h.discord.SendEmbed(g.ConqueredVillagesChannelID, - discord. - NewEmbed(). - SetTitle(title). - SetColor(colorConqueredVillages). - SetFields(conqueredVillagesMsg.ToMessageEmbedFields()). - SetTimestamp(timestamp). - MessageEmbed) - } - - if g.LostVillagesChannelID != "" && !lostVillagesMsg.IsEmpty() { - title := localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.CronLostVillagesTitle, - DefaultMessage: message.FallbackMsg(message.CronLostVillagesTitle, - "Lost villages"), - }) - go h.discord.SendEmbed(g.LostVillagesChannelID, - discord. - NewEmbed(). - SetTitle(title). - SetColor(colorLostVillages). - SetFields(lostVillagesMsg.ToMessageEmbedFields()). - SetTimestamp(timestamp). - MessageEmbed) - } - } -} - -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(), &models.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(), &models.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/cron/helpers.go b/cron/helpers.go index a608dbb..dd9d86b 100644 --- a/cron/helpers.go +++ b/cron/helpers.go @@ -5,7 +5,7 @@ import ( "github.com/tribalwarshelp/shared/tw/twmodel" "time" - "github.com/tribalwarshelp/dcbot/tw/twutil" + "github.com/tribalwarshelp/dcbot/util/twutil" ) func isBarbarian(p *twmodel.Player) bool { diff --git a/discord/admin_commands.go b/discord/admin_commands.go new file mode 100644 index 0000000..2ac8425 --- /dev/null +++ b/discord/admin_commands.go @@ -0,0 +1,76 @@ +package discord + +import ( + "context" + "github.com/bwmarrin/discordgo" + "github.com/nicksnyder/go-i18n/v2/i18n" + + "github.com/tribalwarshelp/dcbot/message" +) + +const ( + cmdChangeLanguage command = "changelanguage" +) + +type hndlrChangeLanguage struct { + *Session +} + +var _ commandHandler = &hndlrChangeLanguage{} + +func (hndlr *hndlrChangeLanguage) cmd() command { + return cmdChangeLanguage +} + +func (hndlr *hndlrChangeLanguage) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrChangeLanguage) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { + argsLength := len(args) + if argsLength != 1 { + hndlr.SendMessage(m.ChannelID, + m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.HelpChangageLanguage, + TemplateData: map[string]interface{}{ + "Command": hndlr.cmd().WithPrefix(hndlr.cfg.CommandPrefix), + "Languages": getAvailableLanguages(), + }, + })) + return + } + + lang := args[0] + valid := isValidLanguageTag(lang) + if !valid { + hndlr.SendMessage(m.ChannelID, + ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.ChangeLanguageLanguageNotSupported, + TemplateData: map[string]interface{}{ + "Mention": m.Author.Mention(), + }, + })) + return + } + + ctx.server.Lang = lang + if err := hndlr.cfg.ServerRepository.Update(context.Background(), ctx.server); err != nil { + hndlr.SendMessage(m.ChannelID, + ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.InternalServerError, + TemplateData: map[string]interface{}{ + "Mention": m.Author.Mention(), + }, + })) + return + } + ctx.localizer = message.NewLocalizer(lang) + + hndlr.SendMessage(m.ChannelID, + ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.ChangeLanguageSuccess, + TemplateData: map[string]interface{}{ + "Mention": m.Author.Mention(), + }, + })) +} diff --git a/discord/command.go b/discord/command.go index 8bf24eb..ebb2e82 100644 --- a/discord/command.go +++ b/discord/command.go @@ -3,35 +3,36 @@ package discord import ( "github.com/bwmarrin/discordgo" "github.com/nicksnyder/go-i18n/v2/i18n" - "github.com/tribalwarshelp/dcbot/models" + + "github.com/tribalwarshelp/dcbot/model" ) -type Command string +type command string -func (cmd Command) String() string { +func (cmd command) String() string { return string(cmd) } -func (cmd Command) WithPrefix(prefix string) Command { - return Command(prefix + cmd.String()) +func (cmd command) WithPrefix(prefix string) string { + return prefix + cmd.String() } type commandCtx struct { - server *models.Server + server *model.Server localizer *i18n.Localizer } -type commandHandler struct { - cmd Command - requireAdmPermissions bool - fn func(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) +type commandHandler interface { + cmd() command + requireAdmPermissions() bool + execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) } -type commandHandlers []*commandHandler +type commandHandlers []commandHandler -func (hs commandHandlers) find(cmd Command) *commandHandler { +func (hs commandHandlers) find(prefix, cmd string) commandHandler { for _, h := range hs { - if h.cmd == cmd { + if h.cmd().WithPrefix(prefix) == cmd { return h } } diff --git a/discord/coords_translation.go b/discord/coords_translation.go index 7260573..c98822e 100644 --- a/discord/coords_translation.go +++ b/discord/coords_translation.go @@ -11,141 +11,173 @@ import ( "github.com/tribalwarshelp/golang-sdk/sdk" "github.com/tribalwarshelp/dcbot/message" - "github.com/tribalwarshelp/dcbot/tw/twutil" + "github.com/tribalwarshelp/dcbot/util/twutil" ) const ( - coordsLimit = 20 - CoordsTranslationCommand Command = "coordstranslation" - DisableCoordsTranslationCommand Command = "disablecoordstranslation" + coordsLimit = 20 + cmdCoordsTranslation command = "coordstranslation" + cmdDisableCoordsTranslation command = "disablecoordstranslation" ) var coordsRegex = regexp.MustCompile(`(\d+)\|(\d+)`) -func (s *Session) handleCoordsTranslationCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrCoordsTranslation struct { + *Session +} + +var _ commandHandler = &hndlrCoordsTranslation{} + +func (hndlr *hndlrCoordsTranslation) cmd() command { + return cmdCoordsTranslation +} + +func (hndlr *hndlrCoordsTranslation) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrCoordsTranslation) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength != 1 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage( + m.ChannelID, m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpCoordsTranslation, - DefaultMessage: message.FallbackMsg(message.HelpCoordsTranslation, - "**{{.Command}}** [server] - enables coords translation feature."), TemplateData: map[string]interface{}{ - "Command": CoordsTranslationCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": hndlr.cmd().WithPrefix(hndlr.cfg.CommandPrefix), }, - })) + }), + ) return } serverKey := args[0] - server, err := s.cfg.API.Server.Read(serverKey, nil) + server, err := hndlr.cfg.API.Server.Read(serverKey, nil) if err != nil || server == nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage( + m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.CoordsTranslationServerNotFound, - DefaultMessage: message.FallbackMsg(message.CoordsTranslationServerNotFound, "{{.Mention}} Server not found."), + MessageID: message.CoordsTranslationServerNotFound, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, - })) + }), + ) return } ctx.server.CoordsTranslation = serverKey - go s.cfg.ServerRepository.Update(context.Background(), ctx.server) + go hndlr.cfg.ServerRepository.Update(context.Background(), ctx.server) - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.CoordsTranslationSuccess, - DefaultMessage: message.FallbackMsg(message.CoordsTranslationSuccess, - "{{.Mention}} Coords translation feature has been enabled."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) } -func (s *Session) handleDisableCoordsTranslationCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { - ctx.server.CoordsTranslation = "" - go s.cfg.ServerRepository.Update(context.Background(), ctx.server) +type hndlrDisableCoordsTranslation struct { + *Session +} - s.SendMessage(m.ChannelID, +var _ commandHandler = &hndlrDisableCoordsTranslation{} + +func (hndlr *hndlrDisableCoordsTranslation) cmd() command { + return cmdDisableCoordsTranslation +} + +func (hndlr *hndlrDisableCoordsTranslation) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrDisableCoordsTranslation) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { + ctx.server.CoordsTranslation = "" + go hndlr.cfg.ServerRepository.Update(context.Background(), ctx.server) + + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.DisableCoordsTranslationSuccess, - DefaultMessage: message.FallbackMsg(message.DisableCoordsTranslationSuccess, - "{{.Mention}} Coords translation feature has been disabled."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) } -func (s *Session) translateCoords(ctx *commandCtx, m *discordgo.MessageCreate) { +type procTranslateCoords struct { + *Session +} + +var _ messageProcessor = &procTranslateCoords{} + +func (p *procTranslateCoords) process(ctx *commandCtx, m *discordgo.MessageCreate) { if ctx.server.CoordsTranslation == "" { return } + coords := coordsRegex.FindAllString(m.Content, -1) coordsLen := len(coords) - if coordsLen > 0 { - version, err := s.cfg.API.Version.Read(twmodel.VersionCodeFromServerKey(ctx.server.CoordsTranslation)) - if err != nil || version == nil { - return - } - if coordsLen > coordsLimit { - coords = coords[0:coordsLimit] - } - list, err := s.cfg.API.Village.Browse(ctx.server.CoordsTranslation, - 0, - 0, - []string{}, - &twmodel.VillageFilter{ - XY: coords, - }, - &sdk.VillageInclude{ - Player: true, - PlayerInclude: sdk.PlayerInclude{ - Tribe: true, - }, - }, - ) - if err != nil || list == nil || len(list.Items) <= 0 { - return - } - - msg := &MessageEmbed{} - for _, village := range list.Items { - villageURL := twurlbuilder.BuildVillageURL(ctx.server.CoordsTranslation, version.Host, village.ID) - playerName := "-" - playerURL := "" - if !twutil.IsPlayerNil(village.Player) { - playerName = village.Player.Name - playerURL = twurlbuilder.BuildPlayerURL(ctx.server.CoordsTranslation, version.Host, village.Player.ID) - } - tribeName := "-" - tribeURL := "" - if !twutil.IsPlayerTribeNil(village.Player) { - tribeName = village.Player.Tribe.Name - tribeURL = twurlbuilder.BuildTribeURL(ctx.server.CoordsTranslation, version.Host, village.Player.Tribe.ID) - } - - msg.Append(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.CoordsTranslationMessage, - DefaultMessage: message.FallbackMsg(message.CoordsTranslationMessage, - "{{.Village}} owned by {{.Player}} (Tribe: {{.Tribe}})."), - TemplateData: map[string]interface{}{ - "Village": BuildLink(village.FullName(), villageURL), - "Player": BuildLink(playerName, playerURL), - "Tribe": BuildLink(tribeName, tribeURL), - }, - }) + "\n") - } - - s.SendEmbed(m.ChannelID, NewEmbed(). - SetTitle(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.CoordsTranslationTitle, - DefaultMessage: message.FallbackMsg(message.CoordsTranslationTitle, "Villages"), - })). - SetFields(msg.ToMessageEmbedFields()). - MessageEmbed) + if coordsLen <= 0 { + return } + + version, err := p.cfg.API.Version.Read(twmodel.VersionCodeFromServerKey(ctx.server.CoordsTranslation)) + if err != nil || version == nil { + return + } + if coordsLen > coordsLimit { + coords = coords[0:coordsLimit] + } + list, err := p.cfg.API.Village.Browse(ctx.server.CoordsTranslation, + 0, + 0, + []string{}, + &twmodel.VillageFilter{ + XY: coords, + }, + &sdk.VillageInclude{ + Player: true, + PlayerInclude: sdk.PlayerInclude{ + Tribe: true, + }, + }, + ) + if err != nil || list == nil || len(list.Items) <= 0 { + return + } + + bldr := &MessageEmbedFieldBuilder{} + for _, village := range list.Items { + villageURL := twurlbuilder.BuildVillageURL(ctx.server.CoordsTranslation, version.Host, village.ID) + playerName := "-" + playerURL := "" + if !twutil.IsPlayerNil(village.Player) { + playerName = village.Player.Name + playerURL = twurlbuilder.BuildPlayerURL(ctx.server.CoordsTranslation, version.Host, village.Player.ID) + } + tribeName := "-" + tribeURL := "" + if !twutil.IsPlayerTribeNil(village.Player) { + tribeName = village.Player.Tribe.Name + tribeURL = twurlbuilder.BuildTribeURL(ctx.server.CoordsTranslation, version.Host, village.Player.Tribe.ID) + } + + bldr.Append(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.CoordsTranslationMessage, + TemplateData: map[string]interface{}{ + "Village": BuildLink(village.FullName(), villageURL), + "Player": BuildLink(playerName, playerURL), + "Tribe": BuildLink(tribeName, tribeURL), + }, + }) + "\n") + } + + title := ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.CoordsTranslationTitle, + }) + bldr.SetName(title) + p.SendEmbed(m.ChannelID, NewEmbed(). + SetTitle(title). + SetFields(bldr.ToMessageEmbedFields())) } diff --git a/discord/discord.go b/discord/discord.go index 2c1510b..739916a 100644 --- a/discord/discord.go +++ b/discord/discord.go @@ -1,311 +1,5 @@ package discord -import ( - "context" - "github.com/pkg/errors" - "strings" - - "github.com/sirupsen/logrus" - - "github.com/tribalwarshelp/dcbot/message" - - "github.com/tribalwarshelp/golang-sdk/sdk" - - "github.com/tribalwarshelp/dcbot/group" - "github.com/tribalwarshelp/dcbot/models" - "github.com/tribalwarshelp/dcbot/observation" - "github.com/tribalwarshelp/dcbot/server" - - "github.com/bwmarrin/discordgo" -) +import "github.com/sirupsen/logrus" var log = logrus.WithField("package", "discord") - -type SessionConfig struct { - Token string - CommandPrefix string - Status string - ServerRepository server.Repository - GroupRepository group.Repository - ObservationRepository observation.Repository - API *sdk.SDK -} - -type Session struct { - dg *discordgo.Session - cfg SessionConfig - handlers commandHandlers -} - -func New(cfg SessionConfig) (*Session, error) { - var err error - s := &Session{ - cfg: cfg, - } - s.dg, err = discordgo.New("Bot " + cfg.Token) - if err != nil { - return nil, err - } - if err := s.init(); err != nil { - return nil, err - } - return s, nil -} - -func (s *Session) init() error { - s.handlers = commandHandlers{ - &commandHandler{ - cmd: HelpCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleHelpCommand, - }, - &commandHandler{ - cmd: AuthorCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleAuthorCommand, - }, - &commandHandler{ - cmd: TribeCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleTribeCommand, - }, - &commandHandler{ - cmd: ChangeLanguageCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleChangeLanguageCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: AddGroupCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleAddGroupCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: DeleteGroupCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleDeleteGroupCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: GroupsCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleGroupsCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: ObserveCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleObserveCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: DeleteObservationCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleDeleteObservationCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: ObservationsCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleObservationsCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: ConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleConqueredVillagesCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: DisableConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleDisableConqueredVillagesCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: LostVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleLostVillagesCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: DisableLostVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleDisableLostVillagesCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: ShowEnnobledBarbariansCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleShowEnnobledBarbariansCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: ShowInternalsCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleShowInternalsCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: CoordsTranslationCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleCoordsTranslationCommand, - requireAdmPermissions: true, - }, - &commandHandler{ - cmd: DisableCoordsTranslationCommand.WithPrefix(s.cfg.CommandPrefix), - fn: s.handleDisableCoordsTranslationCommand, - requireAdmPermissions: true, - }, - } - - s.dg.AddHandler(s.handleNewMessage) - - err := s.dg.Open() - if err != nil { - return errors.Wrap(err, "error opening ws connection") - } - - if err := s.UpdateStatus(s.cfg.Status); err != nil { - return err - } - - return nil -} - -func (s *Session) Close() error { - return s.dg.Close() -} - -func (s *Session) SendMessage(channelID, message string) error { - _, err := s.dg.ChannelMessageSend(channelID, message) - return err -} - -func (s *Session) SendEmbed(channelID string, message *discordgo.MessageEmbed) error { - fields := message.Fields - baseNumberOfCharacters := len(message.Description) + len(message.Title) - if message.Author != nil { - baseNumberOfCharacters += len(message.Author.Name) - } - if message.Footer != nil { - baseNumberOfCharacters += len(message.Footer.Text) - } - - var splittedFields [][]*discordgo.MessageEmbedField - characters := baseNumberOfCharacters - fromIndex := 0 - fieldsLen := len(fields) - for index, field := range fields { - fNameLen := len(field.Name) - fValLen := len(field.Value) - if characters+fNameLen+fValLen > EmbedSizeLimit || index == fieldsLen-1 { - splittedFields = append(splittedFields, fields[fromIndex:index+1]) - fromIndex = index - characters = baseNumberOfCharacters - } - characters += fNameLen - characters += fValLen - } - for _, fields := range splittedFields { - fieldsLen := len(fields) - for i := 0; i < fieldsLen; i += EmbedLimitField { - end := i + EmbedLimitField - - if end > fieldsLen { - end = fieldsLen - } - message.Fields = fields[i:end] - if _, err := s.dg.ChannelMessageSendEmbed(channelID, message); err != nil { - return err - } - } - } - - return nil -} - -func (s *Session) UpdateStatus(status string) error { - if err := s.dg.UpdateStatus(0, status); err != nil { - return err - } - return nil -} - -func (s *Session) IsGuildMember(guildID string) (bool, error) { - _, err := s.dg.State.Guild(guildID) - if err != nil { - if _, err = s.dg.Guild(guildID); err != nil { - return false, err - } - } - return true, nil -} - -func (s *Session) handleNewMessage(_ *discordgo.Session, m *discordgo.MessageCreate) { - if m.Author.ID == s.dg.State.User.ID || m.Author.Bot { - return - } - - parts := strings.Split(m.Content, " ") - args := parts[1:] - svr := &models.Server{ - ID: m.GuildID, - Lang: message.GetDefaultLanguage().String(), - } - if svr.ID != "" { - if err := s.cfg.ServerRepository.Store(context.Background(), svr); err != nil { - return - } - } - ctx := &commandCtx{ - server: svr, - localizer: message.NewLocalizer(svr.Lang), - } - - cmd := Command(parts[0]) - h := s.handlers.find(cmd) - if h != nil { - if h.requireAdmPermissions { - if m.GuildID == "" { - return - } - has, err := s.memberHasPermission(m.GuildID, m.Author.ID, discordgo.PermissionAdministrator) - if err != nil || !has { - return - } - } - log. - WithFields(logrus.Fields{ - "serverID": svr.ID, - "lang": svr.Lang, - "command": cmd, - "args": args, - "authorID": m.Author.ID, - "authorUsername": m.Author.Username, - }). - Info("handleNewMessage: Executing command...") - h.fn(ctx, m, args...) - return - } - - s.translateCoords(ctx, m) -} - -func (s *Session) memberHasPermission(guildID string, userID string, permission int) (bool, error) { - member, err := s.dg.State.Member(guildID, userID) - if err != nil { - if member, err = s.dg.GuildMember(guildID, userID); err != nil { - return false, err - } - } - - // check if user is a guild owner - guild, err := s.dg.State.Guild(guildID) - if err != nil { - if guild, err = s.dg.Guild(guildID); err != nil { - return false, err - } - } - if guild.OwnerID == userID { - return true, nil - } - - // Iterate through the role IDs stored in member.Roles - // to check permissions - for _, roleID := range member.Roles { - role, err := s.dg.State.Role(guildID, roleID) - if err != nil { - return false, err - } - if role.Permissions&permission != 0 { - return true, nil - } - } - - return false, nil -} diff --git a/discord/embed.go b/discord/embed.go index dfadf97..f6211ed 100644 --- a/discord/embed.go +++ b/discord/embed.go @@ -1,12 +1,12 @@ package discord import ( + "strconv" "sync" "github.com/bwmarrin/discordgo" ) -// Constants for message embed character limits const ( EmbedColor = 0x00ff00 EmbedLimitTitle = 256 @@ -29,7 +29,6 @@ func NewEmbed() *Embed { }} } -//SetTitle ... func (e *Embed) SetTitle(name string) *Embed { e.Title = name return e @@ -40,7 +39,6 @@ func (e *Embed) SetTimestamp(timestamp string) *Embed { return e } -//SetDescription [desc] func (e *Embed) SetDescription(description string) *Embed { if len(description) > EmbedLimitDescription { description = description[:EmbedLimitDescription] @@ -49,7 +47,6 @@ func (e *Embed) SetDescription(description string) *Embed { return e } -//AddField [name] [value] func (e *Embed) AddField(name, value string) *Embed { if len(value) > EmbedLimitFieldValue { value = value[:EmbedLimitFieldValue] @@ -65,16 +62,13 @@ func (e *Embed) AddField(name, value string) *Embed { }) return e - } func (e *Embed) SetFields(fields []*discordgo.MessageEmbedField) *Embed { e.Fields = fields return e - } -//SetFooter [Text] [iconURL] func (e *Embed) SetFooter(args ...string) *Embed { iconURL := "" text := "" @@ -102,7 +96,6 @@ func (e *Embed) SetFooter(args ...string) *Embed { return e } -//SetImage ... func (e *Embed) SetImage(args ...string) *Embed { var URL string var proxyURL string @@ -123,7 +116,6 @@ func (e *Embed) SetImage(args ...string) *Embed { return e } -//SetThumbnail ... func (e *Embed) SetThumbnail(args ...string) *Embed { var URL string var proxyURL string @@ -144,7 +136,6 @@ func (e *Embed) SetThumbnail(args ...string) *Embed { return e } -//SetAuthor ... func (e *Embed) SetAuthor(args ...string) *Embed { var ( name string @@ -179,19 +170,16 @@ func (e *Embed) SetAuthor(args ...string) *Embed { return e } -//SetURL ... func (e *Embed) SetURL(URL string) *Embed { e.URL = URL return e } -//SetColor ... func (e *Embed) SetColor(clr int) *Embed { e.Color = clr return e } -// InlineAllFields sets all fields in the embed to be inline func (e *Embed) InlineAllFields() *Embed { for _, v := range e.Fields { v.Inline = true @@ -199,7 +187,6 @@ func (e *Embed) InlineAllFields() *Embed { return e } -// Truncate truncates any embed value over the character limit. func (e *Embed) Truncate() *Embed { e.TruncateDescription() e.TruncateFields() @@ -208,7 +195,6 @@ func (e *Embed) Truncate() *Embed { return e } -// TruncateFields truncates fields that are too long func (e *Embed) TruncateFields() *Embed { if len(e.Fields) > 25 { e.Fields = e.Fields[:EmbedLimitField] @@ -228,7 +214,6 @@ func (e *Embed) TruncateFields() *Embed { return e } -// TruncateDescription ... func (e *Embed) TruncateDescription() *Embed { if len(e.Description) > EmbedLimitDescription { e.Description = e.Description[:EmbedLimitDescription] @@ -236,7 +221,6 @@ func (e *Embed) TruncateDescription() *Embed { return e } -// TruncateTitle ... func (e *Embed) TruncateTitle() *Embed { if len(e.Title) > EmbedLimitTitle { e.Title = e.Title[:EmbedLimitTitle] @@ -244,7 +228,6 @@ func (e *Embed) TruncateTitle() *Embed { return e } -// TruncateFooter ... func (e *Embed) TruncateFooter() *Embed { if e.Footer != nil && len(e.Footer.Text) > EmbedLimitFooter { e.Footer.Text = e.Footer.Text[:EmbedLimitFooter] @@ -252,41 +235,82 @@ func (e *Embed) TruncateFooter() *Embed { return e } -type MessageEmbed struct { +type MessageEmbedFieldBuilder struct { chunks []string index int + name string mutex sync.Mutex } -func (msg *MessageEmbed) IsEmpty() bool { - return len(msg.chunks) == 0 +func (b *MessageEmbedFieldBuilder) SetName(name string) { + b.mutex.Lock() + defer b.mutex.Unlock() + b.name = name } -func (msg *MessageEmbed) Append(m string) { - msg.mutex.Lock() - defer msg.mutex.Unlock() - for len(msg.chunks) < msg.index+1 { - msg.chunks = append(msg.chunks, "") +func (b *MessageEmbedFieldBuilder) IsEmpty() bool { + return len(b.chunks) == 0 +} + +func (b *MessageEmbedFieldBuilder) Append(m string) { + b.mutex.Lock() + defer b.mutex.Unlock() + + for len(b.chunks) < b.index+1 { + b.chunks = append(b.chunks, "") } - if len(m)+len(msg.chunks[msg.index]) > EmbedLimitFieldValue { - msg.chunks = append(msg.chunks, m) - msg.index++ + if len(m)+len(b.chunks[b.index]) > EmbedLimitFieldValue { + b.chunks = append(b.chunks, m) + b.index++ return } - msg.chunks[msg.index] += m + b.chunks[b.index] += m } -func (msg *MessageEmbed) ToMessageEmbedFields() []*discordgo.MessageEmbedField { - msg.mutex.Lock() - defer msg.mutex.Unlock() - fields := []*discordgo.MessageEmbedField{} - for _, chunk := range msg.chunks { +func (b *MessageEmbedFieldBuilder) ToMessageEmbedFields() []*discordgo.MessageEmbedField { + b.mutex.Lock() + defer b.mutex.Unlock() + var fields []*discordgo.MessageEmbedField + name := b.name + if name == "" { + name = "Field" + } + for i, chunk := range b.chunks { fields = append(fields, &discordgo.MessageEmbedField{ - Name: "-", + Name: name + " " + strconv.Itoa(i+1), Value: chunk, }) } return fields } + +func splitEmbedFields(e *Embed) [][]*discordgo.MessageEmbedField { + fields := e.Fields + baseNumberOfCharacters := len(e.Description) + len(e.Title) + if e.Author != nil { + baseNumberOfCharacters += len(e.Author.Name) + } + if e.Footer != nil { + baseNumberOfCharacters += len(e.Footer.Text) + } + + var splitFields [][]*discordgo.MessageEmbedField + characters := baseNumberOfCharacters + fromIndex := 0 + fieldsLen := len(fields) + for index, field := range fields { + fNameLen := len(field.Name) + fValLen := len(field.Value) + if characters+fNameLen+fValLen > EmbedSizeLimit || index == fieldsLen-1 { + splitFields = append(splitFields, fields[fromIndex:index+1]) + fromIndex = index + 1 + characters = baseNumberOfCharacters + } + characters += fNameLen + characters += fValLen + } + + return splitFields +} diff --git a/discord/helpers.go b/discord/helpers.go index e040a40..f15cd8c 100644 --- a/discord/helpers.go +++ b/discord/helpers.go @@ -7,7 +7,7 @@ import ( "github.com/tribalwarshelp/dcbot/message" ) -func getEmojiForGroupsCommand(val bool) string { +func boolToEmoji(val bool) string { if val { return ":white_check_mark:" } @@ -15,7 +15,7 @@ func getEmojiForGroupsCommand(val bool) string { } func getAvailableLanguages() string { - langTags := []string{} + var langTags []string for _, langTag := range message.LanguageTags() { langTags = append(langTags, langTag.String()) } @@ -23,14 +23,12 @@ func getAvailableLanguages() string { } func isValidLanguageTag(lang string) bool { - valid := false for _, langTag := range message.LanguageTags() { if langTag.String() == lang { - valid = true - break + return true } } - return valid + return false } func BuildLink(text string, url string) string { diff --git a/discord/message_processor.go b/discord/message_processor.go new file mode 100644 index 0000000..7724d07 --- /dev/null +++ b/discord/message_processor.go @@ -0,0 +1,7 @@ +package discord + +import "github.com/bwmarrin/discordgo" + +type messageProcessor interface { + process(ctx *commandCtx, m *discordgo.MessageCreate) +} diff --git a/discord/observations.go b/discord/observations.go index 6fe24ea..ca4e3af 100644 --- a/discord/observations.go +++ b/discord/observations.go @@ -14,8 +14,8 @@ import ( "github.com/nicksnyder/go-i18n/v2/i18n" "github.com/tribalwarshelp/dcbot/message" - "github.com/tribalwarshelp/dcbot/models" - "github.com/tribalwarshelp/dcbot/tw/twutil" + "github.com/tribalwarshelp/dcbot/model" + "github.com/tribalwarshelp/dcbot/util/twutil" ) const ( @@ -24,28 +24,39 @@ const ( ) const ( - AddGroupCommand Command = "addgroup" - DeleteGroupCommand Command = "deletegroup" - GroupsCommand Command = "groups" - ShowEnnobledBarbariansCommand Command = "showennobledbarbs" - ObserveCommand Command = "observe" - ObservationsCommand Command = "observations" - DeleteObservationCommand Command = "deleteobservation" - LostVillagesCommand Command = "lostvillages" - DisableLostVillagesCommand Command = "disablelostvillages" - ConqueredVillagesCommand Command = "conqueredvillages" - DisableConqueredVillagesCommand Command = "disableconqueredvillages" - ChangeLanguageCommand Command = "changelanguage" - ShowInternalsCommand Command = "showinternals" + cmdAddGroup command = "addgroup" + cmdDeleteGroup command = "deletegroup" + cmdGroups command = "groups" + cmdShowEnnobledBarbarians command = "showennobledbarbs" + cmdObserve command = "observe" + cmdObservations command = "observations" + cmdDeleteObservation command = "deleteobservation" + cmdLostVillages command = "lostvillages" + cmdDisableLostVillages command = "disablelostvillages" + cmdConqueredVillages command = "conqueredvillages" + cmdDisableConqueredVillages command = "disableconqueredvillages" + cmdShowInternals command = "showinternals" ) -func (s *Session) handleAddGroupCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrAddGroup struct { + *Session +} + +var _ commandHandler = &hndlrAddGroup{} + +func (hndlr *hndlrAddGroup) cmd() command { + return cmdAddGroup +} + +func (hndlr *hndlrAddGroup) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrAddGroup) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { if len(ctx.server.Groups) >= groupsPerServer { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.AddGroupGroupLimitHasBeenReached, - DefaultMessage: message.FallbackMsg(message.AddGroupGroupLimitHasBeenReached, - "{{.Mention}} The group limit has been reached ({{.Total}}/{{.Limit}})."), + MessageID: message.AddGroupLimitHasBeenReached, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), "Total": len(ctx.server.Groups), @@ -55,17 +66,15 @@ func (s *Session) handleAddGroupCommand(ctx *commandCtx, m *discordgo.MessageCre return } - group := &models.Group{ + group := &model.Group{ ServerID: m.GuildID, ShowEnnobledBarbarians: true, } - if err := s.cfg.GroupRepository.Store(context.Background(), group); err != nil { - s.SendMessage(m.ChannelID, + if err := hndlr.cfg.GroupRepository.Store(context.Background(), group); err != nil { + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.InternalServerError, - DefaultMessage: message.FallbackMsg(message.InternalServerError, - "{{.Mention}} An internal server error has occurred, please try again later."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -73,11 +82,9 @@ func (s *Session) handleAddGroupCommand(ctx *commandCtx, m *discordgo.MessageCre return } - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.AddGroupSuccess, - DefaultMessage: message.FallbackMsg(message.AddGroupSuccess, - "{{.Mention}} A new group has been created (ID: {{.ID}})."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), "ID": group.ID, @@ -85,17 +92,29 @@ func (s *Session) handleAddGroupCommand(ctx *commandCtx, m *discordgo.MessageCre })) } -func (s *Session) handleDeleteGroupCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrDeleteGroup struct { + *Session +} + +var _ commandHandler = &hndlrDeleteGroup{} + +func (hndlr *hndlrDeleteGroup) cmd() command { + return cmdDeleteGroup +} + +func (hndlr *hndlrDeleteGroup) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrDeleteGroup) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength != 1 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpDeleteGroup, - DefaultMessage: message.FallbackMsg(message.HelpDeleteGroup, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - deletes an observation group."), TemplateData: map[string]interface{}{ - "Command": DeleteGroupCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdDeleteGroup.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, })) return @@ -103,11 +122,9 @@ func (s *Session) handleDeleteGroupCommand(ctx *commandCtx, m *discordgo.Message groupID, err := strconv.Atoi(args[0]) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.DeleteGroupInvalidID, - DefaultMessage: message.FallbackMsg(message.DeleteGroupInvalidID, - "{{.Mention}} The group ID must be a number greater than 0."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -115,26 +132,38 @@ func (s *Session) handleDeleteGroupCommand(ctx *commandCtx, m *discordgo.Message return } - go s.cfg.GroupRepository.Delete(context.Background(), &models.GroupFilter{ + go hndlr.cfg.GroupRepository.Delete(context.Background(), &model.GroupFilter{ ID: []int{groupID}, ServerID: []string{m.GuildID}, }) - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.DeleteGroupSuccess, - DefaultMessage: message.FallbackMsg(message.DeleteGroupSuccess, - "{{.Mention}} The group has been deleted."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) } -func (s *Session) handleGroupsCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { - groups, _, err := s.cfg.GroupRepository.Fetch(context.Background(), &models.GroupFilter{ +type hndlrGroups struct { + *Session +} + +var _ commandHandler = &hndlrGroups{} + +func (hndlr *hndlrGroups) cmd() command { + return cmdGroups +} + +func (hndlr *hndlrGroups) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrGroups) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { + groups, _, err := hndlr.cfg.GroupRepository.Fetch(context.Background(), &model.GroupFilter{ ServerID: []string{m.GuildID}, - DefaultFilter: models.DefaultFilter{ + DefaultFilter: model.DefaultFilter{ Order: []string{"id ASC"}, }, }) @@ -144,47 +173,53 @@ func (s *Session) handleGroupsCommand(ctx *commandCtx, m *discordgo.MessageCreat msg := "" for i, groups := range groups { - msg += fmt.Sprintf("**%d** | %d | %s | %s | %s | %s\n", i+1, groups.ID, - getEmojiForGroupsCommand(groups.ConqueredVillagesChannelID != ""), - getEmojiForGroupsCommand(groups.LostVillagesChannelID != ""), - getEmojiForGroupsCommand(groups.ShowEnnobledBarbarians), - getEmojiForGroupsCommand(groups.ShowInternals), + boolToEmoji(groups.ConqueredVillagesChannelID != ""), + boolToEmoji(groups.LostVillagesChannelID != ""), + boolToEmoji(groups.ShowEnnobledBarbarians), + boolToEmoji(groups.ShowInternals), ) } if msg == "" { msg = ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.GroupsNoGroupsAdded, - DefaultMessage: message.FallbackMsg(message.GroupsNoGroupsAdded, - "No records to display."), }) } - s.SendEmbed(m.ChannelID, NewEmbed(). + hndlr.SendEmbed(m.ChannelID, NewEmbed(). SetTitle(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.GroupsTitle, - DefaultMessage: message.FallbackMsg(message.GroupsTitle, "Group list"), + MessageID: message.GroupsTitle, })). AddField(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.GroupsFieldTitle, - DefaultMessage: message.FallbackMsg(message.GroupsFieldTitle, "Index | ID | Conquer | Loss | Barbarian | Internal"), - }), msg). - MessageEmbed) + MessageID: message.GroupsFieldTitle, + }), msg)) } -func (s *Session) handleConqueredVillagesCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrConqueredVillages struct { + *Session +} + +var _ commandHandler = &hndlrConqueredVillages{} + +func (hndlr *hndlrConqueredVillages) cmd() command { + return cmdConqueredVillages +} + +func (hndlr *hndlrConqueredVillages) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrConqueredVillages) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength != 1 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpConqueredVillages, - DefaultMessage: message.FallbackMsg(message.HelpConqueredVillages, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - sets the channel on which notifications about conquered village will be displayed. **IMPORTANT!** Run this command on the channel you want to display these notifications."), TemplateData: map[string]interface{}{ - "Command": ConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": hndlr.cmd().WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, })) return @@ -192,11 +227,9 @@ func (s *Session) handleConqueredVillagesCommand(ctx *commandCtx, m *discordgo.M groupID, err := strconv.Atoi(args[0]) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ConqueredVillagesInvalidID, - DefaultMessage: message.FallbackMsg(message.ConqueredVillagesInvalidID, - "{{.Mention}} The group ID must be a number greater than 0."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -204,16 +237,14 @@ func (s *Session) handleConqueredVillagesCommand(ctx *commandCtx, m *discordgo.M return } - groups, _, err := s.cfg.GroupRepository.Fetch(context.Background(), &models.GroupFilter{ + groups, _, err := hndlr.cfg.GroupRepository.Fetch(context.Background(), &model.GroupFilter{ ID: []int{groupID}, ServerID: []string{m.GuildID}, }) if err != nil || len(groups) == 0 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ConqueredVillagesGroupNotFound, - DefaultMessage: message.FallbackMsg(message.ConqueredVillagesGroupNotFound, - "{{.Mention}} Group not found."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -222,29 +253,39 @@ func (s *Session) handleConqueredVillagesCommand(ctx *commandCtx, m *discordgo.M } groups[0].ConqueredVillagesChannelID = m.ChannelID - go s.cfg.GroupRepository.Update(context.Background(), groups[0]) - s.SendMessage(m.ChannelID, + go hndlr.cfg.GroupRepository.Update(context.Background(), groups[0]) + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ConqueredVillagesSuccess, - DefaultMessage: message.FallbackMsg(message.ConqueredVillagesSuccess, - "{{.Mention}} The channel has been successfully set."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) } -func (s *Session) handleDisableConqueredVillagesCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrDisableConqueredVillages struct { + *Session +} + +var _ commandHandler = &hndlrDisableConqueredVillages{} + +func (hndlr *hndlrDisableConqueredVillages) cmd() command { + return cmdDisableConqueredVillages +} + +func (hndlr *hndlrDisableConqueredVillages) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrDisableConqueredVillages) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength != 1 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpDisableConqueredVillages, - DefaultMessage: message.FallbackMsg(message.HelpDisableConqueredVillages, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - disables notifications about conquered villages."), TemplateData: map[string]interface{}{ - "Command": DisableConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": hndlr.cmd().WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, })) return @@ -252,11 +293,9 @@ func (s *Session) handleDisableConqueredVillagesCommand(ctx *commandCtx, m *disc groupID, err := strconv.Atoi(args[0]) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.DisableConqueredVillagesInvalidID, - DefaultMessage: message.FallbackMsg(message.DisableConqueredVillagesInvalidID, - "{{.Mention}} The group ID must be a number greater than 0."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -264,16 +303,14 @@ func (s *Session) handleDisableConqueredVillagesCommand(ctx *commandCtx, m *disc return } - groups, _, err := s.cfg.GroupRepository.Fetch(context.Background(), &models.GroupFilter{ + groups, _, err := hndlr.cfg.GroupRepository.Fetch(context.Background(), &model.GroupFilter{ ID: []int{groupID}, ServerID: []string{m.GuildID}, }) if err != nil || len(groups) == 0 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.DisableConqueredVillagesGroupNotFound, - DefaultMessage: message.FallbackMsg(message.DisableConqueredVillagesGroupNotFound, - "{{.Mention}} Group not found."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -283,30 +320,40 @@ func (s *Session) handleDisableConqueredVillagesCommand(ctx *commandCtx, m *disc if groups[0].ConqueredVillagesChannelID != "" { groups[0].ConqueredVillagesChannelID = "" - go s.cfg.GroupRepository.Update(context.Background(), groups[0]) + go hndlr.cfg.GroupRepository.Update(context.Background(), groups[0]) } - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.DisableConqueredVillagesSuccess, - DefaultMessage: message.FallbackMsg(message.DisableConqueredVillagesSuccess, - "{{.Mention}} Notifications about conquered villages will no longer show up."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) } -func (s *Session) handleLostVillagesCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrLostVillages struct { + *Session +} + +var _ commandHandler = &hndlrLostVillages{} + +func (hndlr *hndlrLostVillages) cmd() command { + return cmdLostVillages +} + +func (hndlr *hndlrLostVillages) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrLostVillages) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength != 1 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpLostVillages, - DefaultMessage: message.FallbackMsg(message.HelpLostVillages, - "**{{.Command}}** [group id from {{.GroupsCommand}}] sets the channel on which notifications about lost village will be displayed. **IMPORTANT!** Run this command on the channel you want to display these notifications."), TemplateData: map[string]interface{}{ - "Command": LostVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": hndlr.cmd().WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, })) return @@ -314,11 +361,9 @@ func (s *Session) handleLostVillagesCommand(ctx *commandCtx, m *discordgo.Messag groupID, err := strconv.Atoi(args[0]) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.LostVillagesInvalidID, - DefaultMessage: message.FallbackMsg(message.LostVillagesInvalidID, - "{{.Mention}} The group ID must be a number greater than 0."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -326,16 +371,14 @@ func (s *Session) handleLostVillagesCommand(ctx *commandCtx, m *discordgo.Messag return } - groups, _, err := s.cfg.GroupRepository.Fetch(context.Background(), &models.GroupFilter{ + groups, _, err := hndlr.cfg.GroupRepository.Fetch(context.Background(), &model.GroupFilter{ ID: []int{groupID}, ServerID: []string{m.GuildID}, }) if err != nil || len(groups) == 0 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.LostVillagesGroupNotFound, - DefaultMessage: message.FallbackMsg(message.LostVillagesGroupNotFound, - "{{.Mention}} Group not found."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -344,30 +387,40 @@ func (s *Session) handleLostVillagesCommand(ctx *commandCtx, m *discordgo.Messag } groups[0].LostVillagesChannelID = m.ChannelID - go s.cfg.GroupRepository.Update(context.Background(), groups[0]) + go hndlr.cfg.GroupRepository.Update(context.Background(), groups[0]) - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.LostVillagesSuccess, - DefaultMessage: message.FallbackMsg(message.LostVillagesSuccess, - "{{.Mention}} The channel has been successfully set."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) } -func (s *Session) handleDisableLostVillagesCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrDisableLostVillages struct { + *Session +} + +var _ commandHandler = &hndlrDisableLostVillages{} + +func (hndlr *hndlrDisableLostVillages) cmd() command { + return cmdDisableLostVillages +} + +func (hndlr *hndlrDisableLostVillages) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrDisableLostVillages) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength != 1 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpDisableLostVillages, - DefaultMessage: message.FallbackMsg(message.HelpDisableLostVillages, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - disables notifications about lost villages."), TemplateData: map[string]interface{}{ - "Command": DisableLostVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": hndlr.cmd().WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, })) return @@ -375,11 +428,9 @@ func (s *Session) handleDisableLostVillagesCommand(ctx *commandCtx, m *discordgo groupID, err := strconv.Atoi(args[0]) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.DisableLostVillagesInvalidID, - DefaultMessage: message.FallbackMsg(message.DisableLostVillagesInvalidID, - "{{.Mention}} The group ID must be a number greater than 0."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -387,16 +438,14 @@ func (s *Session) handleDisableLostVillagesCommand(ctx *commandCtx, m *discordgo return } - groups, _, err := s.cfg.GroupRepository.Fetch(context.Background(), &models.GroupFilter{ + groups, _, err := hndlr.cfg.GroupRepository.Fetch(context.Background(), &model.GroupFilter{ ID: []int{groupID}, ServerID: []string{m.GuildID}, }) if err != nil || len(groups) == 0 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.DisableLostVillagesGroupNotFound, - DefaultMessage: message.FallbackMsg(message.DisableLostVillagesGroupNotFound, - "{{.Mention}} Group not found."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -406,31 +455,41 @@ func (s *Session) handleDisableLostVillagesCommand(ctx *commandCtx, m *discordgo if groups[0].LostVillagesChannelID != "" { groups[0].LostVillagesChannelID = "" - go s.cfg.GroupRepository.Update(context.Background(), groups[0]) + go hndlr.cfg.GroupRepository.Update(context.Background(), groups[0]) } - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.DisableLostVillagesSuccess, - DefaultMessage: message.FallbackMsg(message.DisableLostVillagesSuccess, - "{{.Mention}} Notifications about lost villages will no longer show up."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) } -func (s *Session) handleObserveCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrObserve struct { + *Session +} + +var _ commandHandler = &hndlrObserve{} + +func (hndlr *hndlrObserve) cmd() command { + return cmdObserve +} + +func (hndlr *hndlrObserve) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrObserve) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength != 3 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpObserve, - DefaultMessage: message.FallbackMsg(message.HelpObserve, - "**{{.Command}}** [group id from {{.GroupsCommand}}] [server] [tribe id or tribe tag] - command adds a tribe to the observation group."), TemplateData: map[string]interface{}{ - "Command": ObserveCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": hndlr.cmd().WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, })) return @@ -438,11 +497,9 @@ func (s *Session) handleObserveCommand(ctx *commandCtx, m *discordgo.MessageCrea groupID, err := strconv.Atoi(args[0]) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ObserveInvalidGroupID, - DefaultMessage: message.FallbackMsg(message.ObserveInvalidGroupID, - "{{.Mention}} The group ID must be a number greater than 0."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -453,11 +510,9 @@ func (s *Session) handleObserveCommand(ctx *commandCtx, m *discordgo.MessageCrea tribeTag := strings.TrimSpace(args[2]) tribeID, err := strconv.Atoi(tribeTag) if (err != nil || tribeID <= 0) && tribeTag == "" { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ObserveInvalidTribeID, - DefaultMessage: message.FallbackMsg(message.ObserveInvalidTribeID, - "{{.Mention}} The third parameter must be a number greater than 0 or a valid string."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -465,12 +520,11 @@ func (s *Session) handleObserveCommand(ctx *commandCtx, m *discordgo.MessageCrea return } - server, err := s.cfg.API.Server.Read(serverKey, nil) + server, err := hndlr.cfg.API.Server.Read(serverKey, nil) if err != nil || server == nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ObserveServerNotFound, - DefaultMessage: message.FallbackMsg(message.ObserveServerNotFound, "{{.Mention}} Server not found."), + MessageID: message.ObserveServerNotFound, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -478,10 +532,9 @@ func (s *Session) handleObserveCommand(ctx *commandCtx, m *discordgo.MessageCrea return } if server.Status == twmodel.ServerStatusClosed { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ObserveServerIsClosed, - DefaultMessage: message.FallbackMsg(message.ObserveServerIsClosed, "{{.Mention}} Server is closed."), + MessageID: message.ObserveServerIsClosed, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -491,10 +544,10 @@ func (s *Session) handleObserveCommand(ctx *commandCtx, m *discordgo.MessageCrea var tribe *twmodel.Tribe if tribeID > 0 { - tribe, err = s.cfg.API.Tribe.Read(server.Key, tribeID) + tribe, err = hndlr.cfg.API.Tribe.Read(server.Key, tribeID) } else { list := &sdk.TribeList{} - list, err = s.cfg.API.Tribe.Browse(server.Key, 1, 0, []string{}, &twmodel.TribeFilter{ + list, err = hndlr.cfg.API.Tribe.Browse(server.Key, 1, 0, []string{}, &twmodel.TribeFilter{ Tag: []string{tribeTag}, }) if list != nil && list.Items != nil && len(list.Items) > 0 { @@ -502,10 +555,9 @@ func (s *Session) handleObserveCommand(ctx *commandCtx, m *discordgo.MessageCrea } } if err != nil || tribe == nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ObserveTribeNotFound, - DefaultMessage: message.FallbackMsg(message.ObserveTribeNotFound, "{{.Mention}} Tribe not found."), + MessageID: message.ObserveTribeNotFound, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -513,12 +565,11 @@ func (s *Session) handleObserveCommand(ctx *commandCtx, m *discordgo.MessageCrea return } - group, err := s.cfg.GroupRepository.GetByID(context.Background(), groupID) + group, err := hndlr.cfg.GroupRepository.GetByID(context.Background(), groupID) if err != nil || group.ServerID != m.GuildID { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ObserveGroupNotFound, - DefaultMessage: message.FallbackMsg(message.ObserveGroupNotFound, "{{.Mention}} Group not found."), + MessageID: message.ObserveGroupNotFound, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -527,11 +578,9 @@ func (s *Session) handleObserveCommand(ctx *commandCtx, m *discordgo.MessageCrea } if len(group.Observations) >= observationsPerGroup { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ObserveLimitHasBeenReached, - DefaultMessage: message.FallbackMsg(message.ObserveLimitHasBeenReached, - "{{.Mention}} The observation limit for this group has been reached ({{.Total}}/{{.Limit}})."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), "Total": len(group.Observations), @@ -541,33 +590,44 @@ func (s *Session) handleObserveCommand(ctx *commandCtx, m *discordgo.MessageCrea return } - go s.cfg.ObservationRepository.Store(context.Background(), &models.Observation{ + go hndlr.cfg.ObservationRepository.Store(context.Background(), &model.Observation{ Server: server.Key, TribeID: tribe.ID, GroupID: groupID, }) - s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ObserveSuccess, - DefaultMessage: message.FallbackMsg(message.ObserveSuccess, "{{.Mention}} The tribe has been added to the group."), + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.ObserveSuccess, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) } -func (s *Session) handleDeleteObservationCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrDeleteObservation struct { + *Session +} + +var _ commandHandler = &hndlrDeleteObservation{} + +func (hndlr *hndlrDeleteObservation) cmd() command { + return cmdDeleteObservation +} + +func (hndlr *hndlrDeleteObservation) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrDeleteObservation) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength != 2 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpDeleteObservation, - DefaultMessage: message.FallbackMsg(message.HelpDeleteObservation, - "**{{.Command}}** [group id from {{.GroupsCommand}}] [id from {{.ObservationsCommand}}] - removes a tribe from the observation group."), TemplateData: map[string]interface{}{ - "Command": DeleteObservationCommand.WithPrefix(s.cfg.CommandPrefix), - "ObservationsCommand": ObservationsCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": hndlr.cmd().WithPrefix(hndlr.cfg.CommandPrefix), + "ObservationsCommand": cmdObservations.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, })) return @@ -575,11 +635,9 @@ func (s *Session) handleDeleteObservationCommand(ctx *commandCtx, m *discordgo.M groupID, err := strconv.Atoi(args[0]) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.DeleteObservationInvalidGroupID, - DefaultMessage: message.FallbackMsg(message.DeleteObservationInvalidGroupID, - "{{.Mention}} The group ID must be a number greater than 0."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -588,11 +646,9 @@ func (s *Session) handleDeleteObservationCommand(ctx *commandCtx, m *discordgo.M } observationID, err := strconv.Atoi(args[1]) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.DeleteObservationInvalidTribeID, - DefaultMessage: message.FallbackMsg(message.DeleteObservationInvalidTribeID, - "{{.Mention}} The tribe ID must be a number greater than 0."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -600,12 +656,11 @@ func (s *Session) handleDeleteObservationCommand(ctx *commandCtx, m *discordgo.M return } - group, err := s.cfg.GroupRepository.GetByID(context.Background(), groupID) + group, err := hndlr.cfg.GroupRepository.GetByID(context.Background(), groupID) if err != nil || group.ServerID != m.GuildID { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.DeleteObservationGroupNotFound, - DefaultMessage: message.FallbackMsg(message.DeleteObservationGroupNotFound, "{{.Mention}} Group not found."), + MessageID: message.DeleteObservationGroupNotFound, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -613,31 +668,42 @@ func (s *Session) handleDeleteObservationCommand(ctx *commandCtx, m *discordgo.M return } - go s.cfg.ObservationRepository.Delete(context.Background(), &models.ObservationFilter{ + go hndlr.cfg.ObservationRepository.Delete(context.Background(), &model.ObservationFilter{ GroupID: []int{groupID}, ID: []int{observationID}, }) - s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.DeleteObservationSuccess, - DefaultMessage: message.FallbackMsg(message.DeleteObservationSuccess, "{{.Mention}} The tribe has been removed from the group."), + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.DeleteObservationSuccess, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) } -func (s *Session) handleObservationsCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrObservations struct { + *Session +} + +var _ commandHandler = &hndlrObservations{} + +func (hndlr *hndlrObservations) cmd() command { + return cmdObservations +} + +func (hndlr *hndlrObservations) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrObservations) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength != 1 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpObservations, - DefaultMessage: message.FallbackMsg(message.HelpObservations, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - shows a list of monitored tribes added to this group."), TemplateData: map[string]interface{}{ - "Command": ObservationsCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": hndlr.cmd().WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, })) return @@ -645,41 +711,36 @@ func (s *Session) handleObservationsCommand(ctx *commandCtx, m *discordgo.Messag groupID, err := strconv.Atoi(args[0]) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ObservationsInvalidGroupID, - DefaultMessage: message.FallbackMsg(message.ObservationsInvalidGroupID, - "{{.Mention}} The group ID must be a number greater than 0."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) return } - group, err := s.cfg.GroupRepository.GetByID(context.Background(), groupID) + group, err := hndlr.cfg.GroupRepository.GetByID(context.Background(), groupID) if err != nil || group.ServerID != m.GuildID { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ObservationsGroupNotFound, - DefaultMessage: message.FallbackMsg(message.ObservationsGroupNotFound, "{{.Mention}} Group not found."), + MessageID: message.ObservationsGroupNotFound, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) return } - observations, _, err := s.cfg.ObservationRepository.Fetch(context.Background(), &models.ObservationFilter{ + observations, _, err := hndlr.cfg.ObservationRepository.Fetch(context.Background(), &model.ObservationFilter{ GroupID: []int{groupID}, - DefaultFilter: models.DefaultFilter{ + DefaultFilter: model.DefaultFilter{ Order: []string{"id ASC"}, }, }) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.InternalServerError, - DefaultMessage: message.FallbackMsg(message.InternalServerError, - "{{.Mention}} An internal server error has occurred, please try again later."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -688,7 +749,7 @@ func (s *Session) handleObservationsCommand(ctx *commandCtx, m *discordgo.Messag } tribeIDsByServer := make(map[string][]int) - versionCodes := []twmodel.VersionCode{} + var versionCodes []twmodel.VersionCode for _, observation := range observations { tribeIDsByServer[observation.Server] = append(tribeIDsByServer[observation.Server], observation.TribeID) currentCode := twmodel.VersionCodeFromServerKey(observation.Server) @@ -704,15 +765,13 @@ func (s *Session) handleObservationsCommand(ctx *commandCtx, m *discordgo.Messag } } for server, tribeIDs := range tribeIDsByServer { - list, err := s.cfg.API.Tribe.Browse(server, 0, 0, []string{}, &twmodel.TribeFilter{ + list, err := hndlr.cfg.API.Tribe.Browse(server, 0, 0, []string{}, &twmodel.TribeFilter{ ID: tribeIDs, }) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.InternalServerError, - DefaultMessage: message.FallbackMsg(message.InternalServerError, - "{{.Mention}} An internal server error has occurred, please try again later."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -728,13 +787,13 @@ func (s *Session) handleObservationsCommand(ctx *commandCtx, m *discordgo.Messag } } } - versionList, err := s.cfg.API.Version.Browse(0, 0, []string{}, &twmodel.VersionFilter{ + versionList, err := hndlr.cfg.API.Version.Browse(0, 0, []string{}, &twmodel.VersionFilter{ Code: versionCodes, }) - msg := &MessageEmbed{} + bldr := &MessageEmbedFieldBuilder{} if len(observations) <= 0 || err != nil || versionList == nil || versionList.Items == nil { - msg.Append("-") + bldr.Append("-") } else { for i, observation := range observations { tag := "Unknown" @@ -746,32 +805,41 @@ func (s *Session) handleObservationsCommand(ctx *commandCtx, m *discordgo.Messag if version != nil { tribeURL = twurlbuilder.BuildTribeURL(observation.Server, version.Host, observation.TribeID) } - msg.Append(fmt.Sprintf("**%d** | %d - %s - %s\n", i+1, observation.ID, + bldr.Append(fmt.Sprintf("**%d** | %d - %s - %s\n", i+1, observation.ID, observation.Server, BuildLink(tag, tribeURL))) } } - s.SendEmbed(m.ChannelID, NewEmbed(). + hndlr.SendEmbed(m.ChannelID, NewEmbed(). SetTitle(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ObservationsTitle, - DefaultMessage: message.FallbackMsg(message.ObservationsTitle, - "Observed tribes\nIndex | ID - Server - Tribe"), })). - SetFields(msg.ToMessageEmbedFields()). - MessageEmbed) + SetFields(bldr.ToMessageEmbedFields())) } -func (s *Session) handleShowEnnobledBarbariansCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrShowEnnobledBarbarians struct { + *Session +} + +var _ commandHandler = &hndlrShowEnnobledBarbarians{} + +func (hndlr *hndlrShowEnnobledBarbarians) cmd() command { + return cmdShowEnnobledBarbarians +} + +func (hndlr *hndlrShowEnnobledBarbarians) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrShowEnnobledBarbarians) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength != 1 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpShowEnnobledBarbs, - DefaultMessage: message.FallbackMsg(message.HelpShowEnnobledBarbs, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - enables/disables notifications about ennobling barbarian villages."), TemplateData: map[string]interface{}{ - "Command": ShowEnnobledBarbariansCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdShowEnnobledBarbarians.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, })) return @@ -779,23 +847,20 @@ func (s *Session) handleShowEnnobledBarbariansCommand(ctx *commandCtx, m *discor groupID, err := strconv.Atoi(args[0]) if err != nil || groupID <= 0 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ShowEnnobledBarbsInvalidGroupID, - DefaultMessage: message.FallbackMsg(message.ShowEnnobledBarbsInvalidGroupID, - "{{.Mention}} The group ID must be a number greater than 0."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) return } - group, err := s.cfg.GroupRepository.GetByID(context.Background(), groupID) + group, err := hndlr.cfg.GroupRepository.GetByID(context.Background(), groupID) if err != nil || group.ServerID != m.GuildID { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ShowEnnobledBarbsGroupNotFound, - DefaultMessage: message.FallbackMsg(message.ShowEnnobledBarbsGroupNotFound, "{{.Mention}} Group not found."), + MessageID: message.ShowEnnobledBarbsGroupNotFound, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -805,12 +870,10 @@ func (s *Session) handleShowEnnobledBarbariansCommand(ctx *commandCtx, m *discor oldValue := group.ShowEnnobledBarbarians group.ShowEnnobledBarbarians = !oldValue - if err := s.cfg.GroupRepository.Update(context.Background(), group); err != nil { - s.SendMessage(m.ChannelID, + if err := hndlr.cfg.GroupRepository.Update(context.Background(), group); err != nil { + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.InternalServerError, - DefaultMessage: message.FallbackMsg(message.InternalServerError, - "{{.Mention}} An internal server error has occurred, please try again later."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -819,96 +882,47 @@ func (s *Session) handleShowEnnobledBarbariansCommand(ctx *commandCtx, m *discor } if oldValue { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ShowEnnobledBarbsSuccess1, - DefaultMessage: message.FallbackMsg(message.ShowEnnobledBarbsSuccess1, - "{{.Mention}} Notifications about conquered barbarian villages will no longer show up."), - TemplateData: map[string]interface{}{ - "Mention": m.Author.Mention(), - }, - })) - } else { - s.SendMessage(m.ChannelID, - ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ShowEnnobledBarbsSuccess2, - DefaultMessage: message.FallbackMsg(message.ShowEnnobledBarbsSuccess2, - "{{.Mention}} Enabled notifications about conquered barbarian villages."), - TemplateData: map[string]interface{}{ - "Mention": m.Author.Mention(), - }, - })) - } -} - -func (s *Session) handleChangeLanguageCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { - argsLength := len(args) - if argsLength != 1 { - s.SendMessage(m.ChannelID, - m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.HelpChangageLanguage, - DefaultMessage: message.FallbackMsg(message.HelpChangageLanguage, - "**{{.Command}}** [{{.Languages}}] - changes language."), - TemplateData: map[string]interface{}{ - "Command": ChangeLanguageCommand.WithPrefix(s.cfg.CommandPrefix), - "Languages": getAvailableLanguages(), - }, - })) - return - } - - lang := args[0] - valid := isValidLanguageTag(lang) - if !valid { - s.SendMessage(m.ChannelID, - ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ChangeLanguageLanguageNotSupported, - DefaultMessage: message.FallbackMsg(message.ChangeLanguageLanguageNotSupported, - "{{.Mention}} Language not supported."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) return } - - ctx.server.Lang = lang - if err := s.cfg.ServerRepository.Update(context.Background(), ctx.server); err != nil { - s.SendMessage(m.ChannelID, - ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.InternalServerError, - DefaultMessage: message.FallbackMsg(message.InternalServerError, - "{{.Mention}} An internal server error has occurred, please try again later."), - TemplateData: map[string]interface{}{ - "Mention": m.Author.Mention(), - }, - })) - return - } - ctx.localizer = message.NewLocalizer(lang) - - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ChangeLanguageSuccess, - DefaultMessage: message.FallbackMsg(message.ChangeLanguageSuccess, - "{{.Mention}} The language has been changed."), + MessageID: message.ShowEnnobledBarbsSuccess2, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) } -func (s *Session) handleShowInternalsCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrShowInternals struct { + *Session +} + +var _ commandHandler = &hndlrShowInternals{} + +func (hndlr *hndlrShowInternals) cmd() command { + return cmdShowInternals +} + +func (hndlr *hndlrShowInternals) requireAdmPermissions() bool { + return true +} + +func (hndlr *hndlrShowInternals) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength != 1 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, m.Author.Mention()+" "+ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpShowInternals, - DefaultMessage: message.FallbackMsg(message.HelpShowInternals, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - enables/disables notifications about in-group/in-tribe conquering."), TemplateData: map[string]interface{}{ - "Command": ShowInternalsCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": hndlr.cmd().WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, })) return @@ -916,23 +930,20 @@ func (s *Session) handleShowInternalsCommand(ctx *commandCtx, m *discordgo.Messa groupID, err := strconv.Atoi(args[0]) if err != nil || groupID <= 0 { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ShowInternalsInvalidGroupID, - DefaultMessage: message.FallbackMsg(message.ShowInternalsInvalidGroupID, - "{{.Mention}} The group ID must be a number greater than 0."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) return } - group, err := s.cfg.GroupRepository.GetByID(context.Background(), groupID) + group, err := hndlr.cfg.GroupRepository.GetByID(context.Background(), groupID) if err != nil || group.ServerID != m.GuildID { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ShowInternalsGroupNotFound, - DefaultMessage: message.FallbackMsg(message.ShowInternalsGroupNotFound, "{{.Mention}} Group not found."), + MessageID: message.ShowInternalsGroupNotFound, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -942,12 +953,10 @@ func (s *Session) handleShowInternalsCommand(ctx *commandCtx, m *discordgo.Messa oldValue := group.ShowInternals group.ShowInternals = !oldValue - if err := s.cfg.GroupRepository.Update(context.Background(), group); err != nil { - s.SendMessage(m.ChannelID, + if err := hndlr.cfg.GroupRepository.Update(context.Background(), group); err != nil { + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.InternalServerError, - DefaultMessage: message.FallbackMsg(message.InternalServerError, - "{{.Mention}} An internal server error has occurred, please try again later."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -956,24 +965,20 @@ func (s *Session) handleShowInternalsCommand(ctx *commandCtx, m *discordgo.Messa } if oldValue { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ShowInternalsSuccess1, - DefaultMessage: message.FallbackMsg(message.ShowInternalsSuccess1, - "{{.Mention}} Notifications about internals will no longer show up."), - TemplateData: map[string]interface{}{ - "Mention": m.Author.Mention(), - }, - })) - } else { - s.SendMessage(m.ChannelID, - ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.ShowInternalsSuccess2, - DefaultMessage: message.FallbackMsg(message.ShowInternalsSuccess2, - "{{.Mention}} Notifications about internals have been enabled."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, })) + return } + hndlr.SendMessage(m.ChannelID, + ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.ShowInternalsSuccess2, + TemplateData: map[string]interface{}{ + "Mention": m.Author.Mention(), + }, + })) } diff --git a/discord/public_commands.go b/discord/public_commands.go index 2f3b6ea..efba61a 100644 --- a/discord/public_commands.go +++ b/discord/public_commands.go @@ -18,17 +18,31 @@ import ( ) const ( - HelpCommand Command = "help" - TribeCommand Command = "tribe" - TopODACommand Command = "topoda" - TopODDCommand Command = "topodd" - TopODSCommand Command = "topods" - TopODCommand Command = "topod" - TopPointsCommand Command = "toppoints" - AuthorCommand Command = "author" + cmdHelp command = "help" + cmdTribe command = "tribe" + cmdTopODA command = "topoda" + cmdTopODD command = "topodd" + cmdTopODS command = "topods" + cmdTopOD command = "topod" + cmdTopPoints command = "toppoints" + cmdAuthor command = "author" ) -func (s *Session) handleHelpCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrHelp struct { + *Session +} + +var _ commandHandler = &hndlrHelp{} + +func (hndlr *hndlrHelp) cmd() command { + return cmdHelp +} + +func (hndlr *hndlrHelp) requireAdmPermissions() bool { + return false +} + +func (hndlr *hndlrHelp) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { commandsForAll := fmt.Sprintf(` - %s - %s @@ -39,50 +53,38 @@ func (s *Session) handleHelpCommand(ctx *commandCtx, m *discordgo.MessageCreate, `, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpTribeTopODA, - DefaultMessage: message.FallbackMsg(message.HelpTribeTopODA, - "**{{.Command}}** [server] [page] [tribe id or tribe tag, you can enter more than one] - generates a player list from selected tribes ordered by ODA."), TemplateData: map[string]interface{}{ - "Command": TribeCommand.WithPrefix(s.cfg.CommandPrefix) + " " + TopODACommand, + "Command": cmdTribe.WithPrefix(hndlr.cfg.CommandPrefix) + " " + cmdTopODA.String(), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpTribeTopODD, - DefaultMessage: message.FallbackMsg(message.HelpTribeTopODD, - "**{{.Command}}** [server] [page] [tribe id or tribe tag, you can enter more than one] - generates a player list from selected tribes ordered by ODD."), TemplateData: map[string]interface{}{ - "Command": TribeCommand.WithPrefix(s.cfg.CommandPrefix) + " " + TopODDCommand, + "Command": cmdTribe.WithPrefix(hndlr.cfg.CommandPrefix) + " " + cmdTopODD.String(), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpTribeTopODS, - DefaultMessage: message.FallbackMsg(message.HelpTribeTopODS, - "**{{.Command}}** [server] [page] [tribe id or tribe tag, you can enter more than one] - generates a player list from selected tribes ordered by ODS."), TemplateData: map[string]interface{}{ - "Command": TribeCommand.WithPrefix(s.cfg.CommandPrefix) + " " + TopODSCommand, + "Command": cmdTribe.WithPrefix(hndlr.cfg.CommandPrefix) + " " + cmdTopODS.String(), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpTribeTopOD, - DefaultMessage: message.FallbackMsg(message.HelpTribeTopOD, - "**{{.Command}}** [server] [page] [tribe id or tribe tag, you can enter more than one] - generates a player list from selected tribes ordered by OD."), TemplateData: map[string]interface{}{ - "Command": TribeCommand.WithPrefix(s.cfg.CommandPrefix) + " " + TopODCommand, + "Command": cmdTribe.WithPrefix(hndlr.cfg.CommandPrefix) + " " + cmdTopOD.String(), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpTribeTopPoints, - DefaultMessage: message.FallbackMsg(message.HelpTribeTopPoints, - "**{{.Command}}** [server] [page] [tribe id or tribe tag, you can enter more than one] - generates a player list from selected tribes ordered by points."), TemplateData: map[string]interface{}{ - "Command": TribeCommand.WithPrefix(s.cfg.CommandPrefix) + " " + TopPointsCommand, + "Command": cmdTribe.WithPrefix(hndlr.cfg.CommandPrefix) + " " + cmdTopPoints.String(), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpAuthor, - DefaultMessage: message.FallbackMsg(message.HelpAuthor, - "**{{.Command}}** - shows how to get in touch with the author."), TemplateData: map[string]interface{}{ - "Command": AuthorCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdAuthor.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ) @@ -100,82 +102,64 @@ func (s *Session) handleHelpCommand(ctx *commandCtx, m *discordgo.MessageCreate, `, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpAddGroup, - DefaultMessage: message.FallbackMsg(message.HelpAddGroup, - "**{{.Command}}** - adds a new observation group."), TemplateData: map[string]interface{}{ - "Command": AddGroupCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdAddGroup.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpGroups, - DefaultMessage: message.FallbackMsg(message.HelpGroups, - "**{{.Command}}** - shows you a list of groups created by this server."), TemplateData: map[string]interface{}{ - "Command": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpDeleteGroup, - DefaultMessage: message.FallbackMsg(message.HelpDeleteGroup, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - deletes an observation group."), TemplateData: map[string]interface{}{ - "Command": DeleteGroupCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdDeleteGroup.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpShowEnnobledBarbs, - DefaultMessage: message.FallbackMsg(message.HelpShowEnnobledBarbs, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - enables/disables notifications about ennobling barbarian villages."), TemplateData: map[string]interface{}{ - "Command": ShowEnnobledBarbariansCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdShowEnnobledBarbarians.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpObserve, - DefaultMessage: message.FallbackMsg(message.HelpObserve, - "**{{.Command}}** [group id from {{.GroupsCommand}}] [server] [tribe id or tribe tag] - adds a tribe to the observation group."), TemplateData: map[string]interface{}{ - "Command": ObserveCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdObserve.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpGroups, - DefaultMessage: message.FallbackMsg(message.HelpGroups, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - shows a list of monitored tribes added to this group."), TemplateData: map[string]interface{}{ - "Command": ObservationsCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdObservations.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpDeleteObservation, - DefaultMessage: message.FallbackMsg(message.HelpDeleteObservation, - "**{{.Command}}** [group id from {{.GroupsCommand}}] [id from {{.ObservationsCommand}}] - removes a tribe from the observation group."), TemplateData: map[string]interface{}{ - "Command": DeleteObservationCommand.WithPrefix(s.cfg.CommandPrefix), - "ObservationsCommand": ObservationsCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdDeleteObservation.WithPrefix(hndlr.cfg.CommandPrefix), + "ObservationsCommand": cmdObservations.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpConqueredVillages, - DefaultMessage: message.FallbackMsg(message.HelpConqueredVillages, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - sets the channel on which notifications about conquered village will be displayed. IMPORTANT! Call this command on the channel you want to display these notifications."), TemplateData: map[string]interface{}{ - "Command": ConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdConqueredVillages.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpDisableConqueredVillages, - DefaultMessage: message.FallbackMsg(message.HelpDisableConqueredVillages, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - disables notifications about conquered villages."), TemplateData: map[string]interface{}{ - "Command": DisableConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdDisableConqueredVillages.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ) @@ -190,101 +174,111 @@ func (s *Session) handleHelpCommand(ctx *commandCtx, m *discordgo.MessageCreate, `, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpLostVillages, - DefaultMessage: message.FallbackMsg(message.HelpLostVillages, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - sets the channel on which notifications about lost village will be displayed. IMPORTANT! Call this command on the channel you want to display these notifications."), TemplateData: map[string]interface{}{ - "Command": LostVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdLostVillages.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpDisableLostVillages, - DefaultMessage: message.FallbackMsg(message.HelpDisableLostVillages, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - sets the channel on which notifications about lost village will be displayed. IMPORTANT! Call this command on the channel you want to display these notifications."), TemplateData: map[string]interface{}{ - "Command": DisableLostVillagesCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdDisableLostVillages.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpShowInternals, - DefaultMessage: message.FallbackMsg(message.HelpShowInternals, - "**{{.Command}}** [group id from {{.GroupsCommand}}] - enables/disables notifications about in-group/in-tribe conquering."), TemplateData: map[string]interface{}{ - "Command": ShowInternalsCommand.WithPrefix(s.cfg.CommandPrefix), - "GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdShowInternals.WithPrefix(hndlr.cfg.CommandPrefix), + "GroupsCommand": cmdGroups.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpChangageLanguage, - DefaultMessage: message.FallbackMsg(message.HelpChangageLanguage, - "**{{.Command}}** [{{.Languages}}] - changes language."), TemplateData: map[string]interface{}{ - "Command": ChangeLanguageCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdChangeLanguage.WithPrefix(hndlr.cfg.CommandPrefix), "Languages": getAvailableLanguages(), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpCoordsTranslation, - DefaultMessage: message.FallbackMsg(message.HelpCoordsTranslation, - "**{{.Command}}** [server] - enables coords translation feature."), TemplateData: map[string]interface{}{ - "Command": CoordsTranslationCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdCoordsTranslation.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.HelpDisableCoordsTranslation, - DefaultMessage: message.FallbackMsg(message.HelpDisableCoordsTranslation, - "**{{.Command}}** - disables coords translation feature."), TemplateData: map[string]interface{}{ - "Command": DisableCoordsTranslationCommand.WithPrefix(s.cfg.CommandPrefix), + "Command": cmdDisableCoordsTranslation.WithPrefix(hndlr.cfg.CommandPrefix), }, }), ) forAdmins := ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.HelpForAdmins, - DefaultMessage: message.FallbackMsg(message.HelpForAdmins, "For admins"), + MessageID: message.HelpForAdmins, }) - s.SendEmbed(m.ChannelID, NewEmbed(). + hndlr.SendEmbed(m.ChannelID, NewEmbed(). SetTitle(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.HelpTitle, - DefaultMessage: message.FallbackMsg(message.HelpTitle, "Help"), + MessageID: message.HelpTitle, })). SetURL("https://dcbot.tribalwarshelp.com/"). SetDescription(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.HelpDescription, - DefaultMessage: message.FallbackMsg(message.HelpDescription, "Command list"), + MessageID: message.HelpDescription, })). AddField(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.HelpForAllUsers, - DefaultMessage: message.FallbackMsg(message.HelpForAllUsers, "For everyone"), + MessageID: message.HelpForAllUsers, }), commandsForAll). AddField(forAdmins, commandsForGuildAdmins). - AddField(forAdmins+" 2", commandsForGuildAdmins2). - MessageEmbed) + AddField(forAdmins+" 2", commandsForGuildAdmins2)) } -func (s *Session) handleAuthorCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { - s.SendMessage(m.ChannelID, +type hndlrAuthor struct { + *Session +} + +var _ commandHandler = &hndlrAuthor{} + +func (hndlr *hndlrAuthor) cmd() command { + return cmdAuthor +} + +func (hndlr *hndlrAuthor) requireAdmPermissions() bool { + return false +} + +func (hndlr *hndlrAuthor) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { + hndlr.SendMessage(m.ChannelID, fmt.Sprintf("%s Discord: Kichiyaki#2064 | https://dwysokinski.me/#contact.", m.Author.Mention())) } -func (s *Session) handleTribeCommand(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { +type hndlrTribe struct { + *Session +} + +var _ commandHandler = &hndlrTribe{} + +func (hndlr *hndlrTribe) cmd() command { + return cmdTribe +} + +func (hndlr *hndlrTribe) requireAdmPermissions() bool { + return false +} + +func (hndlr *hndlrTribe) execute(ctx *commandCtx, m *discordgo.MessageCreate, args ...string) { argsLength := len(args) if argsLength < 3 { return } - command := Command(args[0]) + command := command(args[0]) server := args[1] page, err := strconv.Atoi(args[2]) if err != nil || page <= 0 { - s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.TribeInvalidPage, - DefaultMessage: message.FallbackMsg(message.TribeInvalidPage, "{{.Mention}} The page must be a number greater than 0."), + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.TribeInvalidPage, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -306,9 +300,8 @@ func (s *Session) handleTribeCommand(ctx *commandCtx, m *discordgo.MessageCreate } } if len(ids) == 0 && len(tags) == 0 { - s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.TribeNoTribeID, - DefaultMessage: message.FallbackMsg(message.TribeNoTribeID, "{{.Mention}} At least one tribe id/tag is required."), + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.TribeNoTribeID, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -331,45 +324,40 @@ func (s *Session) handleTribeCommand(ctx *commandCtx, m *discordgo.MessageCreate title := "" sort := "" switch command { - case TopODACommand: + case cmdTopODA: filter.RankAttGTE = 1 sort = "rankAtt ASC" title = ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.TribeTitleOrderedByODA, - DefaultMessage: message.FallbackMsg(message.TribeTitleOrderedByODA, "Ordered by ODA"), + MessageID: message.TribeTitleOrderedByODA, }) - case TopODDCommand: + case cmdTopODD: filter.RankDefGTE = 1 sort = "rankDef ASC" title = ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.TribeTitleOrderedByODD, - DefaultMessage: message.FallbackMsg(message.TribeTitleOrderedByODD, "Ordered by ODD"), + MessageID: message.TribeTitleOrderedByODD, }) - case TopODSCommand: + case cmdTopODS: filter.RankSupGTE = 1 sort = "rankSup ASC" title = ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.TribeTitleOrderedByODS, - DefaultMessage: message.FallbackMsg(message.TribeTitleOrderedByODS, "Ordered by ODS"), + MessageID: message.TribeTitleOrderedByODS, }) - case TopODCommand: + case cmdTopOD: filter.RankTotalGTE = 1 sort = "rankTotal ASC" title = ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.TribeTitleOrderedByOD, - DefaultMessage: message.FallbackMsg(message.TribeTitleOrderedByOD, "Ordered by OD"), + MessageID: message.TribeTitleOrderedByOD, }) - case TopPointsCommand: + case cmdTopPoints: sort = "rank ASC" title = ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.TribeTitleOrderedByPoints, - DefaultMessage: message.FallbackMsg(message.TribeTitleOrderedByPoints, "Ordered by points"), + MessageID: message.TribeTitleOrderedByPoints, }) default: return } - playerList, err := s.cfg.API.Player.Browse(server, + playerList, err := hndlr.cfg.API.Player.Browse(server, limit, offset, []string{sort}, @@ -378,11 +366,9 @@ func (s *Session) handleTribeCommand(ctx *commandCtx, m *discordgo.MessageCreate Tribe: true, }) if err != nil { - s.SendMessage(m.ChannelID, + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.ApiDefaultError, - DefaultMessage: message.FallbackMsg(message.ApiDefaultError, - "{{.Mention}} Can't fetch data from the API at the moment, please try again later."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -390,9 +376,8 @@ func (s *Session) handleTribeCommand(ctx *commandCtx, m *discordgo.MessageCreate return } if playerList == nil || playerList.Total == 0 { - s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.TribeTribesNotFound, - DefaultMessage: message.FallbackMsg(message.TribeTribesNotFound, "{{.Mention}} Tribes not found."), + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + MessageID: message.TribeTribesNotFound, TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -401,10 +386,8 @@ func (s *Session) handleTribeCommand(ctx *commandCtx, m *discordgo.MessageCreate } totalPages := int(math.Ceil(float64(playerList.Total) / float64(limit))) if page > totalPages { - s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.TribeExceededMaximumNumberOfPages, - DefaultMessage: message.FallbackMsg(message.TribeExceededMaximumNumberOfPages, - "{{.Mention}} You have exceeded the maximum number of pages ({{.Page}}/{{.MaxPage}})."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), "Page": page, @@ -415,12 +398,10 @@ func (s *Session) handleTribeCommand(ctx *commandCtx, m *discordgo.MessageCreate } code := twmodel.VersionCodeFromServerKey(server) - version, err := s.cfg.API.Version.Read(code) + version, err := hndlr.cfg.API.Version.Read(code) if err != nil || version == nil { - s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + hndlr.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.InternalServerError, - DefaultMessage: message.FallbackMsg(message.InternalServerError, - "{{.Mention}} An internal server error has occurred, please try again later."), TemplateData: map[string]interface{}{ "Mention": m.Author.Mention(), }, @@ -428,7 +409,8 @@ func (s *Session) handleTribeCommand(ctx *commandCtx, m *discordgo.MessageCreate return } - msg := &MessageEmbed{} + bldr := &MessageEmbedFieldBuilder{} + bldr.SetName(title) for i, player := range playerList.Items { if player == nil { continue @@ -437,19 +419,19 @@ func (s *Session) handleTribeCommand(ctx *commandCtx, m *discordgo.MessageCreate rank := 0 score := 0 switch command { - case TopODACommand: + case cmdTopODA: rank = player.RankAtt score = player.ScoreAtt - case TopODDCommand: + case cmdTopODD: rank = player.RankDef score = player.ScoreDef - case TopODSCommand: + case cmdTopODS: rank = player.RankSup score = player.ScoreSup - case TopODCommand: + case cmdTopOD: rank = player.RankTotal score = player.ScoreTotal - case TopPointsCommand: + case cmdTopPoints: rank = player.Rank score = player.Points } @@ -461,10 +443,8 @@ func (s *Session) handleTribeCommand(ctx *commandCtx, m *discordgo.MessageCreate tribeURL = twurlbuilder.BuildTribeURL(server, version.Host, player.Tribe.ID) } - msg.Append(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ + bldr.Append(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ MessageID: message.TribeMessageLine, - DefaultMessage: message.FallbackMsg(message.TribeMessageLine, - "**{{.Index}}**. [`{{.PlayerName}}`]({{.PlayerURL}}) (Tribe: [`{{.TribeTag}}`]({{.TribeURL}}) | Rank: **{{.Rank}}** | Score: **{{.Score}}**)\n"), TemplateData: map[string]interface{}{ "Index": offset + i + 1, "PlayerName": player.Name, @@ -477,16 +457,14 @@ func (s *Session) handleTribeCommand(ctx *commandCtx, m *discordgo.MessageCreate })) } - s.SendEmbed(m.ChannelID, NewEmbed(). + hndlr.SendEmbed(m.ChannelID, NewEmbed(). SetTitle(title). - SetFields(msg.ToMessageEmbedFields()). + SetFields(bldr.ToMessageEmbedFields()). SetFooter(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{ - MessageID: message.PaginationLabelDisplayedPage, - DefaultMessage: message.FallbackMsg(message.PaginationLabelDisplayedPage, "{{.Page}} of {{.MaxPage}}"), + MessageID: message.PaginationLabelDisplayedPage, TemplateData: map[string]interface{}{ "Page": page, "MaxPage": totalPages, }, - })). - MessageEmbed) + }))) } diff --git a/discord/session.go b/discord/session.go new file mode 100644 index 0000000..5bd128d --- /dev/null +++ b/discord/session.go @@ -0,0 +1,217 @@ +package discord + +import ( + "context" + "github.com/pkg/errors" + "strings" + + "github.com/sirupsen/logrus" + + "github.com/tribalwarshelp/dcbot/message" + + "github.com/tribalwarshelp/golang-sdk/sdk" + + "github.com/tribalwarshelp/dcbot/group" + "github.com/tribalwarshelp/dcbot/model" + "github.com/tribalwarshelp/dcbot/observation" + "github.com/tribalwarshelp/dcbot/server" + + "github.com/bwmarrin/discordgo" +) + +type SessionConfig struct { + Token string + CommandPrefix string + Status string + ServerRepository server.Repository + GroupRepository group.Repository + ObservationRepository observation.Repository + API *sdk.SDK +} + +type Session struct { + dg *discordgo.Session + cfg SessionConfig + handlers commandHandlers + messageProcessors []messageProcessor +} + +func New(cfg SessionConfig) (*Session, error) { + var err error + s := &Session{ + cfg: cfg, + } + s.dg, err = discordgo.New("Bot " + cfg.Token) + if err != nil { + return nil, err + } + if err := s.init(); err != nil { + return nil, err + } + return s, nil +} + +func (s *Session) init() error { + s.handlers = commandHandlers{ + &hndlrHelp{s}, + &hndlrAuthor{s}, + &hndlrTribe{s}, + &hndlrChangeLanguage{s}, + &hndlrAddGroup{s}, + &hndlrDeleteGroup{s}, + &hndlrGroups{s}, + &hndlrObserve{s}, + &hndlrDeleteObservation{s}, + &hndlrObservations{s}, + &hndlrConqueredVillages{s}, + &hndlrDisableConqueredVillages{s}, + &hndlrLostVillages{s}, + &hndlrDisableLostVillages{s}, + &hndlrShowEnnobledBarbarians{s}, + &hndlrShowInternals{s}, + &hndlrCoordsTranslation{s}, + &hndlrDisableCoordsTranslation{s}, + } + s.messageProcessors = []messageProcessor{ + &procTranslateCoords{s}, + } + + s.dg.AddHandler(s.handleNewMessage) + + err := s.dg.Open() + if err != nil { + return errors.Wrap(err, "error opening ws connection") + } + + if err := s.UpdateStatus(s.cfg.Status); err != nil { + return err + } + + return nil +} + +func (s *Session) Close() error { + return s.dg.Close() +} + +func (s *Session) SendMessage(channelID, message string) error { + _, err := s.dg.ChannelMessageSend(channelID, message) + return err +} + +func (s *Session) SendEmbed(channelID string, e *Embed) error { + for _, fields := range splitEmbedFields(e) { + fieldsLen := len(fields) + for i := 0; i < fieldsLen; i += EmbedLimitField { + end := i + EmbedLimitField + if end > fieldsLen { + end = fieldsLen + } + e.Fields = fields[i:end] + if _, err := s.dg.ChannelMessageSendEmbed(channelID, e.MessageEmbed); err != nil { + return err + } + } + } + + return nil +} + +func (s *Session) UpdateStatus(status string) error { + if err := s.dg.UpdateStatus(0, status); err != nil { + return err + } + return nil +} + +func (s *Session) IsGuildMember(guildID string) (bool, error) { + _, err := s.dg.State.Guild(guildID) + if err != nil { + if _, err = s.dg.Guild(guildID); err != nil { + return false, err + } + } + return true, nil +} + +func (s *Session) handleNewMessage(_ *discordgo.Session, m *discordgo.MessageCreate) { + if m.Author.ID == s.dg.State.User.ID || m.Author.Bot { + return + } + + parts := strings.Split(m.Content, " ") + args := parts[1:] + svr := &model.Server{ + ID: m.GuildID, + Lang: message.GetDefaultLanguage().String(), + } + if svr.ID != "" { + if err := s.cfg.ServerRepository.Store(context.Background(), svr); err != nil { + return + } + } + ctx := &commandCtx{ + server: svr, + localizer: message.NewLocalizer(svr.Lang), + } + + h := s.handlers.find(s.cfg.CommandPrefix, parts[0]) + if h != nil { + if h.requireAdmPermissions() && m.GuildID != "" { + has, err := s.memberHasPermission(m.GuildID, m.Author.ID, discordgo.PermissionAdministrator) + if err != nil || !has { + return + } + } + log. + WithFields(logrus.Fields{ + "serverID": svr.ID, + "lang": svr.Lang, + "command": parts[0], + "args": args, + "authorID": m.Author.ID, + "authorUsername": m.Author.Username, + }). + Infof(`handleNewMessage: Executing command "%s"...`, m.Content) + h.execute(ctx, m, args...) + return + } + + for _, p := range s.messageProcessors { + p.process(ctx, m) + } +} + +func (s *Session) memberHasPermission(guildID string, userID string, permission int) (bool, error) { + member, err := s.dg.State.Member(guildID, userID) + if err != nil { + if member, err = s.dg.GuildMember(guildID, userID); err != nil { + return false, err + } + } + + // check if user is a guild owner + guild, err := s.dg.State.Guild(guildID) + if err != nil { + if guild, err = s.dg.Guild(guildID); err != nil { + return false, err + } + } + if guild.OwnerID == userID { + return true, nil + } + + // Iterate through the role IDs stored in member.Roles + // to check permissions + for _, roleID := range member.Roles { + role, err := s.dg.State.Role(guildID, roleID) + if err != nil { + return false, err + } + if role.Permissions&permission != 0 { + return true, nil + } + } + + return false, nil +} diff --git a/go.mod b/go.mod index f2c3e7d..c878770 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,5 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/tribalwarshelp/golang-sdk v0.0.0-20210717112029-bb518cbee33d github.com/tribalwarshelp/shared v0.0.0-20210717094429-6efa1a4f614c - go.opentelemetry.io/otel v0.20.0 // indirect golang.org/x/text v0.3.6 ) diff --git a/go.sum b/go.sum index 1b467d1..2ce4ff3 100644 --- a/go.sum +++ b/go.sum @@ -7,7 +7,6 @@ github.com/Kichiyaki/appmode v0.0.0-20210502105643-0a26207c548d/go.mod h1:41p1KT github.com/Kichiyaki/go-pg-logrus-query-logger/v10 v10.0.0-20210428180109-fb97298564d9 h1:S/08K0AD4bXYeSPJKei8ZbumDy1JNARZsgYbNZgr9Tk= github.com/Kichiyaki/go-pg-logrus-query-logger/v10 v10.0.0-20210428180109-fb97298564d9/go.mod h1:ADHVWnGlWcRn1aGthuh7I1Lrn6zzsjkVJju151dXyDw= github.com/Kichiyaki/go-php-serialize v0.0.0-20200601110855-47b6982acf83/go.mod h1:+iGkf5HfOVeRVd9K7qQDucIl+/Kt3MyenMa90b/O/c4= -github.com/Kichiyaki/gopgutil/v10 v10.0.0-20210505093434-655fa2df248f/go.mod h1:MSAEhr8oeK+Rhjhqyl31/8/AI88thYky80OyD8mheDA= github.com/Kichiyaki/gopgutil/v10 v10.0.0-20210521204542-cc672e361b3d h1:7ZJVfFgAR0zNf5fNc6M9v2PZbXvTgGgDjQo4/+NIezQ= github.com/Kichiyaki/gopgutil/v10 v10.0.0-20210521204542-cc672e361b3d/go.mod h1:MSAEhr8oeK+Rhjhqyl31/8/AI88thYky80OyD8mheDA= github.com/Kichiyaki/goutil v0.0.0-20210502095630-318d17091eab/go.mod h1:+HhI932Xb0xrCodNcCv5GPiCjLYhDxWhCtlEqMIJhB4= @@ -29,10 +28,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/go-pg/pg/v10 v10.9.1 h1:kU4t84zWGGaU0Qsu49FbNtToUVrlSTkNOngW8aQmwvk= github.com/go-pg/pg/v10 v10.9.1/go.mod h1:rgmTPgHgl5EN2CNKKoMwC7QT62t8BqsdpEkUQuiZMQs= -github.com/go-pg/pg/v10 v10.10.1 h1:82lLX4KGs2wOFOvVVIICoU0Si1fLu6Aitniu73HaDuM= -github.com/go-pg/pg/v10 v10.10.1/go.mod h1:EmoJGYErc+stNN/1Jf+o4csXuprjxcRztBnn6cHe38E= github.com/go-pg/pg/v10 v10.10.2 h1:8G2DdKrB3/0nRIlpur0HySEWBJnHYUByiC0ko4XzE8w= github.com/go-pg/pg/v10 v10.10.2/go.mod h1:EmoJGYErc+stNN/1Jf+o4csXuprjxcRztBnn6cHe38E= github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= @@ -54,7 +50,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= @@ -100,27 +95,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= -github.com/tribalwarshelp/golang-sdk v0.0.0-20210505172651-7dc458534a8c h1:Ip4C8G6zjdHnsHeyuajb5BZeRrls/Ym14Vww8e9jGLM= -github.com/tribalwarshelp/golang-sdk v0.0.0-20210505172651-7dc458534a8c/go.mod h1:0VNYCWHFE5Szvd/b5RpnhtHdvYwR/6XfB/iFjgPTAtY= -github.com/tribalwarshelp/golang-sdk v0.0.0-20210705044231-26168540b50a h1:LAru/bPO0v+vzhYhzQoDoEsQ4nNW2IQzX1xs8MnTZjY= -github.com/tribalwarshelp/golang-sdk v0.0.0-20210705044231-26168540b50a/go.mod h1:SWLnHJnMhZcr1hpIyiwN7A5u6yaJqQ64Dv0I49f4OLc= -github.com/tribalwarshelp/golang-sdk v0.0.0-20210712101301-0e40924ae102 h1:a5Zb0i8m/5KDqm67vt4Z65V7ypgXK145vU5AauvfoA0= -github.com/tribalwarshelp/golang-sdk v0.0.0-20210712101301-0e40924ae102/go.mod h1:TuTVOLripDuR/YUXRkzC0QnbAtrabSlNF3c1Vl7S9wA= -github.com/tribalwarshelp/golang-sdk v0.0.0-20210717082347-98161a4b2533 h1:84UBdpylmqqlGDwOmRUbZAXX/CnJCCKWnnbTHZNyZ7A= -github.com/tribalwarshelp/golang-sdk v0.0.0-20210717082347-98161a4b2533/go.mod h1:2uacP5AiUf1HWK/gwun5Y8VHLybIMaocsSrnluDBNFo= github.com/tribalwarshelp/golang-sdk v0.0.0-20210717112029-bb518cbee33d h1:Xh5rVjUP55jOZZM3PaI2AvMpCoiRadwIe5gfDBpDC+0= github.com/tribalwarshelp/golang-sdk v0.0.0-20210717112029-bb518cbee33d/go.mod h1:COakTplg+GL/4mIPyry00PcZqOcCNCDXkhM36Dci+kA= -github.com/tribalwarshelp/shared v0.0.0-20210505172413-bf85190fd66d/go.mod h1:GBnSKQrxL8Nmi3MViIzZVbyP9+ugd28gWArsSvw1iVU= -github.com/tribalwarshelp/shared v0.0.0-20210521205055-fcef062f6b8a h1:nKEymuRmwv5qGS2uyURYwN6JKqdqEFsb6Q9Fbn4iQbY= -github.com/tribalwarshelp/shared v0.0.0-20210521205055-fcef062f6b8a/go.mod h1://pyC+3J9nFBdpvNpVf1w7+LNNOw+L/kqxHlFK+kuLk= -github.com/tribalwarshelp/shared v0.0.0-20210606172508-1eaae48e4c3e h1:qez3K2fVLaocxHBgDHP6z/ZiTlzVm1DF0Y81jY5bnHo= -github.com/tribalwarshelp/shared v0.0.0-20210606172508-1eaae48e4c3e/go.mod h1://pyC+3J9nFBdpvNpVf1w7+LNNOw+L/kqxHlFK+kuLk= -github.com/tribalwarshelp/shared v0.0.0-20210704200704-f3e5bd092975 h1:H0Dm+nHpbiNz3wmpYV1UcF//6KXN8bRO260QG5zdEX0= -github.com/tribalwarshelp/shared v0.0.0-20210704200704-f3e5bd092975/go.mod h1:R7761+5rdyaMUM2dPMgBma05Z8XDgA3bCl/Grydb09g= -github.com/tribalwarshelp/shared v0.0.0-20210712092456-dcd6203b1fd9 h1:rpuecZUAul4lCdcWjYx05ZNDff6RBbwJatxOcYpFjRY= -github.com/tribalwarshelp/shared v0.0.0-20210712092456-dcd6203b1fd9/go.mod h1:M+33qeTM/oxG7IyxKbIZqs0wqpB5nEHPfgdo29kqHMI= -github.com/tribalwarshelp/shared v0.0.0-20210717070514-5030d62000d4 h1:lnbuxPwiWsCTLeFCvuxDRNlqYvD1O/ssYE52HInqdAY= -github.com/tribalwarshelp/shared v0.0.0-20210717070514-5030d62000d4/go.mod h1:6xooIU27fjagr/KVBPayktW4gn9ylTdBPGgxlQ6+x9s= github.com/tribalwarshelp/shared v0.0.0-20210717094429-6efa1a4f614c h1:dgWoORgR3COUbO0Dhho+fB1axhsKGXadaxeCiw9APFA= github.com/tribalwarshelp/shared v0.0.0-20210717094429-6efa1a4f614c/go.mod h1:6xooIU27fjagr/KVBPayktW4gn9ylTdBPGgxlQ6+x9s= github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= @@ -133,17 +109,9 @@ github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgq github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= go.opentelemetry.io/otel v0.19.0/go.mod h1:j9bF567N9EfomkSidSfmMwIwIBuP37AMAIzVW85OxSg= -go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel/metric v0.19.0/go.mod h1:8f9fglJPRnXuskQmKpnad31lcLJ2VmNNqIsx/uIwBSc= -go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/oteltest v0.19.0/go.mod h1:tI4yxwh8U21v7JD6R3BcA/2+RBoTKFexE/PJ/nSO7IA= -go.opentelemetry.io/otel/oteltest v0.20.0 h1:HiITxCawalo5vQzdHfKeZurV8x7ljcqAgiWzF6Vaeaw= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/trace v0.19.0/go.mod h1:4IXiNextNOpPnRlI4ryK69mn5iC84bjBWZQA5DXz/qg= -go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -185,14 +153,11 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7 h1:iGu644GcxtEcrInvDsQRCwJjtCIOlT2V7IRt6ah2Whw= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -235,8 +200,8 @@ gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8 h1:jL/vaozO53FMfZLySWM+4nulF3gQEC6q5jH90LPomDo= gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/group/repository.go b/group/repository.go index 8945641..ffe15f1 100644 --- a/group/repository.go +++ b/group/repository.go @@ -3,14 +3,14 @@ package group import ( "context" - "github.com/tribalwarshelp/dcbot/models" + "github.com/tribalwarshelp/dcbot/model" ) type Repository interface { - Store(ctx context.Context, group *models.Group) error - StoreMany(ctx context.Context, groups []*models.Group) error - Update(ctx context.Context, group *models.Group) error - Delete(ctx context.Context, filter *models.GroupFilter) ([]*models.Group, error) - GetByID(ctx context.Context, id int) (*models.Group, error) - Fetch(ctx context.Context, filter *models.GroupFilter) ([]*models.Group, int, error) + Store(ctx context.Context, group *model.Group) error + StoreMany(ctx context.Context, groups []*model.Group) error + Update(ctx context.Context, group *model.Group) error + Delete(ctx context.Context, filter *model.GroupFilter) ([]*model.Group, error) + GetByID(ctx context.Context, id int) (*model.Group, error) + Fetch(ctx context.Context, filter *model.GroupFilter) ([]*model.Group, int, error) } diff --git a/group/repository/group_pg_repo.go b/group/repository/group_pg_repo.go index adf4f14..e5c2d03 100644 --- a/group/repository/group_pg_repo.go +++ b/group/repository/group_pg_repo.go @@ -4,42 +4,44 @@ import ( "context" "github.com/tribalwarshelp/dcbot/group" - "github.com/tribalwarshelp/dcbot/models" + "github.com/tribalwarshelp/dcbot/model" "github.com/go-pg/pg/v10" "github.com/go-pg/pg/v10/orm" "github.com/pkg/errors" ) -type pgRepo struct { +type PGRepository struct { *pg.DB } -func NewPgRepo(db *pg.DB) (group.Repository, error) { - if err := db.Model((*models.Group)(nil)).CreateTable(&orm.CreateTableOptions{ +var _ group.Repository = &PGRepository{} + +func NewPgRepo(db *pg.DB) (*PGRepository, error) { + if err := db.Model((*model.Group)(nil)).CreateTable(&orm.CreateTableOptions{ IfNotExists: true, FKConstraints: true, }); err != nil { return nil, errors.Wrap(err, "couldn't create the 'groups' table") } - return &pgRepo{db}, nil + return &PGRepository{db}, nil } -func (repo *pgRepo) Store(ctx context.Context, group *models.Group) error { +func (repo *PGRepository) Store(ctx context.Context, group *model.Group) error { if _, err := repo.Model(group).Returning("*").Context(ctx).Insert(); err != nil { return err } return nil } -func (repo *pgRepo) StoreMany(ctx context.Context, groups []*models.Group) error { +func (repo *PGRepository) StoreMany(ctx context.Context, groups []*model.Group) error { if _, err := repo.Model(&groups).Returning("*").Context(ctx).Insert(); err != nil { return err } return nil } -func (repo *pgRepo) Update(ctx context.Context, group *models.Group) error { +func (repo *PGRepository) Update(ctx context.Context, group *model.Group) error { if _, err := repo. Model(group). WherePK(). @@ -51,8 +53,8 @@ func (repo *pgRepo) Update(ctx context.Context, group *models.Group) error { return nil } -func (repo *pgRepo) GetByID(ctx context.Context, id int) (*models.Group, error) { - g := &models.Group{ +func (repo *PGRepository) GetByID(ctx context.Context, id int) (*model.Group, error) { + g := &model.Group{ ID: id, } if err := repo. @@ -67,9 +69,9 @@ func (repo *pgRepo) GetByID(ctx context.Context, id int) (*models.Group, error) return g, nil } -func (repo *pgRepo) Fetch(ctx context.Context, f *models.GroupFilter) ([]*models.Group, int, error) { +func (repo *PGRepository) Fetch(ctx context.Context, f *model.GroupFilter) ([]*model.Group, int, error) { var err error - var data []*models.Group + var data []*model.Group query := repo.Model(&data).Relation("Server").Relation("Observations").Context(ctx) if f != nil { @@ -87,8 +89,8 @@ func (repo *pgRepo) Fetch(ctx context.Context, f *models.GroupFilter) ([]*models return data, total, nil } -func (repo *pgRepo) Delete(ctx context.Context, f *models.GroupFilter) ([]*models.Group, error) { - var data []*models.Group +func (repo *PGRepository) Delete(ctx context.Context, f *model.GroupFilter) ([]*model.Group, error) { + var data []*model.Group query := repo.Model(&data).Context(ctx) if f != nil { diff --git a/main.go b/main.go index bf34a61..7a13918 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,15 +23,13 @@ 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 ( commandPrefix = "tw!" + status = "tribalwarshelp.com | tw!help" ) -var status = "tribalwarshelp.com | " + discord.HelpCommand.WithPrefix(commandPrefix).String() - func init() { os.Setenv("TZ", "UTC") @@ -40,7 +37,7 @@ func init() { godotenv.Load(".env.local") } - setupLogger() + prepareLogger() } func main() { @@ -64,14 +61,14 @@ 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, }) } - serverRepo, err := serverepository.NewPgRepo(db) + serverRepo, err := serverepository.NewPgRepository(db) if err != nil { logrus.Fatal(err) } @@ -79,7 +76,7 @@ func main() { if err != nil { logrus.Fatal(err) } - observationRepo, err := observationrepository.NewPgRepo(db) + observationRepo, err := observationrepository.NewPgRepository(db) if err != nil { logrus.Fatal(err) } @@ -106,14 +103,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 +123,7 @@ func main() { logrus.Info("shutting down...") } -func setupLogger() { +func prepareLogger() { if appmode.Equals(appmode.DevelopmentMode) { logrus.SetLevel(logrus.DebugLevel) } diff --git a/message/helpers.go b/message/helpers.go deleted file mode 100644 index 250a02b..0000000 --- a/message/helpers.go +++ /dev/null @@ -1,12 +0,0 @@ -package message - -import "github.com/nicksnyder/go-i18n/v2/i18n" - -func FallbackMsg(id string, msg string) *i18n.Message { - return &i18n.Message{ - ID: id, - One: msg, - Many: msg, - Other: msg, - } -} diff --git a/message/ids.go b/message/ids.go index e5f50b8..a4e11cd 100644 --- a/message/ids.go +++ b/message/ids.go @@ -44,8 +44,8 @@ const ( TribeExceededMaximumNumberOfPages = "tribe.exceededMaximumNumberOfPages" TribeMessageLine = "tribe.messageLine" - AddGroupGroupLimitHasBeenReached = "addGroup.groupLimitHasBeenReached" - AddGroupSuccess = "addGroup.success" + AddGroupLimitHasBeenReached = "addGroup.groupLimitHasBeenReached" + AddGroupSuccess = "addGroup.success" DeleteGroupInvalidID = "deleteGroup.invalidID" DeleteGroupSuccess = "deleteGroup.success" diff --git a/models/default_filter.go b/model/default_filter.go similarity index 95% rename from models/default_filter.go rename to model/default_filter.go index 951539d..63c421a 100644 --- a/models/default_filter.go +++ b/model/default_filter.go @@ -1,4 +1,4 @@ -package models +package model import "github.com/go-pg/pg/v10/orm" diff --git a/models/group.go b/model/group.go similarity index 98% rename from models/group.go rename to model/group.go index 3b2ca92..244df14 100644 --- a/models/group.go +++ b/model/group.go @@ -1,4 +1,4 @@ -package models +package model import ( "github.com/Kichiyaki/gopgutil/v10" diff --git a/models/observation.go b/model/observation.go similarity index 99% rename from models/observation.go rename to model/observation.go index f959c13..4113bef 100644 --- a/models/observation.go +++ b/model/observation.go @@ -1,4 +1,4 @@ -package models +package model import ( "github.com/Kichiyaki/gopgutil/v10" diff --git a/models/server.go b/model/server.go similarity index 98% rename from models/server.go rename to model/server.go index b0014d8..0fd6b23 100644 --- a/models/server.go +++ b/model/server.go @@ -1,4 +1,4 @@ -package models +package model import ( "github.com/Kichiyaki/gopgutil/v10" diff --git a/observation/repository.go b/observation/repository.go index 111a737..6387ee4 100644 --- a/observation/repository.go +++ b/observation/repository.go @@ -3,14 +3,14 @@ package observation import ( "context" - "github.com/tribalwarshelp/dcbot/models" + "github.com/tribalwarshelp/dcbot/model" ) type Repository interface { - Store(ctx context.Context, observation *models.Observation) error - StoreMany(ctx context.Context, observations []*models.Observation) error - Update(ctx context.Context, observation *models.Observation) error - Delete(ctx context.Context, filter *models.ObservationFilter) ([]*models.Observation, error) - Fetch(ctx context.Context, filter *models.ObservationFilter) ([]*models.Observation, int, error) + Store(ctx context.Context, observation *model.Observation) error + StoreMany(ctx context.Context, observations []*model.Observation) error + Update(ctx context.Context, observation *model.Observation) error + Delete(ctx context.Context, filter *model.ObservationFilter) ([]*model.Observation, error) + Fetch(ctx context.Context, filter *model.ObservationFilter) ([]*model.Observation, int, error) FetchServers(ctx context.Context) ([]string, error) } diff --git a/observation/repository/pg_repository.go b/observation/repository/pg_repository.go index 27a9e4b..c836ade 100644 --- a/observation/repository/pg_repository.go +++ b/observation/repository/pg_repository.go @@ -3,7 +3,7 @@ package repository import ( "context" - "github.com/tribalwarshelp/dcbot/models" + "github.com/tribalwarshelp/dcbot/model" "github.com/tribalwarshelp/dcbot/observation" "github.com/go-pg/pg/v10" @@ -11,35 +11,37 @@ import ( "github.com/pkg/errors" ) -type pgRepo struct { +type PGRepository struct { *pg.DB } -func NewPgRepo(db *pg.DB) (observation.Repository, error) { - if err := db.Model((*models.Observation)(nil)).CreateTable(&orm.CreateTableOptions{ +var _ observation.Repository = &PGRepository{} + +func NewPgRepository(db *pg.DB) (*PGRepository, error) { + if err := db.Model((*model.Observation)(nil)).CreateTable(&orm.CreateTableOptions{ IfNotExists: true, FKConstraints: true, }); err != nil { return nil, errors.Wrap(err, "couldn't create the 'observations' table") } - return &pgRepo{db}, nil + return &PGRepository{db}, nil } -func (repo *pgRepo) Store(ctx context.Context, observation *models.Observation) error { +func (repo *PGRepository) Store(ctx context.Context, observation *model.Observation) error { if _, err := repo.Model(observation).Returning("*").Context(ctx).Insert(); err != nil { return err } return nil } -func (repo *pgRepo) StoreMany(ctx context.Context, observations []*models.Observation) error { +func (repo *PGRepository) StoreMany(ctx context.Context, observations []*model.Observation) error { if _, err := repo.Model(&observations).Returning("*").Context(ctx).Insert(); err != nil { return err } return nil } -func (repo *pgRepo) Update(ctx context.Context, observation *models.Observation) error { +func (repo *PGRepository) Update(ctx context.Context, observation *model.Observation) error { if _, err := repo. Model(observation). WherePK(). @@ -51,9 +53,9 @@ func (repo *pgRepo) Update(ctx context.Context, observation *models.Observation) return nil } -func (repo *pgRepo) Fetch(ctx context.Context, f *models.ObservationFilter) ([]*models.Observation, int, error) { +func (repo *PGRepository) Fetch(ctx context.Context, f *model.ObservationFilter) ([]*model.Observation, int, error) { var err error - var data []*models.Observation + var data []*model.Observation query := repo.Model(&data).Context(ctx) if f != nil { @@ -71,10 +73,10 @@ func (repo *pgRepo) Fetch(ctx context.Context, f *models.ObservationFilter) ([]* return data, total, nil } -func (repo *pgRepo) FetchServers(ctx context.Context) ([]string, error) { +func (repo *PGRepository) FetchServers(ctx context.Context) ([]string, error) { var res []string err := repo. - Model(&models.Observation{}). + Model(&model.Observation{}). Column("server"). Context(ctx). Group("server"). @@ -83,8 +85,8 @@ func (repo *pgRepo) FetchServers(ctx context.Context) ([]string, error) { return res, err } -func (repo *pgRepo) Delete(ctx context.Context, f *models.ObservationFilter) ([]*models.Observation, error) { - var data []*models.Observation +func (repo *PGRepository) Delete(ctx context.Context, f *model.ObservationFilter) ([]*model.Observation, error) { + var data []*model.Observation query := repo.Model(&data).Context(ctx) if f != nil { diff --git a/server/repository.go b/server/repository.go index b660afd..a6736f0 100644 --- a/server/repository.go +++ b/server/repository.go @@ -3,12 +3,12 @@ package server import ( "context" - "github.com/tribalwarshelp/dcbot/models" + "github.com/tribalwarshelp/dcbot/model" ) type Repository interface { - Store(ctx context.Context, server *models.Server) error - Update(ctx context.Context, server *models.Server) error - Delete(ctx context.Context, filter *models.ServerFilter) ([]*models.Server, error) - Fetch(ctx context.Context, filter *models.ServerFilter) ([]*models.Server, int, error) + Store(ctx context.Context, server *model.Server) error + Update(ctx context.Context, server *model.Server) error + Delete(ctx context.Context, filter *model.ServerFilter) ([]*model.Server, error) + Fetch(ctx context.Context, filter *model.ServerFilter) ([]*model.Server, int, error) } diff --git a/server/repository/server_pg_repo.go b/server/repository/server_pg_repo.go index 68f09e5..1e70ace 100644 --- a/server/repository/server_pg_repo.go +++ b/server/repository/server_pg_repo.go @@ -3,7 +3,7 @@ package repository import ( "context" - "github.com/tribalwarshelp/dcbot/models" + "github.com/tribalwarshelp/dcbot/model" "github.com/tribalwarshelp/dcbot/server" "github.com/go-pg/pg/v10" @@ -11,20 +11,22 @@ import ( "github.com/pkg/errors" ) -type pgRepo struct { +type PGRepository struct { *pg.DB } -func NewPgRepo(db *pg.DB) (server.Repository, error) { - if err := db.Model((*models.Server)(nil)).CreateTable(&orm.CreateTableOptions{ +var _ server.Repository = &PGRepository{} + +func NewPgRepository(db *pg.DB) (*PGRepository, error) { + if err := db.Model((*model.Server)(nil)).CreateTable(&orm.CreateTableOptions{ IfNotExists: true, }); err != nil { return nil, errors.Wrap(err, "couldn't create the 'servers' table") } - return &pgRepo{db}, nil + return &PGRepository{db}, nil } -func (repo *pgRepo) Store(ctx context.Context, server *models.Server) error { +func (repo *PGRepository) Store(ctx context.Context, server *model.Server) error { if _, err := repo. Model(server). Where("id = ?id"). @@ -37,7 +39,7 @@ func (repo *pgRepo) Store(ctx context.Context, server *models.Server) error { return nil } -func (repo *pgRepo) Update(ctx context.Context, server *models.Server) error { +func (repo *PGRepository) Update(ctx context.Context, server *model.Server) error { if _, err := repo. Model(server). WherePK(). @@ -49,9 +51,9 @@ func (repo *pgRepo) Update(ctx context.Context, server *models.Server) error { return nil } -func (repo *pgRepo) Fetch(ctx context.Context, f *models.ServerFilter) ([]*models.Server, int, error) { +func (repo *PGRepository) Fetch(ctx context.Context, f *model.ServerFilter) ([]*model.Server, int, error) { var err error - var data []*models.Server + var data []*model.Server query := repo.Model(&data).Context(ctx).Relation("Groups") if f != nil { @@ -69,8 +71,8 @@ func (repo *pgRepo) Fetch(ctx context.Context, f *models.ServerFilter) ([]*model return data, total, nil } -func (repo *pgRepo) Delete(ctx context.Context, f *models.ServerFilter) ([]*models.Server, error) { - var data []*models.Server +func (repo *PGRepository) Delete(ctx context.Context, f *model.ServerFilter) ([]*model.Server, error) { + var data []*model.Server query := repo.Model(&data).Context(ctx) if f != nil { diff --git a/tw/twutil/find_version_by_code.go b/util/twutil/find_version_by_code.go similarity index 100% rename from tw/twutil/find_version_by_code.go rename to util/twutil/find_version_by_code.go diff --git a/tw/twutil/tribalwars.go b/util/twutil/tribalwars.go similarity index 100% rename from tw/twutil/tribalwars.go rename to util/twutil/tribalwars.go