Merge pull request #11 from tribalwarshelp/v0.8

V0.8
This commit is contained in:
Dawid Wysokiński 2020-07-19 17:22:58 +02:00 committed by GitHub
commit 22f696a9be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1219 additions and 356 deletions

View File

@ -32,12 +32,12 @@ func Attach(c *cron.Cron, cfg Config) {
api: cfg.API,
status: cfg.Status,
}
c.AddFunc("@every 1m", h.checkLastEnnoblements)
c.AddFunc("@every 30m", h.checkBotMembershipOnServers)
c.AddFunc("@every 1m", h.checkEnnoblements)
c.AddFunc("@every 30m", h.checkBotServers)
c.AddFunc("@every 2h10m", h.deleteClosedTribalWarsServers)
c.AddFunc("@every 6h", h.updateBotStatus)
go func() {
h.checkBotMembershipOnServers()
h.checkBotServers()
h.deleteClosedTribalWarsServers()
h.updateBotStatus()
}()

View File

@ -5,6 +5,9 @@ import (
"log"
"time"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/tribalwarshelp/dcbot/message"
"github.com/pkg/errors"
shared_models "github.com/tribalwarshelp/shared/models"
@ -85,22 +88,22 @@ func (h *handler) loadLangVersions(servers []string) ([]*shared_models.LangVersi
return langVersionList.Items, nil
}
func (h *handler) checkLastEnnoblements() {
func (h *handler) checkEnnoblements() {
start := time.Now()
servers, err := h.observationRepo.FetchServers(context.Background())
if err != nil {
log.Print("checkLastEnnoblements error: " + err.Error())
log.Print("checkEnnoblements error: " + err.Error())
return
}
log.Print("checkLastEnnoblements: servers: ", servers)
log.Print("checkEnnoblements: servers: ", servers)
groups, total, err := h.groupRepo.Fetch(context.Background(), nil)
if err != nil {
log.Print("checkLastEnnoblements error: " + err.Error())
log.Print("checkEnnoblements error: " + err.Error())
return
}
log.Print("checkLastEnnoblements: number of loaded groups: ", total)
log.Print("checkEnnoblements: number of loaded groups: ", total)
langVersions, err := h.loadLangVersions(servers)
if err != nil {
@ -113,6 +116,7 @@ func (h *handler) checkLastEnnoblements() {
if group.ConqueredVillagesChannelID == "" && group.LostVillagesChannelID == "" {
continue
}
localizer := message.NewLocalizer(group.Server.Lang)
lostVillagesMsg := &discord.EmbedMessage{}
conqueredVillagesMsg := &discord.EmbedMessage{}
for _, observation := range group.Observations {
@ -130,6 +134,7 @@ func (h *handler) checkLastEnnoblements() {
server: observation.Server,
ennoblement: ennoblement,
t: messageTypeLost,
localizer: localizer,
}
lostVillagesMsg.Append(newMessage(newMsgDataConfig).String())
}
@ -148,6 +153,7 @@ func (h *handler) checkLastEnnoblements() {
server: observation.Server,
ennoblement: ennoblement,
t: messageTypeConquer,
localizer: localizer,
}
conqueredVillagesMsg.Append(newMessage(newMsgDataConfig).String())
}
@ -159,7 +165,11 @@ func (h *handler) checkLastEnnoblements() {
h.discord.SendEmbed(group.ConqueredVillagesChannelID,
discord.
NewEmbed().
SetTitle("Podbite wioski").
SetTitle(localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "cron.conqueredVillages.title",
DefaultMessage: message.FallbackMsg("cron.conqueredVillages.title",
"Conquered villages"),
})).
SetColor(colorConqueredVillage).
SetFields(conqueredVillagesMsg.ToMessageEmbedFields()).
SetTimestamp(formatDateOfConquest(time.Now())).
@ -169,7 +179,11 @@ func (h *handler) checkLastEnnoblements() {
h.discord.SendEmbed(group.LostVillagesChannelID,
discord.
NewEmbed().
SetTitle("Stracone wioski").
SetTitle(localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "cron.lostVillages.title",
DefaultMessage: message.FallbackMsg("cron.lostVillages.title",
"Lost villages"),
})).
SetColor(colorLostVillage).
SetFields(lostVillagesMsg.ToMessageEmbedFields()).
SetTimestamp(formatDateOfConquest(time.Now())).
@ -177,16 +191,16 @@ func (h *handler) checkLastEnnoblements() {
}
}
log.Printf("checkLastEnnoblements: finished in %s", time.Since(start).String())
log.Printf("checkEnnoblements: finished in %s", time.Since(start).String())
}
func (h *handler) checkBotMembershipOnServers() {
func (h *handler) checkBotServers() {
servers, total, err := h.serverRepo.Fetch(context.Background(), nil)
if err != nil {
log.Print("checkBotMembershipOnServers error: " + err.Error())
log.Print("checkBotServers error: " + err.Error())
return
}
log.Print("checkBotMembershipOnServers: total number of loaded discord servers: ", total)
log.Print("checkBotServers: total number of loaded discord servers: ", total)
idsToDelete := []string{}
for _, server := range servers {
@ -200,9 +214,9 @@ func (h *handler) checkBotMembershipOnServers() {
ID: idsToDelete,
})
if err != nil {
log.Print("checkBotMembershipOnServers error: " + err.Error())
log.Print("checkBotServers error: " + err.Error())
} else {
log.Printf("checkBotMembershipOnServers: total number of deleted discord servers: %d", len(deleted))
log.Printf("checkBotServers: total number of deleted discord servers: %d", len(deleted))
}
}
}

View File

@ -3,8 +3,8 @@ package cron
import (
"fmt"
"github.com/bwmarrin/discordgo"
"github.com/tribalwarshelp/dcbot/discord"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/tribalwarshelp/dcbot/message"
"github.com/tribalwarshelp/dcbot/utils"
shared_models "github.com/tribalwarshelp/shared/models"
)
@ -18,7 +18,7 @@ const (
colorConqueredVillage = 0x00ff00
)
type message struct {
type checkEnnoblementsMsg struct {
t messageType
server string
date string
@ -32,6 +32,7 @@ type message struct {
newOwnerName string
newOwnerTribeURL string
newOwnerTribeTag string
localizer *i18n.Localizer
}
type newMessageConfig struct {
@ -39,10 +40,11 @@ type newMessageConfig struct {
host string
server string
ennoblement *shared_models.LiveEnnoblement
localizer *i18n.Localizer
}
func newMessage(cfg newMessageConfig) message {
data := message{
func newMessage(cfg newMessageConfig) checkEnnoblementsMsg {
data := checkEnnoblementsMsg{
t: cfg.t,
date: formatDateOfConquest(cfg.ennoblement.EnnobledAt),
server: cfg.server,
@ -51,6 +53,7 @@ func newMessage(cfg newMessageConfig) message {
oldOwnerTribeTag: "-",
newOwnerName: "-",
newOwnerTribeTag: "-",
localizer: cfg.localizer,
}
if !isVillageNil(cfg.ennoblement.Village) {
data.village = fmt.Sprintf("%s (%d|%d) %s",
@ -80,46 +83,17 @@ func newMessage(cfg newMessageConfig) message {
return data
}
func (msg message) formatMsgAboutVillageLost() string {
return fmt.Sprintf(`Wioska %s gracza %s (%s) została stracona na rzecz %s (%s)`,
formatMsgLink(msg.village, msg.villageURL),
formatMsgLink(msg.oldOwnerName, msg.oldOwnerURL),
formatMsgLink(msg.oldOwnerTribeTag, msg.oldOwnerTribeURL),
formatMsgLink(msg.newOwnerName, msg.newOwnerURL),
formatMsgLink(msg.newOwnerTribeTag, msg.newOwnerTribeURL))
}
func (msg message) formatMsgAboutVillageConquest() string {
return fmt.Sprintf("Gracz %s (%s) podbił wioskę %s od gracza %s (%s)",
formatMsgLink(msg.newOwnerName, msg.newOwnerURL),
formatMsgLink(msg.newOwnerTribeTag, msg.newOwnerTribeURL),
formatMsgLink(msg.village, msg.villageURL),
formatMsgLink(msg.oldOwnerName, msg.oldOwnerURL),
formatMsgLink(msg.oldOwnerTribeTag, msg.oldOwnerTribeURL))
}
func (msg message) String() string {
fieldContent := msg.formatMsgAboutVillageConquest()
if msg.t == messageTypeLost {
fieldContent = msg.formatMsgAboutVillageLost()
}
return fieldContent + "\n"
}
func (msg message) toEmbed() *discordgo.MessageEmbed {
title := "Podbita wioska"
fieldContent := msg.String()
color := colorConqueredVillage
if msg.t == messageTypeLost {
title = "Stracona wioska"
color = colorLostVillage
}
return discord.
NewEmbed().
SetTitle(title).
AddField(msg.server, fieldContent).
SetTimestamp(msg.date).
SetColor(color).
MessageEmbed
func (msg checkEnnoblementsMsg) String() string {
return msg.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "cron.checkEnnoblements.msgLine",
DefaultMessage: message.FallbackMsg("cron.checkEnnoblements.msgLine",
"{{.NewOwner}} ({{.NewOwnerTribe}}) has conquered the village {{.Village}} (Old owner: {{.OldOwner}} ({{.OldOwnerTribe}}))"),
TemplateData: map[string]interface{}{
"NewOwner": formatMsgLink(msg.newOwnerName, msg.newOwnerURL),
"NewOwnerTribe": formatMsgLink(msg.newOwnerTribeTag, msg.newOwnerTribeURL),
"Village": formatMsgLink(msg.village, msg.villageURL),
"OldOwner": formatMsgLink(msg.oldOwnerName, msg.oldOwnerURL),
"OldOwnerTribe": formatMsgLink(msg.oldOwnerTribeTag, msg.oldOwnerTribeURL),
},
}) + "\n"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,10 @@
package discord
import (
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/tribalwarshelp/dcbot/models"
)
type Command string
func (cmd Command) String() string {
@ -9,3 +14,8 @@ func (cmd Command) String() string {
func (cmd Command) WithPrefix(prefix string) string {
return prefix + cmd.String()
}
type commandCtx struct {
server *models.Server
localizer *i18n.Localizer
}

View File

@ -1,10 +1,14 @@
package discord
import (
"context"
"fmt"
"strings"
"github.com/tribalwarshelp/dcbot/message"
"github.com/tribalwarshelp/dcbot/group"
"github.com/tribalwarshelp/dcbot/models"
"github.com/tribalwarshelp/dcbot/observation"
"github.com/tribalwarshelp/dcbot/server"
"github.com/tribalwarshelp/golang-sdk/sdk"
@ -78,44 +82,58 @@ func (s *Session) UpdateStatus(status string) error {
}
func (s *Session) handleNewMessage(_ *discordgo.Session, m *discordgo.MessageCreate) {
if m.Author.ID == s.dg.State.User.ID || m.Author.Bot {
if m.Author.ID == s.dg.State.User.ID || m.Author.Bot || m.GuildID == "" {
return
}
splitted := strings.Split(m.Content, " ")
argsLength := len(splitted) - 1
args := splitted[1 : argsLength+1]
server := &models.Server{
ID: m.GuildID,
Lang: message.GetDefaultLanguage().String(),
}
if err := s.cfg.ServerRepository.Store(context.Background(), server); err != nil {
return
}
ctx := commandCtx{
server: server,
localizer: message.NewLocalizer(server.Lang),
}
switch splitted[0] {
case HelpCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleHelpCommand(m)
s.handleHelpCommand(ctx, m)
case AuthorCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleAuthorCommand(m)
case TribeCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleTribeCommand(m, args...)
s.handleTribeCommand(ctx, m, args...)
case ChangeLanguageCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleChangeLanguageCommand(ctx, m, args...)
case AddGroupCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleAddGroupCommand(m)
s.handleAddGroupCommand(ctx, m)
case DeleteGroupCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleDeleteGroupCommand(m, args...)
s.handleDeleteGroupCommand(ctx, m, args...)
case GroupsCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleGroupsCommand(m)
s.handleGroupsCommand(ctx, m)
case ShowEnnobledBarbariansCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleShowEnnobledBarbariansCommand(m, args...)
s.handleShowEnnobledBarbariansCommand(ctx, m, args...)
case ObserveCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleObserveCommand(m, args...)
case UnObserveCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleUnObserveCommand(m, args...)
s.handleObserveCommand(ctx, m, args...)
case DeleteObservationCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleDeleteObservationCommand(ctx, m, args...)
case ObservationsCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleObservationsCommand(m, args...)
s.handleObservationsCommand(ctx, m, args...)
case ConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleConqueredVillagesCommand(m, args...)
case UnObserveConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleUnObserveConqueredVillagesCommand(m, args...)
s.handleConqueredVillagesCommand(ctx, m, args...)
case DisableConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleDisableConqueredVillagesCommand(ctx, m, args...)
case LostVillagesCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleLostVillagesCommand(m, args...)
case UnObserveLostVillagesCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleUnObserveLostVillagesCommand(m, args...)
s.handleLostVillagesCommand(ctx, m, args...)
case DisableLostVillagesCommand.WithPrefix(s.cfg.CommandPrefix):
s.handleDisableLostVillagesCommand(ctx, m, args...)
}
}
@ -155,7 +173,7 @@ func (s *Session) memberHasPermission(guildID string, userID string, permission
}
func (s *Session) sendUnknownCommandError(mention, channelID string, command ...string) {
s.SendMessage(channelID, mention+` Nieznana komenda: `+strings.Join(command, " "))
s.SendMessage(channelID, mention+` Unknown command: `+strings.Join(command, " "))
}
func (s *Session) IsGuildMember(guildID string) (bool, error) {

15
discord/helpers.go Normal file
View File

@ -0,0 +1,15 @@
package discord
import (
"strings"
"github.com/tribalwarshelp/dcbot/message"
)
func getAvailableLanguages() string {
langTags := []string{}
for _, langTag := range message.LanguageTags() {
langTags = append(langTags, langTag.String())
}
return strings.Join(langTags, " | ")
}

View File

@ -5,6 +5,9 @@ import (
"math"
"strconv"
"github.com/tribalwarshelp/dcbot/message"
"github.com/nicksnyder/go-i18n/v2/i18n"
shared_models "github.com/tribalwarshelp/shared/models"
"github.com/bwmarrin/discordgo"
@ -23,89 +26,226 @@ const (
AuthorCommand Command = "author"
)
func (s *Session) handleHelpCommand(m *discordgo.MessageCreate) {
func (s *Session) handleHelpCommand(ctx commandCtx, m *discordgo.MessageCreate) {
tribeCMDWithPrefix := TribeCommand.WithPrefix(s.cfg.CommandPrefix)
commandsForAll := fmt.Sprintf(`
- **%s %s** [serwer] [strona] [id1] [id2] [id3] [n id] - wyświetla graczy o największym RA z plemion o podanych id
- **%s %s** [serwer] [strona] [id1] [id2] [id3] [n id] - wyświetla graczy o największym RO z plemion o podanych id
- **%s %s** [serwer] [strona] [id1] [id2] [id3] [n id] - wyświetla graczy o największym RW z plemion o podanych id
- **%s %s** [serwer] [strona] [id1] [id2] [id3] [n id] - wyświetla graczy o największej liczbie pokonanych z plemion o podanych id
- **%s %s** [serwer] [strona] [id1] [id2] [id3] [n id] - wyświetla graczy o największej liczbie punktów z plemion o podanych id
- **%s** - kontakt z autorem bota
- %s
- %s
- %s
- %s
- %s
- %s
`,
tribeCMDWithPrefix,
TopAttCommand.String(),
tribeCMDWithPrefix,
TopDefCommand.String(),
tribeCMDWithPrefix,
TopSuppCommand.String(),
tribeCMDWithPrefix,
TopTotalCommand.String(),
tribeCMDWithPrefix,
TopPointsCommand.String(),
AuthorCommand.WithPrefix(s.cfg.CommandPrefix),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.tribe.topatt",
DefaultMessage: message.FallbackMsg("help.tribe.topatt",
"**{{.Command}}** [server] [page] [id1] [id2] [id3] [n id] - generates a player list from selected tribes ordered by ODA."),
TemplateData: map[string]interface{}{
"Command": tribeCMDWithPrefix + " " + TopAttCommand.String(),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.tribe.topdef",
DefaultMessage: message.FallbackMsg("help.tribe.topdef",
"**{{.Command}}** [server] [page] [id1] [id2] [id3] [n id] - generates a player list from selected tribes ordered by ODD."),
TemplateData: map[string]interface{}{
"Command": tribeCMDWithPrefix + " " + TopDefCommand.String(),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.tribe.topsupp",
DefaultMessage: message.FallbackMsg("help.tribe.topsupp",
"**{{.Command}}** [server] [page] [id1] [id2] [id3] [n id] - generates a player list from selected tribes ordered by ODS."),
TemplateData: map[string]interface{}{
"Command": tribeCMDWithPrefix + " " + TopSuppCommand.String(),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.tribe.toptotal",
DefaultMessage: message.FallbackMsg("help.tribe.toptotal",
"**{{.Command}}** [server] [page] [id1] [id2] [id3] [n id] - generates a player list from selected tribes ordered by OD."),
TemplateData: map[string]interface{}{
"Command": tribeCMDWithPrefix + " " + TopTotalCommand.String(),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.tribe.toppoints",
DefaultMessage: message.FallbackMsg("help.tribe.toppoints",
"**{{.Command}}** [server] [page] [id1] [id2] [id3] [n id] - generates a player list from selected tribes ordered by points."),
TemplateData: map[string]interface{}{
"Command": tribeCMDWithPrefix + " " + TopPointsCommand.String(),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.author",
DefaultMessage: message.FallbackMsg("help.author",
"**{{.Command}}** - shows how to contact the author."),
TemplateData: map[string]interface{}{
"Command": AuthorCommand.WithPrefix(s.cfg.CommandPrefix),
},
}),
)
commandsForGuildAdmins := fmt.Sprintf(`
- **%s** - tworzy nową grupę
- **%s** - lista grup
- **%s** [id grupy z %s] - usuwa grupę
- **%s** [id grupy z %s] - włącza/wyłącza wyświetlanie powiadomień o podbitych wioskach barbarzyńskich
- **%s** [id grupy z %s] [świat] [id plemienia] - dodaje plemię z danego świata do obserwowanych
- **%s** [id grupy z %s] - wyświetla wszystkie obserwowane plemiona
- **%s** [id grupy z %s] [id z %s] - usuwa plemię z obserwowanych
- **%s** [id grupy z %s] - ustawia kanał na którym będą wyświetlać się informacje o podbitych wioskach
- **%s** [id grupy z %s] - informacje o podbitych wioskach nie będą się już pojawiały
- **%s** [id grupy z %s] - ustawia kanał na którym będą wyświetlać się informacje o straconych wioskach
- %s
- %s
- %s
- %s
- %s
- %s
- %s
- %s
- %s
`,
AddGroupCommand.WithPrefix(s.cfg.CommandPrefix),
GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
DeleteGroupCommand.WithPrefix(s.cfg.CommandPrefix),
GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
ShowEnnobledBarbariansCommand.WithPrefix(s.cfg.CommandPrefix),
GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
ObserveCommand.WithPrefix(s.cfg.CommandPrefix),
GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
ObservationsCommand.WithPrefix(s.cfg.CommandPrefix),
GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
UnObserveCommand.WithPrefix(s.cfg.CommandPrefix),
GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
ObservationsCommand.WithPrefix(s.cfg.CommandPrefix),
ConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix),
GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
UnObserveConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix),
GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
LostVillagesCommand.WithPrefix(s.cfg.CommandPrefix),
GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.addgroup",
DefaultMessage: message.FallbackMsg("help.addgroup",
"**{{.Command}}** - adds a new observation group."),
TemplateData: map[string]interface{}{
"Command": AddGroupCommand.WithPrefix(s.cfg.CommandPrefix),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.groups",
DefaultMessage: message.FallbackMsg("help.groups",
"**{{.Command}}** - shows you a list of groups created by this guild."),
TemplateData: map[string]interface{}{
"Command": GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.deletegroup",
DefaultMessage: message.FallbackMsg("help.deletegroup",
"**{{.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),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.showennobledbarbs",
DefaultMessage: message.FallbackMsg("help.showennobledbarbs",
"**{{.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),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.observe",
DefaultMessage: message.FallbackMsg("help.observe",
"**{{.Command}}** [group id from {{.GroupsCommand}}] [server] [tribe id] - command adds a tribe to the observation group."),
TemplateData: map[string]interface{}{
"Command": ObserveCommand.WithPrefix(s.cfg.CommandPrefix),
"GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.observations",
DefaultMessage: message.FallbackMsg("help.observations",
"**{{.Command}}** [group id from {{.GroupsCommand}}] shows a list of observed tribes by this group."),
TemplateData: map[string]interface{}{
"Command": ObservationsCommand.WithPrefix(s.cfg.CommandPrefix),
"GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.deleteobservation",
DefaultMessage: message.FallbackMsg("help.deleteobservation",
"**{{.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),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.conqueredvillages",
DefaultMessage: message.FallbackMsg("help.conqueredvillages",
"**{{.Command}}** [group id from {{.GroupsCommand}}] - changes the channel on which notifications about conquered village will show. 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),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.disableconqueredvillages",
DefaultMessage: message.FallbackMsg("help.disableconqueredvillages",
"**{{.Command}}** [group id from {{.GroupsCommand}}] - disable notifications about conquered villages."),
TemplateData: map[string]interface{}{
"Command": DisableConqueredVillagesCommand.WithPrefix(s.cfg.CommandPrefix),
"GroupsCommand": GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
},
}),
)
commandsForGuildAdmins2 := fmt.Sprintf(`
- **%s** [id grupy z %s] - informacje o straconych wioskach nie będą się już pojawiały
- %s
- %s
- %s
`,
UnObserveLostVillagesCommand.WithPrefix(s.cfg.CommandPrefix),
GroupsCommand.WithPrefix(s.cfg.CommandPrefix),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.lostvillages",
DefaultMessage: message.FallbackMsg("help.lostvillages",
"**{{.Command}}** [group id from {{.GroupsCommand}}] - changes the channel on which notifications about lost village will show. 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),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.disablelostvillages",
DefaultMessage: message.FallbackMsg("help.disablelostvillages",
"**{{.Command}}** [group id from {{.GroupsCommand}}] - changes the channel on which notifications about lost village will show. 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),
},
}),
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.changelanguage",
DefaultMessage: message.FallbackMsg("help.changelanguage",
"**{{.Command}}** [{{.Languages}}] - change language."),
TemplateData: map[string]interface{}{
"Command": ChangeLanguageCommand.WithPrefix(s.cfg.CommandPrefix),
"Languages": getAvailableLanguages(),
},
}),
)
forAdmins := ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.forAdmins",
DefaultMessage: message.FallbackMsg("help.forAdmins", "For admins"),
})
s.SendEmbed(m.ChannelID, NewEmbed().
SetTitle("Pomoc").
SetDescription("Komendy oferowane przez bota").
AddField("Dla wszystkich", commandsForAll).
AddField("Dla adminów", commandsForGuildAdmins).
AddField("Dla adminów 2", commandsForGuildAdmins2).
SetTitle(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.title",
DefaultMessage: message.FallbackMsg("help.title", "Help"),
})).
SetURL("https://dcbot.tribalwarshelp.com/").
SetDescription(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.description",
DefaultMessage: message.FallbackMsg("help.description", "Commands offered by bot"),
})).
AddField(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "help.forAllUsers",
DefaultMessage: message.FallbackMsg("help.forAllUsers", "For all guild members."),
}), commandsForAll).
AddField(forAdmins, commandsForGuildAdmins).
AddField(forAdmins+" 2", commandsForGuildAdmins2).
MessageEmbed)
}
func (s *Session) handleAuthorCommand(m *discordgo.MessageCreate) {
s.SendMessage(m.ChannelID, fmt.Sprintf("%s Discord: Kichiyaki#2064 | https://dawid-wysokinski.pl/#contact.", m.Author.Mention()))
s.SendMessage(m.ChannelID,
fmt.Sprintf("%s Discord: Kichiyaki#2064 | https://dawid-wysokinski.pl/#contact.",
m.Author.Mention()))
}
func (s *Session) handleTribeCommand(m *discordgo.MessageCreate, args ...string) {
func (s *Session) handleTribeCommand(ctx commandCtx, m *discordgo.MessageCreate, args ...string) {
argsLength := len(args)
if argsLength < 4 {
s.SendMessage(m.ChannelID,
fmt.Sprintf("%s Niepoprawna komenda (sprawdź %s)",
m.Author.Mention(),
HelpCommand.WithPrefix(s.cfg.CommandPrefix)))
if argsLength < 3 {
return
}
@ -113,7 +253,13 @@ func (s *Session) handleTribeCommand(m *discordgo.MessageCreate, args ...string)
world := args[1]
page, err := strconv.Atoi(args[2])
if err != nil || page <= 0 {
s.SendMessage(m.ChannelID, fmt.Sprintf("%s 3 argument musi być liczbą większą od 0.", m.Author.Mention()))
s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "tribe.invalidPage",
DefaultMessage: message.FallbackMsg("tribe.invalidPage", "{{.Mention}} The page must be a number greater than 0."),
TemplateData: map[string]interface{}{
"Mention": m.Author.Mention(),
},
}))
return
}
ids := []int{}
@ -125,7 +271,13 @@ func (s *Session) handleTribeCommand(m *discordgo.MessageCreate, args ...string)
ids = append(ids, id)
}
if len(ids) == 0 {
s.SendMessage(m.ChannelID, fmt.Sprintf("%s Nie wprowadziłeś ID plemion.", m.Author.Mention()))
s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "tribe.noTribeID",
DefaultMessage: message.FallbackMsg("tribe.noTribeID", "{{.Mention}} You haven't entered the tribe ID."),
TemplateData: map[string]interface{}{
"Mention": m.Author.Mention(),
},
}))
return
}
@ -143,28 +295,38 @@ func (s *Session) handleTribeCommand(m *discordgo.MessageCreate, args ...string)
case TopAttCommand:
filter.RankAttGTE = 1
filter.Sort = "rankAtt ASC"
title = "Top pokonani w ataku"
title = ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "tribe.title.sortedByODA",
DefaultMessage: message.FallbackMsg("tribe.title.sortedByODA", "Ordered by ODA"),
})
case TopDefCommand:
filter.RankDefGTE = 1
filter.Sort = "rankDef ASC"
title = "Top pokonani w obronie"
title = ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "tribe.title.sortedByODD",
DefaultMessage: message.FallbackMsg("tribe.title.sortedByODD", "Ordered by ODD"),
})
case TopSuppCommand:
filter.RankSupGTE = 1
filter.Sort = "rankSup ASC"
title = "Top pokonani jako wspierający"
title = ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "tribe.title.sortedByODS",
DefaultMessage: message.FallbackMsg("tribe.title.sortedByODS", "Ordered by ODS"),
})
case TopTotalCommand:
filter.RankTotalGTE = 1
filter.Sort = "rankTotal ASC"
title = "Top pokonani ogólnie"
title = ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "tribe.title.sortedByOD",
DefaultMessage: message.FallbackMsg("tribe.title.sortedByOD", "Ordered by OD"),
})
case TopPointsCommand:
filter.Sort = "rank ASC"
title = "Najwięcej punktów"
title = ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "tribe.title.sortedByPoints",
DefaultMessage: message.FallbackMsg("tribe.title.sortedByPoints", "Ordered by points"),
})
default:
s.SendMessage(m.ChannelID,
fmt.Sprintf("%s Nieznana komenda %s (sprawdź %s)",
m.Author.Mention(),
command.String(),
HelpCommand.WithPrefix(s.cfg.CommandPrefix)))
return
}
@ -173,23 +335,53 @@ func (s *Session) handleTribeCommand(m *discordgo.MessageCreate, args ...string)
})
if err != nil {
s.SendMessage(m.ChannelID,
fmt.Sprintf("%s Wystąpił błąd podczas pobierania danych z API, prosimy spróbować później.", m.Author.Mention()))
ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "api.defaultError",
DefaultMessage: message.FallbackMsg("api.defaultError",
"{{.Mention}} There was an error fetching data from the API, please try again later."),
TemplateData: map[string]interface{}{
"Mention": m.Author.Mention(),
},
}))
return
}
if playersList == nil || playersList.Total == 0 {
s.SendMessage(m.ChannelID, fmt.Sprintf("%s Nie znaleziono graczy należących do plemion o podanych ID.", m.Author.Mention()))
s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "tribe.tribesNotFound",
DefaultMessage: message.FallbackMsg("tribe.tribesNotFound",
"{{.Mention}} There was an error fetching data from the API, please try again later."),
TemplateData: map[string]interface{}{
"Mention": m.Author.Mention(),
},
}))
return
}
totalPages := int(math.Ceil(float64(playersList.Total) / float64(limit)))
if page > totalPages {
s.SendMessage(m.ChannelID, fmt.Sprintf("%s Przekroczyłeś limit stron (%d/%d).", m.Author.Mention(), page, totalPages))
s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "tribe.exceededMaximumNumberOfPages",
DefaultMessage: message.FallbackMsg("tribe.exceededMaximumNumberOfPages",
"{{.Mention}} You have exceeded the maximum number of pages ({{.Page}}/{{.MaxPage}})."),
TemplateData: map[string]interface{}{
"Mention": m.Author.Mention(),
"Page": page,
"MaxPage": totalPages,
},
}))
return
}
langTag := utils.LanguageTagFromWorldName(world)
langVersion, err := s.cfg.API.LangVersions.Read(langTag)
if err != nil || langVersion == nil {
s.SendMessage(m.ChannelID, fmt.Sprintf("%s Nie znaleziono wersji językowej: %s.", m.Author.Mention(), langTag))
s.SendMessage(m.ChannelID, ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "internalServerError",
DefaultMessage: message.FallbackMsg("internalServerError",
"{{.Mention}} Internal server error occurred, please try again later."),
TemplateData: map[string]interface{}{
"Mention": m.Author.Mention(),
},
}))
return
}
@ -226,20 +418,32 @@ func (s *Session) handleTribeCommand(m *discordgo.MessageCreate, args ...string)
tribeURL = utils.FormatTribeURL(world, langVersion.Host, player.Tribe.ID)
}
msg.Append(fmt.Sprintf("**%d**. [``%s``](%s) (Plemię: [``%s``](%s) | Ranking ogólny: **%d** | Wynik: **%d**)\n",
offset+i+1,
player.Name,
utils.FormatPlayerURL(world, langVersion.Host, player.ID),
tribeTag,
tribeURL,
rank,
score))
msg.Append(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "tribe.messageLine",
DefaultMessage: message.FallbackMsg("tribe.messageLine",
"**{{.Index}}**. [``{{.PlayerName}}``]({{.PlayerURL}}) (Tribe: [``{{.TribeTag}}``]({{.TribeURL}}) | Rank: **{{.Rank}}** | Score: **{{.Score}}**)\n"),
TemplateData: map[string]interface{}{
"Index": offset + i + 1,
"PlayerName": player.Name,
"PlayerURL": utils.FormatPlayerURL(world, langVersion.Host, player.ID),
"TribeTag": tribeTag,
"TribeURL": tribeURL,
"Rank": rank,
"Score": score,
},
}))
}
s.SendEmbed(m.ChannelID, NewEmbed().
SetTitle(title).
SetDescription("A oto lista!").
SetFields(msg.ToMessageEmbedFields()).
SetFooter(fmt.Sprintf("Strona %d z %d", page, totalPages)).
SetFooter(ctx.localizer.MustLocalize(&i18n.LocalizeConfig{
MessageID: "pagination.labelDisplayedPage",
DefaultMessage: message.FallbackMsg("pagination.labelDisplayedPage", "{{.Page}} from {{.MaxPage}}"),
TemplateData: map[string]interface{}{
"Page": page,
"MaxPage": totalPages,
},
})).
MessageEmbed)
}

