Merge pull request #31 from tribalwarshelp/chore/refactor

chore: refactor
This commit is contained in:
Dawid Wysokiński 2021-07-18 12:39:48 +02:00 committed by GitHub
commit 8dc36f29ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1371 additions and 1412 deletions

View File

@ -1,7 +1,13 @@
package cron
import (
"context"
"fmt"
"github.com/Kichiyaki/appmode"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
"github.com/tribalwarshelp/shared/tw/twmodel"
"time"
"github.com/sirupsen/logrus"
@ -9,10 +15,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())
}
}

View File

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

View File

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

View File

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

View File

@ -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 {

76
discord/admin_commands.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 {

View File

@ -0,0 +1,7 @@
package discord
import "github.com/bwmarrin/discordgo"
type messageProcessor interface {
process(ctx *commandCtx, m *discordgo.MessageCreate)
}

File diff suppressed because it is too large Load Diff

View File

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

217
discord/session.go Normal file
View File

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

1
go.mod
View File

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

37
go.sum
View File

@ -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=

View File

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

View File

@ -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 {

26
main.go
View File

@ -6,7 +6,6 @@ import (
"os"
"os/signal"
"path"
"strings"
"syscall"
"github.com/sirupsen/logrus"
@ -15,7 +14,7 @@ import (
"github.com/tribalwarshelp/golang-sdk/sdk"
_cron "github.com/tribalwarshelp/dcbot/cron"
"github.com/tribalwarshelp/dcbot/cron"
"github.com/tribalwarshelp/dcbot/discord"
grouprepository "github.com/tribalwarshelp/dcbot/group/repository"
observationrepository "github.com/tribalwarshelp/dcbot/observation/repository"
@ -24,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)
}

View File

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

View File

@ -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"

View File

@ -1,4 +1,4 @@
package models
package model
import "github.com/go-pg/pg/v10/orm"

View File

@ -1,4 +1,4 @@
package models
package model
import (
"github.com/Kichiyaki/gopgutil/v10"

View File

@ -1,4 +1,4 @@
package models
package model
import (
"github.com/Kichiyaki/gopgutil/v10"

View File

@ -1,4 +1,4 @@
package models
package model
import (
"github.com/Kichiyaki/gopgutil/v10"

View File

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

View File

@ -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 {

View File

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

View File

@ -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 {