Dawid Wysokiński
d1ba315bd2
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #116
378 lines
11 KiB
Go
378 lines
11 KiB
Go
package discord
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/discord/internal/discordi18n"
|
|
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
|
|
"github.com/bwmarrin/discordgo"
|
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type coordsCommand struct {
|
|
localizer *discordi18n.Localizer
|
|
logger *zap.Logger
|
|
villageSvc VillageService
|
|
choiceSvc *choiceService
|
|
}
|
|
|
|
func (c *coordsCommand) name() string {
|
|
return "coords"
|
|
}
|
|
|
|
func (c *coordsCommand) register(s *discordgo.Session) error {
|
|
if err := c.create(s); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.AddHandler(c.handle)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *coordsCommand) create(s *discordgo.Session) error {
|
|
dm := true
|
|
|
|
optionVersionDefault, optionVersionLocalizations, err := c.localizer.LocalizeDiscord("cmd.coords.option.version.description")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
optionServerDefault, optionServerLocalizations, err := c.localizer.LocalizeDiscord("cmd.coords.option.server.description")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
optionCoordsDefault, optionCoordsLocalizations, err := c.localizer.LocalizeDiscord("cmd.coords.option.coords.description")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.coords.description")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
versionChoices, err := c.choiceSvc.versions(context.Background())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = s.ApplicationCommandCreate(s.State.User.ID, "", &discordgo.ApplicationCommand{
|
|
Name: c.name(),
|
|
Description: cmdDescriptionDefault,
|
|
DescriptionLocalizations: &cmdDescriptionLocalizations,
|
|
DMPermission: &dm,
|
|
Options: []*discordgo.ApplicationCommandOption{
|
|
{
|
|
Name: "version",
|
|
Description: optionVersionDefault,
|
|
DescriptionLocalizations: optionVersionLocalizations,
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
Choices: versionChoices,
|
|
Required: true,
|
|
},
|
|
{
|
|
Name: "server",
|
|
Description: optionServerDefault,
|
|
DescriptionLocalizations: optionServerLocalizations,
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
Required: true,
|
|
},
|
|
{
|
|
Name: "coords",
|
|
Description: optionCoordsDefault,
|
|
DescriptionLocalizations: optionCoordsLocalizations,
|
|
Type: discordgo.ApplicationCommandOptionString,
|
|
Required: true,
|
|
MaxLength: domain.VillageCoordsStringMaxLength,
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *coordsCommand) handle(s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
ctx := context.Background()
|
|
|
|
//nolint:exhaustive
|
|
switch i.Type {
|
|
case discordgo.InteractionApplicationCommand:
|
|
c.handleCommand(ctx, s, i)
|
|
case discordgo.InteractionMessageComponent:
|
|
c.handleMessageComponent(ctx, s, i)
|
|
}
|
|
}
|
|
|
|
func (c *coordsCommand) handleCommand(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
cmdData := i.ApplicationCommandData()
|
|
|
|
if cmdData.Name != c.name() {
|
|
return
|
|
}
|
|
|
|
locale := string(i.Locale)
|
|
|
|
params, err := c.optionsToTranslateCoordsParams(cmdData.Options)
|
|
if err != nil {
|
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: c.localizer.LocalizeError(locale, err),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
res, err := c.villageSvc.TranslateCoords(ctx, params, 1)
|
|
if err != nil {
|
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: c.localizer.LocalizeError(locale, err),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
_ = s.InteractionRespond(i.Interaction, c.buildResponse(res, locale))
|
|
}
|
|
|
|
const coordsTranslationVillagesPerPage = 7
|
|
|
|
func (c *coordsCommand) optionsToTranslateCoordsParams(
|
|
options []*discordgo.ApplicationCommandInteractionDataOption,
|
|
) (domain.TranslateVillageCoordsParams, error) {
|
|
version := ""
|
|
server := ""
|
|
coordsStr := ""
|
|
|
|
for _, opt := range options {
|
|
switch opt.Name {
|
|
case "version":
|
|
version = opt.StringValue()
|
|
case "server":
|
|
server = opt.StringValue()
|
|
case "coords":
|
|
coordsStr = opt.StringValue()
|
|
}
|
|
}
|
|
|
|
return domain.NewTranslateVillageCoordsParams(version, server, coordsStr, coordsTranslationVillagesPerPage)
|
|
}
|
|
|
|
func (c *coordsCommand) handleMessageComponent(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
|
|
cmpData := i.MessageComponentData()
|
|
|
|
if cmpData.ComponentType != discordgo.ButtonComponent {
|
|
return
|
|
}
|
|
|
|
locale := string(i.Locale)
|
|
|
|
page, hash, err := c.parseButtonID(cmpData.CustomID)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
res, err := c.villageSvc.TranslateCoordsFromHash(ctx, hash, page)
|
|
if err != nil {
|
|
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: c.localizer.LocalizeError(locale, err),
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
_ = s.InteractionRespond(i.Interaction, c.buildResponse(res, locale))
|
|
}
|
|
|
|
func (c *coordsCommand) buildResponse(res domain.TranslateVillageCoordsResult, locale string) *discordgo.InteractionResponse {
|
|
titleMessageID := "cmd.coords.embed.title"
|
|
title, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: titleMessageID})
|
|
if err != nil {
|
|
c.logger.Error("no message with the specified id", zap.String("id", titleMessageID), zap.Error(err))
|
|
return &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: c.localizer.LocalizeError(locale, err),
|
|
},
|
|
}
|
|
}
|
|
|
|
footerMessageID := "cmd.coords.embed.footer"
|
|
footer, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{
|
|
MessageID: footerMessageID,
|
|
TemplateData: map[string]any{
|
|
"Page": res.Page,
|
|
"MaxPage": res.MaxPage,
|
|
},
|
|
})
|
|
if err != nil {
|
|
c.logger.Error("no message with the specified id", zap.String("id", footerMessageID), zap.Error(err))
|
|
return &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: c.localizer.LocalizeError(locale, err),
|
|
},
|
|
}
|
|
}
|
|
|
|
buttonNextPageMessageID := "cmd.coords.embed.button.next"
|
|
buttonNextPage, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: buttonNextPageMessageID})
|
|
if err != nil {
|
|
c.logger.Error("no message with the specified id", zap.String("id", buttonNextPageMessageID), zap.Error(err))
|
|
return &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: c.localizer.LocalizeError(locale, err),
|
|
},
|
|
}
|
|
}
|
|
|
|
buttonPrevPageMessageID := "cmd.coords.embed.button.prev"
|
|
buttonPrevPage, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: buttonPrevPageMessageID})
|
|
if err != nil {
|
|
c.logger.Error("no message with the specified id", zap.String("id", buttonPrevPageMessageID), zap.Error(err))
|
|
return &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: c.localizer.LocalizeError(locale, err),
|
|
},
|
|
}
|
|
}
|
|
|
|
description, err := c.buildDescription(locale, res.Villages, res.NotFound)
|
|
if err != nil {
|
|
return &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Content: c.localizer.LocalizeError(locale, err),
|
|
},
|
|
}
|
|
}
|
|
|
|
return &discordgo.InteractionResponse{
|
|
Type: discordgo.InteractionResponseChannelMessageWithSource,
|
|
Data: &discordgo.InteractionResponseData{
|
|
Embeds: []*discordgo.MessageEmbed{
|
|
{
|
|
Type: discordgo.EmbedTypeRich,
|
|
Title: title,
|
|
Description: description,
|
|
Color: colorGrey,
|
|
Timestamp: formatTimestamp(time.Now()),
|
|
Footer: &discordgo.MessageEmbedFooter{
|
|
Text: footer,
|
|
},
|
|
},
|
|
},
|
|
Components: []discordgo.MessageComponent{
|
|
discordgo.ActionsRow{
|
|
Components: []discordgo.MessageComponent{
|
|
discordgo.Button{
|
|
Style: discordgo.PrimaryButton,
|
|
CustomID: c.buildButtonID(res.Page-1, res.ParamsSHA256),
|
|
Label: buttonPrevPage,
|
|
Disabled: !res.HasPrev,
|
|
},
|
|
discordgo.Button{
|
|
Style: discordgo.PrimaryButton,
|
|
CustomID: c.buildButtonID(res.Page+1, res.ParamsSHA256),
|
|
Label: buttonNextPage,
|
|
Disabled: !res.HasNext,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (c *coordsCommand) buildDescription(locale string, villages []domain.Village, notFound []string) (string, error) {
|
|
var builderDescription strings.Builder
|
|
|
|
for i, v := range villages {
|
|
if i > 0 {
|
|
builderDescription.WriteString("\n")
|
|
}
|
|
|
|
descriptionMessageID := "cmd.coords.embed.description.village"
|
|
description, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{
|
|
MessageID: descriptionMessageID,
|
|
TemplateData: map[string]any{
|
|
"Village": v,
|
|
},
|
|
Funcs: template.FuncMap{
|
|
"buildPlayerMarkdown": buildPlayerMarkdown,
|
|
"buildLink": buildLink,
|
|
},
|
|
})
|
|
if err != nil {
|
|
c.logger.Error("no message with the specified id", zap.String("id", descriptionMessageID), zap.Error(err))
|
|
return "", err
|
|
}
|
|
|
|
builderDescription.WriteString(description)
|
|
}
|
|
|
|
if len(notFound) == 0 {
|
|
return builderDescription.String(), nil
|
|
}
|
|
|
|
if builderDescription.Len() > 0 {
|
|
builderDescription.WriteString("\n")
|
|
}
|
|
|
|
descriptionMessageID := "cmd.coords.embed.description.not-found"
|
|
description, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{
|
|
MessageID: descriptionMessageID,
|
|
TemplateData: map[string]any{
|
|
"Coords": strings.Join(notFound, ", "),
|
|
},
|
|
})
|
|
if err != nil {
|
|
c.logger.Error("no message with the specified id", zap.String("id", descriptionMessageID), zap.Error(err))
|
|
return "", err
|
|
}
|
|
|
|
builderDescription.WriteString(description)
|
|
|
|
return builderDescription.String(), nil
|
|
}
|
|
|
|
const (
|
|
coordsTranslationButtonIDPrefix = "coords"
|
|
coordsTranslationButtonIDSeparator = "-"
|
|
)
|
|
|
|
func (c *coordsCommand) buildButtonID(page int32, hash string) string {
|
|
return fmt.Sprintf("%s%s%d%s%s", coordsTranslationButtonIDPrefix, coordsTranslationButtonIDSeparator, page, coordsTranslationButtonIDSeparator, hash)
|
|
}
|
|
|
|
func (c *coordsCommand) parseButtonID(id string) (int32, string, error) {
|
|
chunks := strings.Split(id, coordsTranslationButtonIDSeparator)
|
|
if len(chunks) != 3 || chunks[0] != coordsTranslationButtonIDPrefix {
|
|
return 0, "", fmt.Errorf("couldn't parse '%s': incorrect format", id)
|
|
}
|
|
|
|
page, err := strconv.ParseInt(chunks[1], 10, 32)
|
|
if err != nil {
|
|
return 0, "", fmt.Errorf("couldn't parse '%s': %w", id, err)
|
|
}
|
|
|
|
return int32(page), chunks[2], nil
|
|
}
|