2
go.mod
View File

@ -6,10 +6,12 @@ require (
github.com/bwmarrin/discordgo v0.20.3
github.com/go-pg/pg/v10 v10.0.0-beta.2
github.com/joho/godotenv v1.3.0
github.com/nicksnyder/go-i18n/v2 v2.0.3
github.com/pkg/errors v0.9.1
github.com/robfig/cron/v3 v3.0.1
github.com/segmentio/encoding v0.1.14 // indirect
github.com/tribalwarshelp/golang-sdk v0.0.0-20200709112050-ba3a7ba145b1
github.com/tribalwarshelp/shared v0.0.0-20200707075151-722e4a520a3c
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 // indirect
golang.org/x/text v0.3.2
)

21
go.sum
View File

@ -1,4 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7 h1:qELHH0AWCvf98Yf+CNIJx9vOZOfHFDDzgDRYsnNk/vs=
github.com/DataDog/sketches-go v0.0.0-20190923095040-43f19ad77ff7/go.mod h1:Q5DbzQ+3AkgGwymQO7aZFNP7ns2lZKGtvRBzRXfdi60=
@ -74,6 +76,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/nicksnyder/go-i18n/v2 v2.0.3 h1:ks/JkQiOEhhuF6jpNvx+Wih1NIiXzUnZeZVnJuI8R8M=
github.com/nicksnyder/go-i18n/v2 v2.0.3/go.mod h1:oDab7q8XCYMRlcrBnaY/7B1eOectbvj6B1UPBT+p5jo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -101,21 +105,8 @@ github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgh
github.com/stretchr/testify v1.6.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-20200623145613-0fb6430054d2 h1:8sp6dweIF6v6m9ORL8JXFMraxV29qcLeZyXZx4b9uwY=
github.com/tribalwarshelp/golang-sdk v0.0.0-20200623145613-0fb6430054d2/go.mod h1:ls5/l0IY/Aa12URZt8IRRYSR8xi3puOoWpgUTf6QNVY=
github.com/tribalwarshelp/golang-sdk v0.0.0-20200625131004-06b44e214642 h1:PD5bOsgnMAQwq4m2R19/dM5an56kEo0OSHm2R2gYgoY=
github.com/tribalwarshelp/golang-sdk v0.0.0-20200625131004-06b44e214642/go.mod h1:dM2bGl7OxfsejbDu+TSFie8XS1XD1WBc1/dEqbIRLUk=
github.com/tribalwarshelp/golang-sdk v0.0.0-20200629130313-0880d8996ba6 h1:+C127PqJZY5xS1gyWQAaSblxnktBxnFISzHsnEjPj0s=
github.com/tribalwarshelp/golang-sdk v0.0.0-20200629130313-0880d8996ba6/go.mod h1:J1nwE1gYoP8LJkb1sbL3OGqTp7u8RJZGpsKHYjS3+0Y=
github.com/tribalwarshelp/golang-sdk v0.0.0-20200709112050-ba3a7ba145b1 h1:p+/OHcQQYXCmxjG+YTkXfeL4Oxku7t6sW0KK2Ne85V4=
github.com/tribalwarshelp/golang-sdk v0.0.0-20200709112050-ba3a7ba145b1/go.mod h1:jQWbuz96EMQTuxhanSY1imtK4zEjPrB1cM5wCKkk7l4=
github.com/tribalwarshelp/shared v0.0.0-20200623144748-aa834a01dce6 h1:WZ1oxHysFtiPjHa2ADUqiGrzwcN3j0YpiVg/V2e/74o=
github.com/tribalwarshelp/shared v0.0.0-20200623144748-aa834a01dce6/go.mod h1:tf+2yTHasV6jAF3V2deZ9slNoCyBzC0fMdTjI7clf6Y=
github.com/tribalwarshelp/shared v0.0.0-20200625120510-6d18ee334662/go.mod h1:tf+2yTHasV6jAF3V2deZ9slNoCyBzC0fMdTjI7clf6Y=
github.com/tribalwarshelp/shared v0.0.0-20200625131045-74c5a9b3b4f0 h1:k1j0Nh2OIr1fTrCrbznjPAH+eRVpB3HYXM1sHErrayg=
github.com/tribalwarshelp/shared v0.0.0-20200625131045-74c5a9b3b4f0/go.mod h1:tf+2yTHasV6jAF3V2deZ9slNoCyBzC0fMdTjI7clf6Y=
github.com/tribalwarshelp/shared v0.0.0-20200629123803-0cd6cb6f1e87 h1:TMuZUk0wW+8dXSGVJVLRFFhLONKKYJRnLBFvbb9XTrE=
github.com/tribalwarshelp/shared v0.0.0-20200629123803-0cd6cb6f1e87/go.mod h1:tf+2yTHasV6jAF3V2deZ9slNoCyBzC0fMdTjI7clf6Y=
github.com/tribalwarshelp/shared v0.0.0-20200707075151-722e4a520a3c h1:wenSReGHPux51q5s+9Ji0TOHCt8Zx6whxsjx8Iv7aZg=
github.com/tribalwarshelp/shared v0.0.0-20200707075151-722e4a520a3c/go.mod h1:tf+2yTHasV6jAF3V2deZ9slNoCyBzC0fMdTjI7clf6Y=
github.com/vmihailenco/bufpool v0.1.5/go.mod h1:fL9i/PRTuS7AELqAHwSU1Zf1c70xhkhGe/cD5ud9pJk=
@ -135,6 +126,7 @@ go.opentelemetry.io/otel v0.6.0/go.mod h1:jzBIgIzK43Iu1BpDAXwqOd6UPsSAk+ewVZ5ofS
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=
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191029031824-8986dd9e96cf/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191128160524-b544559bb6d1/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -152,6 +144,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -168,6 +161,7 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -183,6 +177,7 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -70,7 +70,7 @@ func (repo *pgRepo) GetByID(ctx context.Context, id int) (*models.Group, error)
func (repo *pgRepo) Fetch(ctx context.Context, f *models.GroupFilter) ([]*models.Group, int, error) {
var err error
data := []*models.Group{}
query := repo.Model(&data).Relation("Observations").Context(ctx)
query := repo.Model(&data).Relation("Server").Relation("Observations").Context(ctx)
if f != nil {
query = query.

11
main.go
View File

@ -4,8 +4,11 @@ import (
"log"
"os"
"os/signal"
"path"
"syscall"
"github.com/tribalwarshelp/dcbot/message"
"github.com/tribalwarshelp/golang-sdk/sdk"
_cron "github.com/tribalwarshelp/dcbot/cron"
@ -35,6 +38,14 @@ func init() {
}
func main() {
dir, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
if err := message.LoadMessageFiles(path.Join(dir, "message", "translations")); err != nil {
log.Fatal(err)
}
db := pg.Connect(&pg.Options{
User: os.Getenv("DB_USER"),
Password: os.Getenv("DB_PASSWORD"),

39
message/bundle.go Normal file
View File

@ -0,0 +1,39 @@
package message
import (
"os"
"path/filepath"
"github.com/nicksnyder/go-i18n/v2/i18n"
"golang.org/x/text/language"
)
var lang = language.English
var bundle = i18n.NewBundle(lang)
func SetDefaultLanguage(tag language.Tag) {
lang = tag
bundle = i18n.NewBundle(tag)
}
func GetDefaultLanguage() language.Tag {
return lang
}
func NewLocalizer(l ...string) *i18n.Localizer {
return i18n.NewLocalizer(bundle, append(l, lang.String())...)
}
func LanguageTags() []language.Tag {
return bundle.LanguageTags()
}
func LoadMessageFiles(root string) error {
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if path != root {
bundle.MustLoadMessageFile(path)
}
return nil
})
}

12
message/helpers.go Normal file
View File

@ -0,0 +1,12 @@
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

@ -0,0 +1,97 @@
{
"help.title": "Help",
"help.description": "Commands offered by the bot.",
"help.footer": "",
"help.forAllUsers": "For all users",
"help.forAdmins": "For admins",
"help.tribe.topatt": "**{{.Command}}** [server] [page] [id1] [id2] [id3] [n id] - generates a player list from selected tribes ordered by ODA.",
"help.tribe.topdef": "**{{.Command}}** [server] [page] [id1] [id2] [id3] [n id] - generates a player list from selected tribes ordered by ODD.",
"help.tribe.topsupp": "**{{.Command}}** [server] [page] [id1] [id2] [id3] [n id] - generates a player list from selected tribes ordered by ODS.",
"help.tribe.toptotal": "**{{.Command}}** [server] [page] [id1] [id2] [id3] [n id] - generates a player list from selected tribes ordered by OD.",
"help.tribe.toppoints": "**{{.Command}}** [server] [page] [id1] [id2] [id3] [n id] - generates a player list from selected tribes ordered by points.",
"help.author": "**{{.Command}}** - shows how to contact the author.",
"help.addgroup": "**{{.Command}}** - adds a new observation group.",
"help.groups": "**{{.Command}}** - shows you a list of groups created by this guild.",
"help.deletegroup": "**{{.Command}}** [group id from {{.GroupsCommand}}] - deletes an observation group.",
"help.showennobledbarbs": "**{{.Command}}** [group id from {{.GroupsCommand}}] - enables/disables notifications about ennobling barbarian villages.",
"help.observe": "**{{.Command}}** [group id from {{.GroupsCommand}}] [server] [tribe id] - command adds a tribe to the observation group.",
"help.observations": "**{{.Command}}** [group id from {{.GroupsCommand}}] - shows a list of observed tribes by this group.",
"help.deleteobservation": "**{{.Command}}** [group id from {{.GroupsCommand}}] [id from {{.ObservationsCommand}}] - removes a tribe from the observation group.",
"help.conqueredvillages": "**{{.Command}}** [group id from {{.GroupsCommand}}] - changes the channel on which notifications about conquered village will show. **IMPORTANT!** Call this command on the channel you want to display these notifications.",
"help.disableconqueredvillages": "**{{.Command}}** [group id from {{.GroupsCommand}}] - disable notifications about conquered villages.",
"help.lostvillages": "**{{.Command}}** [group id from {{.GroupsCommand}}] - changes the channel on which notifications about lost village will show. **IMPORTANT!** Call this command on the channel you want to display these notifications.",
"help.disablelostvillages": "**{{.Command}}** [group id from {{.GroupsCommand}}] - disable notifications about lost villages.",
"help.changelanguage": "**{{.Command}}** [{{.Languages}}] - change language.",
"tribe.invalidPage": "{{.Mention}} The page must be a number greater than 0.",
"tribe.noTribeID": "{{.Mention}} You haven't entered the tribe ID.",
"tribe.title.sortedByOD": "Ordered by OD",
"tribe.title.sortedByODA": "Ordered by ODA",
"tribe.title.sortedByODD": "Ordered by ODD",
"tribe.title.sortedByODS": "Ordered by ODS",
"tribe.title.sortedByPoints": "Ordered by points",
"tribe.tribesNotFound": "{{.Mention}} Tribes not found.",
"tribe.exceededMaximumNumberOfPages": "{{.Mention}} You have exceeded the maximum number of pages ({{.Page}}/{{.MaxPage}}).",
"tribe.messageLine": "**{{.Index}}**. [``{{.PlayerName}}``]({{.PlayerURL}}) (Tribe: [``{{.TribeTag}}``]({{.TribeURL}}) | Rank: **{{.Rank}}** | Score: **{{.Score}}**)\n",
"addGroup.groupLimitHasBeenReached": "{{.Mention}} The group limit has been reached ({{.Total}}/{{.Limit}}).",
"addGroup.success": "{{.Mention}} A new group has been created (ID: {{.ID}}).",
"deleteGroup.invalidID": "{{.Mention}} The group ID must be a number greater than 0.",
"deleteGroup.success": "{{.Mention}} The group has been deleted.",
"groups.noGroupsAdded": "No groups added.",
"groups.title": "Group list",
"conqueredVillages.invalidID": "{{.Mention}} The group ID must be a number greater than 0.",
"conqueredVillages.groupNotFound": "{{.Mention}} Group not found.",
"conqueredVillages.success": "{{.Mention}} Channel changed successfully.",
"disableConqueredVillages.invalidID": "{{.Mention}} The group ID must be a number greater than 0.",
"disableConqueredVillages.groupNotFound": "{{.Mention}} Group not found.",
"disableConqueredVillages.success": "{{.Mention}} Notifications about conquered villages will no longer show up.",
"lostVillages.invalidID": "{{.Mention}} The group ID must be a number greater than 0.",
"lostVillages.groupNotFound": "{{.Mention}} Group not found.",
"lostVillages.success": "{{.Mention}} Channel changed successfully.",
"disableLostVillages.invalidID": "{{.Mention}} The group ID must be a number greater than 0.",
"disableLostVillages.groupNotFound": "{{.Mention}} Group not found.",
"disableLostVillages.success": "{{.Mention}} Notifications about lost villages will no longer show up.",
"observe.invalidGroupID": "{{.Mention}} The group ID must be a number greater than 0.",
"observe.invalidTribeID": "{{.Mention}} The tribe ID must be a number greater than 0.",
"observe.serverIsClosed": "{{.Mention}} Server is closed.",
"observe.serverNotFound": "{{.Mention}} Server not found.",
"observe.groupNotFound": "{{.Mention}} Group not found.",
"observe.tribeNotFound": "{{.Mention}} Tribe not found.",
"observe.observationLimitHasBeenReached": "{{.Mention}} The observation limit for this group has been reached ({{.Total}}/{{.Limit}}).",
"observe.success": "{{.Mention}} Added.",
"deleteObservation.invalidGroupID": "{{.Mention}} The group ID must be a number greater than 0.",
"deleteObservation.invalidObservationID": "{{.Mention}} The observation ID must be a number greater than 0.",
"deleteObservation.groupNotFound": "{{.Mention}} Group not found.",
"deleteObservation.success": "{{.Mention}} Deleted.",
"observations.invalidGroupID": "{{.Mention}} The group ID must be a number greater than 0.",
"observations.groupNotFound": "{{.Mention}} Group not found.",
"observations.title": "Observed tribes\nIndex | ID - Server - Tribe",
"showEnnobledBarbs.invalidGroupID": "{{.Mention}} The group ID must be a number greater than 0.",
"showEnnobledBarbs.groupNotFound": "{{.Mention}} Group not found.",
"showEnnobledBarbs.success_1": "{{.Mention}} Notifications about conquered barbarian villages will no longer show up.",
"showEnnobledBarbs.success_2": "{{.Mention}} Enabled notifications about conquered barbarian villages.",
"changeLanguage.languageNotSupported": "{{.Mention}} Language not supported.",
"changeLanguage.success": "{{.Mention}} The language has been changed.",
"cron.lostVillages.title": "Lost villages",
"cron.conqueredVillages.title": "Conquered villages",
"cron.checkEnnoblements.msgLine": "{{.NewOwner}} [{{.NewOwnerTribe}}] has conquered the village {{.Village}} (Old owner: {{.OldOwner}} [{{.OldOwnerTribe}}])",
"api.defaultError": "{{.Mention}} There was an error fetching data from the API, please try again later.",
"pagination.labelDisplayedPage": "Page: {{.Page}} from {{.MaxPage}}",
"internalServerError": "{{.Mention}} Internal server error occurred, please try again later."
}

View File

@ -0,0 +1,97 @@
{
"help.title": "Pomoc",
"help.description": "Komendy oferowane przez bota.",
"help.footer": "",
"help.forAllUsers": "Dla wszystkich",
"help.forAdmins": "Dla adminów",
"help.tribe.topatt": "**{{.Command}}** [serwer] [strona] [id1] [id2] [id3] [n id] - generuje listę graczy z wybranych plemion i sortuje po pokonanych w ataku.",
"help.tribe.topdef": "**{{.Command}}** [serwer] [strona] [id1] [id2] [id3] [n id] - generuje listę graczy z wybranych plemion i sortuje po pokonanych w obronie.",
"help.tribe.topsupp": "**{{.Command}}** [serwer] [strona] [id1] [id2] [id3] [n id] - generuje listę graczy z wybranych plemion i sortuje po pokonanych jako wspierający.",
"help.tribe.toptotal": "**{{.Command}}** [serwer] [strona] [id1] [id2] [id3] [n id] - generuje listę graczy z wybranych plemion i sortuje po pokonanych ogólnie.",
"help.tribe.toppoints": "**{{.Command}}** [serwer] [strona] [id1] [id2] [id3] [n id] - Generuje listę graczy z wybranych plemion i sortuje po punktach.",
"help.author": "**{{.Command}}** - pokazuje możliwe formy kontaktu z autorem.",
"help.addgroup": "**{{.Command}}** - dodaje nową grupę obserwacyjną.",
"help.groups": "**{{.Command}}** - pokazuje listę grup dodanych przez administrację tego serwera Discord..",
"help.deletegroup": "**{{.Command}}** [id grupy z {{.GroupsCommand}}] - usuwa grupę obserwacyjną.",
"help.showennobledbarbs": "**{{.Command}}** [id grupy z {{.GroupsCommand}}] - włącza/wyłącza informacje o podbitych wioskach barbarzyńskich.",
"help.observe": "**{{.Command}}** [id grupy z {{.GroupsCommand}}] [serwer] [id plemienia] - dodaje plemię do grupy.",
"help.observations": "**{{.Command}}** [id grupy z {{.GroupsCommand}}] - pokazuje plemiona należące do wybranej grupy.",
"help.deleteobservation": "**{{.Command}}** [id grupy z {{.GroupsCommand}}] [id from {{.ObservationsCommand}}] - usuwa plemię z grupy.",
"help.conqueredvillages": "**{{.Command}}** [id grupy z {{.GroupsCommand}}] - zmienia kanał na którym będą się pojawiać informację o podbitych wioskach w danej grupie. **WAŻNE!** Wywołaj tę komendę na kanale na którym chcesz dostawać te powiadomienia.",
"help.disableconqueredvillages": "**{{.Command}}** [id grupy z {{.GroupsCommand}}] - wyłącza powiadomienia o podbitych wioskach w danej grupie.",
"help.lostvillages": "**{{.Command}}** [id grupy z {{.GroupsCommand}}] - zmienia kanał na którym będą się pojawiać informację o straconych wioskach w danej grupie. **WAŻNE!** Wywołaj tę komendę na kanale na którym chcesz dostawać te powiadomienia.",
"help.disablelostvillages": "**{{.Command}}** [id grupy z {{.GroupsCommand}}] - wyłącza powiadomienia o straconych wioskach w danej grupie.",
"help.changelanguage": "**{{.Command}}** [{{.Languages}}] - zmień język.",
"tribe.invalidPage": "{{.Mention}} Strona musi być liczbą większą od 0.",
"tribe.noTribeID": "{{.Mention}} Nie wprowadziłeś ID plemienia.",
"tribe.title.sortedByOD": "Gracze posortowani według pokonanych przeciwników",
"tribe.title.sortedByODA": "Gracze posortowani według pokonanych przeciwników jako agresor",
"tribe.title.sortedByODD": "Gracze posortowani według pokonanych przeciwników jako obrońca",
"tribe.title.sortedByODS": "Gracze posortowani według pokonanych przeciwników jako wspierający",
"tribe.title.sortedByPoints": "Gracze posortowani według punktów",
"tribe.tribesNotFound": "{{.Mention}} Plemię nie zostało znalezione.",
"tribe.exceededMaximumNumberOfPages": "{{.Mention}} Przekroczyłeś limit stron ({{.Page}}/{{.MaxPage}}).",
"tribe.messageLine": "**{{.Index}}**. [``{{.PlayerName}}``]({{.PlayerURL}}) (Plemię: [``{{.TribeTag}}``]({{.TribeURL}}) | Ranking: **{{.Rank}}** | Wynik: **{{.Score}}**)\n",
"addGroup.groupLimitHasBeenReached": "{{.Mention}} Osiągnięto limit grup ({{.Total}}/{{.Limit}}).",
"addGroup.success": "{{.Mention}} Nowa grupa została utworzona (ID: {{.ID}}).",
"deleteGroup.invalidID": "{{.Mention}} ID grupy musi być liczbą większą od 0.",
"deleteGroup.success": "{{.Mention}} Grupa została usunięta.",
"groups.noGroupsAdded": "Brak dodanych grup.",
"groups.title": "Lista grup",
"conqueredVillages.invalidID": "{{.Mention}} ID grupy musi być liczbą większą od 0.",
"conqueredVillages.groupNotFound": "{{.Mention}} Grupa nie została znaleziona.",
"conqueredVillages.success": "{{.Mention}} Kanał został pomyślnie zmieniony.",
"disableConqueredVillages.invalidID": "{{.Mention}} ID grupy musi być liczbą większą od 0.",
"disableConqueredVillages.groupNotFound": "{{.Mention}} Grupa nie została znaleziona.",
"disableConqueredVillages.success": "{{.Mention}} Informacje o podbitych wioskach nie będą się już pokazywały.",
"lostVillages.invalidID": "{{.Mention}} ID grupy musi być liczbą większą od 0.",
"lostVillages.groupNotFound": "{{.Mention}} Grupa nie została znaleziona.",
"lostVillages.success": "{{.Mention}} Kanał został pomyślnie zmieniony.",
"disableLostVillages.invalidID": "{{.Mention}} ID grupy musi być liczbą większą od 0.",
"disableLostVillages.groupNotFound": "{{.Mention}} Grupa nie została znaleziona.",
"disableLostVillages.success": "{{.Mention}} Informacje o straconych wioskach nie będą się już pokazywały.",
"observe.invalidGroupID": "{{.Mention}} ID grupy musi być liczbą większą od 0.",
"observe.invalidTribeID": "{{.Mention}} ID plemienia musi być liczbą większą od 0.",
"observe.serwerIsClosed": "{{.Mention}} Sewer jest zamknięty.",
"observe.serwerNotFound": "{{.Mention}} Serwer nie został znaleziony.",
"observe.groupNotFound": "{{.Mention}} Grupa nie została znaleziona.",
"observe.tribeNotFound": "{{.Mention}} Plemię nie zostało znalezione.",
"observe.observationLimitHasBeenReached": "{{.Mention}} Limit obserwacji dla grupy został osiągnięty ({{.Total}}/{{.Limit}}).",
"observe.success": "{{.Mention}} Dodano.",
"deleteObservation.invalidGroupID": "{{.Mention}} ID grupy musi być liczbą większą od 0.",
"deleteObservation.invalidObservationID": "{{.Mention}} ID obserwacji musi być liczbą większą od 0.",
"deleteObservation.groupNotFound": "{{.Mention}} Grupa nie została znaleziona.",
"deleteObservation.success": "{{.Mention}} Usunięto.",
"observations.invalidGroupID": "{{.Mention}} ID grupy musi być liczbą większą od 0.",
"observations.groupNotFound": "{{.Mention}} Grupa nie została znaleziona.",
"observations.title": "Obserwowane plemiona\nIndex | ID - Serwer - Plemię",
"showEnnobledBarbs.invalidGroupID": "{{.Mention}} ID grupy musi być liczbą większą od 0.",
"showEnnobledBarbs.groupNotFound": "{{.Mention}} Grupa nie została znaleziona.",
"showEnnobledBarbs.success_1": "{{.Mention}} Powiadomienia o podbitych barbarkach nie będą się już dłużej pokazywały.",
"showEnnobledBarbs.success_2": "{{.Mention}} Włączono powiadomienia o podbitych barbarkach.",
"changeLanguage.languageNotSupported": "{{.Mention}} Język nie jest obsługiwany.",
"changeLanguage.success": "{{.Mention}} Język został zmieniony.",
"cron.lostVillages.title": "Stracone wioski",
"cron.conqueredVillages.title": "Podbite wioski",
"cron.checkEnnoblements.msgLine": "{{.NewOwner}} [{{.NewOwnerTribe}}] podbił wioskę {{.Village}} (Poprzedni właściciel: {{.OldOwner}} [{{.OldOwnerTribe}}])",
"api.defaultError": "{{.Mention}} Wystąpił błąd podczas pobierania danych z API, prosimy spróbować później.",
"pagination.labelDisplayedPage": "Strona: {{.Page}} z {{.MaxPage}}",
"internalServerError": "{{.Mention}} Wewnętrzny błąd serwera, prosimy spróbować później."
}

View File

@ -4,13 +4,15 @@ type Group struct {
ID int `pg:",pk" json:"id" gqlgen:"id"`
ConqueredVillagesChannelID string `pg:",use_zero" json:"conqueredVillagesChannelID" gqlgen:"conqueredVillagesChannelID"`
LostVillagesChannelID string `pg:",use_zero" json:"lostVillagesChannelID" gqlgen:"lostVillagesChannelID"`
ServerID string `pg:"on_delete:CASCADE,use_zero" json:"serverID" gqlgen:"serverID"`
ShowEnnobledBarbarians bool `pg:",use_zero"`
ServerID string `pg:"on_delete:CASCADE,use_zero" json:"serverID" gqlgen:"serverID"`
Server *Server `json:"server,omitempty" gqlgen:"server"`
Observations Observations `json:"observation,omitempty" gqlgen:"observation"`
}
type GroupFilter struct {
tableName struct{} `urlstruct:"group"`
ID []int
ServerID []string
Limit int `urlstruct:",nowhere"`

View File

@ -30,6 +30,8 @@ func (o Observations) Contains(server string, id int) bool {
}
type ObservationFilter struct {
tableName struct{} `urlstruct:"observation"`
ID []int
Server []string
GroupID []int

View File

@ -4,10 +4,12 @@ type Server struct {
tableName struct{} `pg:",alias:server"`
ID string `pg:",pk" json:"id" gqlgen:"id"`
Lang string `pg:",use_zero"`
Groups []*Group
}
type ServerFilter struct {
tableName struct{} `urlstruct:"server"`
ID []string
Limit int `urlstruct:",nowhere"`
Offset int `urlstruct:",nowhere"`