package discord import ( "context" "strings" "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" ) const ( tribeTagMaxLength = 10 ) type groupCommand struct { localizer *discordi18n.Localizer logger *zap.Logger groupSvc GroupService choiceSvc *choiceService } func (c *groupCommand) name() string { return "group" } func (c *groupCommand) register(s *discordgo.Session) error { if err := c.create(s); err != nil { return err } s.AddHandler(c.handle) return nil } func (c *groupCommand) create(s *discordgo.Session) error { var perm int64 = discordgo.PermissionAdministrator dm := false subcommandBuilders := []func() (*discordgo.ApplicationCommandOption, error){ c.buildSubcommandCreate, c.buildSubcommandList, c.buildSubcommandDetails, c.buildSubcommandTribe, c.buildSubcommandSet, c.buildSubcommandDelete, } subcommands := make([]*discordgo.ApplicationCommandOption, 0, len(subcommandBuilders)) for _, f := range subcommandBuilders { cmd, err := f() if err != nil { return err } subcommands = append(subcommands, cmd) } groupCmdDescriptionDefault, groupCmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.description") if err != nil { return err } _, err = s.ApplicationCommandCreate(s.State.User.ID, "", &discordgo.ApplicationCommand{ Name: c.name(), Description: groupCmdDescriptionDefault, DescriptionLocalizations: &groupCmdDescriptionLocalizations, DefaultMemberPermissions: &perm, DMPermission: &dm, Options: subcommands, }) if err != nil { return err } return nil } func (c *groupCommand) buildSubcommandCreate() (*discordgo.ApplicationCommandOption, error) { options, err := c.buildSubcommandCreateOptions() if err != nil { return nil, err } cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.create.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "create", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommand, Options: options, }, nil } func (c *groupCommand) buildSubcommandCreateOptions() ([]*discordgo.ApplicationCommandOption, error) { versionChoices, err := c.choiceSvc.versions(context.Background()) if err != nil { return nil, err } languageChoices, err := c.choiceSvc.languages() if err != nil { return nil, err } optionVersionDefault, optionVersionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.create.option.version.description") if err != nil { return nil, err } optionServerDefault, optionServerLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.create.option.server.description") if err != nil { return nil, err } optionInternalsDefault, optionInternalsLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.create.option.internals.description") if err != nil { return nil, err } optionBarbariansDefault, optionBarbariansLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.create.option.barbarians.description") if err != nil { return nil, err } optionChannelGainsDefault, optionChannelGainsLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.create.option.channel-gains.description") if err != nil { return nil, err } optionLanguageDefault, optionLanguageLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.create.option.language.description") if err != nil { return nil, err } optionChannelLossesDefault, optionChannelLossesLocalizations, err := c.localizer.LocalizeDiscord( "cmd.group.create.option.channel-losses.description", ) if err != nil { return nil, err } return []*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: "language", Description: optionLanguageDefault, DescriptionLocalizations: optionLanguageLocalizations, Type: discordgo.ApplicationCommandOptionString, Choices: languageChoices, Required: true, }, { Name: "internals", Description: optionInternalsDefault, DescriptionLocalizations: optionInternalsLocalizations, Type: discordgo.ApplicationCommandOptionBoolean, Required: true, }, { Name: "barbarians", Description: optionBarbariansDefault, DescriptionLocalizations: optionBarbariansLocalizations, Type: discordgo.ApplicationCommandOptionBoolean, Required: true, }, { Name: "channel-gains", Description: optionChannelGainsDefault, DescriptionLocalizations: optionChannelGainsLocalizations, Type: discordgo.ApplicationCommandOptionChannel, ChannelTypes: []discordgo.ChannelType{ discordgo.ChannelTypeGuildText, }, Required: false, }, { Name: "channel-losses", Description: optionChannelLossesDefault, DescriptionLocalizations: optionChannelLossesLocalizations, Type: discordgo.ApplicationCommandOptionChannel, ChannelTypes: []discordgo.ChannelType{ discordgo.ChannelTypeGuildText, }, Required: false, }, }, nil } func (c *groupCommand) buildSubcommandList() (*discordgo.ApplicationCommandOption, error) { cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.list.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "list", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommand, }, nil } func (c *groupCommand) buildSubcommandDetails() (*discordgo.ApplicationCommandOption, error) { cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.details.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "details", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: "Group ID", Type: discordgo.ApplicationCommandOptionString, Required: true, }, }, }, nil } func (c *groupCommand) buildSubcommandTribe() (*discordgo.ApplicationCommandOption, error) { cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.tribe.description") if err != nil { return nil, err } subcommandAdd, err := c.buildSubcommandMonitorAdd() if err != nil { return nil, err } subcommandRemove, err := c.buildSubcommandMonitorRemove() if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "tribe", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommandGroup, Options: []*discordgo.ApplicationCommandOption{ subcommandAdd, subcommandRemove, }, }, nil } func (c *groupCommand) buildSubcommandMonitorAdd() (*discordgo.ApplicationCommandOption, error) { cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.tribe.add.description") if err != nil { return nil, err } optionGroupDefault, optionGroupLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.tribe.add.option.group.description") if err != nil { return nil, err } optionTagDefault, optionTagLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.tribe.add.option.tag.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "add", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: optionGroupDefault, DescriptionLocalizations: optionGroupLocalizations, Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "tag", Description: optionTagDefault, DescriptionLocalizations: optionTagLocalizations, Type: discordgo.ApplicationCommandOptionString, Required: true, MaxLength: tribeTagMaxLength, }, }, }, nil } func (c *groupCommand) buildSubcommandMonitorRemove() (*discordgo.ApplicationCommandOption, error) { cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.tribe.remove.description") if err != nil { return nil, err } optionGroupDefault, optionGroupLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.tribe.remove.option.group.description") if err != nil { return nil, err } optionTagDefault, optionTagLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.tribe.remove.option.tag.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "remove", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: optionGroupDefault, DescriptionLocalizations: optionGroupLocalizations, Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "tag", Description: optionTagDefault, DescriptionLocalizations: optionTagLocalizations, Type: discordgo.ApplicationCommandOptionString, Required: true, MaxLength: tribeTagMaxLength, }, }, }, nil } func (c *groupCommand) buildSubcommandSet() (*discordgo.ApplicationCommandOption, error) { subcommandBuilders := []func() (*discordgo.ApplicationCommandOption, error){ c.buildSubcommandSetLanguage, c.buildSubcommandSetChannelGains, c.buildSubcommandSetChannelLosses, c.buildSubcommandSetInternals, c.buildSubcommandSetBarbarians, } subcommands := make([]*discordgo.ApplicationCommandOption, 0, len(subcommandBuilders)) for _, f := range subcommandBuilders { cmd, err := f() if err != nil { return nil, err } subcommands = append(subcommands, cmd) } cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "set", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommandGroup, Options: subcommands, }, nil } func (c *groupCommand) buildSubcommandSetLanguage() (*discordgo.ApplicationCommandOption, error) { languageChoices, err := c.choiceSvc.languages() if err != nil { return nil, err } cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.language.description") if err != nil { return nil, err } optionGroupDefault, optionGroupLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.language.option.group.description") if err != nil { return nil, err } optionLanguageDefault, optionLanguageLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.language.option.language.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "language", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: optionGroupDefault, DescriptionLocalizations: optionGroupLocalizations, Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "language", Description: optionLanguageDefault, DescriptionLocalizations: optionLanguageLocalizations, Type: discordgo.ApplicationCommandOptionString, Choices: languageChoices, Required: true, }, }, }, nil } func (c *groupCommand) buildSubcommandSetChannelGains() (*discordgo.ApplicationCommandOption, error) { cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.channel-gains.description") if err != nil { return nil, err } optionGroupDefault, optionGroupLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.channel-gains.option.group.description") if err != nil { return nil, err } optionChannelDefault, optionChannelLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.channel-gains.option.channel.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "channel-gains", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: optionGroupDefault, DescriptionLocalizations: optionGroupLocalizations, Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "channel", Description: optionChannelDefault, DescriptionLocalizations: optionChannelLocalizations, Type: discordgo.ApplicationCommandOptionChannel, ChannelTypes: []discordgo.ChannelType{ discordgo.ChannelTypeGuildText, }, }, }, }, nil } func (c *groupCommand) buildSubcommandSetChannelLosses() (*discordgo.ApplicationCommandOption, error) { cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.channel-losses.description") if err != nil { return nil, err } optionGroupDefault, optionGroupLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.channel-losses.option.group.description") if err != nil { return nil, err } optionChannelDefault, optionChannelLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.channel-losses.option.channel.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "channel-losses", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: optionGroupDefault, DescriptionLocalizations: optionGroupLocalizations, Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "channel", Description: optionChannelDefault, DescriptionLocalizations: optionChannelLocalizations, Type: discordgo.ApplicationCommandOptionChannel, ChannelTypes: []discordgo.ChannelType{ discordgo.ChannelTypeGuildText, }, }, }, }, nil } func (c *groupCommand) buildSubcommandSetInternals() (*discordgo.ApplicationCommandOption, error) { cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.internals.description") if err != nil { return nil, err } optionGroupDefault, optionGroupLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.internals.option.group.description") if err != nil { return nil, err } optionInternalsDefault, optionInternalsLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.internals.option.internals.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "internals", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: optionGroupDefault, DescriptionLocalizations: optionGroupLocalizations, Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "internals", Description: optionInternalsDefault, DescriptionLocalizations: optionInternalsLocalizations, Type: discordgo.ApplicationCommandOptionBoolean, Required: true, }, }, }, nil } func (c *groupCommand) buildSubcommandSetBarbarians() (*discordgo.ApplicationCommandOption, error) { cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.barbarians.description") if err != nil { return nil, err } optionGroupDefault, optionGroupLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.barbarians.option.group.description") if err != nil { return nil, err } optionBarbariansDefault, optionBarbariansLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.barbarians.option.barbarians.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "barbarians", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: optionGroupDefault, DescriptionLocalizations: optionGroupLocalizations, Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "barbarians", Description: optionBarbariansDefault, DescriptionLocalizations: optionBarbariansLocalizations, Type: discordgo.ApplicationCommandOptionBoolean, Required: true, }, }, }, nil } func (c *groupCommand) buildSubcommandDelete() (*discordgo.ApplicationCommandOption, error) { cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.delete.description") if err != nil { return nil, err } optionGroupDefault, optionGroupLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.delete.option.group.description") if err != nil { return nil, err } return &discordgo.ApplicationCommandOption{ Name: "delete", Description: cmdDescriptionDefault, DescriptionLocalizations: cmdDescriptionLocalizations, Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: optionGroupDefault, DescriptionLocalizations: optionGroupLocalizations, Type: discordgo.ApplicationCommandOptionString, Required: true, }, }, }, nil } func (c *groupCommand) handle(s *discordgo.Session, i *discordgo.InteractionCreate) { if i.Type != discordgo.InteractionApplicationCommand { return } cmdData := i.ApplicationCommandData() if cmdData.Name != c.name() { return } ctx := context.Background() name := cmdData.Options[0].Name switch name { case "create": c.handleCreate(ctx, s, i) case "list": c.handleList(ctx, s, i) case "details": c.handleDetails(ctx, s, i) case "set": c.handleSet(ctx, s, i) case "tribe": c.handleTribe(ctx, s, i) case "delete": c.handleDelete(ctx, s, i) default: c.logger.Error("unknown subcommand", zap.String("command", "group"), zap.String("subcommand", name)) } } func (c *groupCommand) handleCreate(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { locale := string(i.Locale) params, err := c.optionsToCreateGroupParams(s, i.GuildID, i.ApplicationCommandData().Options[0].Options) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } group, err := c.groupSvc.Create(ctx, params) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } successMessageID := "cmd.group.create.success" successMsg, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{ MessageID: successMessageID, TemplateData: map[string]any{ "GroupID": group.ID, }, }) if err != nil { c.logger.Error("no message with the specified id", zap.String("id", successMessageID), zap.Error(err)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: successMsg, }, }) } func (c *groupCommand) optionsToCreateGroupParams( s *discordgo.Session, guildID string, options []*discordgo.ApplicationCommandInteractionDataOption, ) (domain.CreateGroupParams, error) { version := "" server := "" channelGains := "" channelLosses := "" internals := false barbarians := false languageTag := "" for _, opt := range options { switch opt.Name { case "version": version = opt.StringValue() case "server": server = opt.StringValue() case "language": languageTag = opt.StringValue() case "channel-gains": channelGains = opt.ChannelValue(s).ID case "channel-losses": channelLosses = opt.ChannelValue(s).ID case "internals": internals = opt.BoolValue() case "barbarians": barbarians = opt.BoolValue() } } return domain.NewCreateGroupParams( guildID, version, server, languageTag, channelGains, channelLosses, barbarians, internals, ) } func (c *groupCommand) handleList(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { locale := string(i.Locale) groups, err := c.groupSvc.ListServer(ctx, i.GuildID) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } fieldValueMessageID := "cmd.group.list.embed.field.value" fields := make([]*discordgo.MessageEmbedField, 0, len(groups)) for _, g := range groups { channelGains := "-" if g.ChannelGains != "" { channelGains = buildChannelMention(g.ChannelGains) } channelLosses := "-" if g.ChannelLosses != "" { channelLosses = buildChannelMention(g.ChannelLosses) } langMessageID := buildLangMessageID(g.LanguageTag) lang, localizeErr := c.localizer.Localize(locale, &i18n.LocalizeConfig{ MessageID: langMessageID, }) if localizeErr != nil { c.logger.Error("no message with the specified id", zap.String("id", langMessageID), zap.Error(localizeErr)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, localizeErr), }, }) return } fieldValue, localizeErr := c.localizer.Localize(locale, &i18n.LocalizeConfig{ MessageID: fieldValueMessageID, TemplateData: map[string]any{ "ServerKey": g.ServerKey, "ChannelGains": channelGains, "ChannelLosses": channelLosses, "Internals": boolToEmoji(g.Internals), "Barbarians": boolToEmoji(g.Barbarians), "NumTribes": len(g.Monitors), "Language": lang, }, }) if localizeErr != nil { c.logger.Error("no message with the specified id", zap.String("id", fieldValueMessageID), zap.Error(localizeErr)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, localizeErr), }, }) return } fields = append(fields, &discordgo.MessageEmbedField{ Name: g.ID, Value: fieldValue, Inline: false, }) } titleMessageID := "cmd.group.list.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)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Embeds: []*discordgo.MessageEmbed{ { Type: discordgo.EmbedTypeRich, Title: title, Fields: fields, Color: colorGrey, Timestamp: formatTimestamp(time.Now()), }, }, }, }) } func (c *groupCommand) handleDetails(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { locale := string(i.Locale) g, err := c.groupSvc.GetWithTribes(ctx, i.ApplicationCommandData().Options[0].Options[0].StringValue(), i.GuildID) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } var builderTribes strings.Builder for i, m := range g.Monitors { if i > 0 { builderTribes.WriteString(" ") } builderTribes.WriteString(buildLink(m.Tribe.Tag, m.Tribe.ProfileURL)) } if builderTribes.Len() == 0 { builderTribes.WriteString("-") } channelGains := "-" if g.ChannelGains != "" { channelGains = buildChannelMention(g.ChannelGains) } channelLosses := "-" if g.ChannelLosses != "" { channelLosses = buildChannelMention(g.ChannelLosses) } langMessageID := buildLangMessageID(g.LanguageTag) lang, localizeErr := c.localizer.Localize(locale, &i18n.LocalizeConfig{ MessageID: langMessageID, }) if localizeErr != nil { c.logger.Error("no message with the specified id", zap.String("id", langMessageID), zap.Error(localizeErr)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, localizeErr), }, }) return } descriptionMessageID := "cmd.group.details.embed.description" description, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{ MessageID: descriptionMessageID, TemplateData: map[string]any{ "ServerKey": g.ServerKey, "ChannelGains": channelGains, "ChannelLosses": channelLosses, "Internals": boolToEmoji(g.Internals), "Barbarians": boolToEmoji(g.Barbarians), "Tribes": builderTribes.String(), "Language": lang, }, }) if err != nil { c.logger.Error("no message with the specified id", zap.String("id", descriptionMessageID), zap.Error(err)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Embeds: []*discordgo.MessageEmbed{ { Type: discordgo.EmbedTypeRich, Title: g.ID, Description: description, Color: colorGrey, Timestamp: formatTimestamp(time.Now()), }, }, }, }) } func (c *groupCommand) handleSet(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { name := i.ApplicationCommandData().Options[0].Options[0].Name switch name { case "language": c.handleSetLanguage(ctx, s, i) case "channel-gains": c.handleSetChannelGains(ctx, s, i) case "channel-losses": c.handleSetChannelLosses(ctx, s, i) case "internals": c.handleSetInternals(ctx, s, i) case "barbarians": c.handleSetBarbarians(ctx, s, i) default: c.logger.Error("unknown subcommand", zap.String("command", "group set"), zap.String("subcommand", name)) } } func (c *groupCommand) handleSetLanguage(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { locale := string(i.Locale) group := "" languageTag := "" for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options { if opt == nil { continue } switch opt.Name { case "group": group = opt.StringValue() case "language": languageTag = opt.StringValue() } } _, err := c.groupSvc.SetLanguageTag(ctx, group, i.GuildID, languageTag) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } successMessageID := "cmd.group.set.language.success" successMsg, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: successMessageID}) if err != nil { c.logger.Error("no message with the specified id", zap.String("id", successMessageID), zap.Error(err)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: successMsg, }, }) } func (c *groupCommand) handleSetChannelGains(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { locale := string(i.Locale) group := "" channel := "" for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options { if opt == nil { continue } switch opt.Name { case "group": group = opt.StringValue() case "channel": channel = opt.ChannelValue(s).ID } } _, err := c.groupSvc.SetChannelGains(ctx, group, i.GuildID, channel) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } successMessageID := "cmd.group.set.channel-gains.success" successMsg, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: successMessageID}) if err != nil { c.logger.Error("no message with the specified id", zap.String("id", successMessageID), zap.Error(err)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: successMsg, }, }) } func (c *groupCommand) handleSetChannelLosses(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { locale := string(i.Locale) group := "" channel := "" for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options { if opt == nil { continue } switch opt.Name { case "group": group = opt.StringValue() case "channel": channel = opt.ChannelValue(s).ID } } _, err := c.groupSvc.SetChannelLosses(ctx, group, i.GuildID, channel) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } successMessageID := "cmd.group.set.channel-losses.success" successMsg, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: successMessageID}) if err != nil { c.logger.Error("no message with the specified id", zap.String("id", successMessageID), zap.Error(err)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: successMsg, }, }) } func (c *groupCommand) handleSetInternals(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { locale := string(i.Locale) group := "" internals := false for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options { if opt == nil { continue } switch opt.Name { case "group": group = opt.StringValue() case "internals": internals = opt.BoolValue() } } _, err := c.groupSvc.SetInternals(ctx, group, i.GuildID, internals) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } successMessageID := "cmd.group.set.internals.success" successMsg, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: successMessageID}) if err != nil { c.logger.Error("no message with the specified id", zap.String("id", successMessageID), zap.Error(err)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: successMsg, }, }) } func (c *groupCommand) handleSetBarbarians(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { locale := string(i.Locale) group := "" barbarians := false for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options { if opt == nil { continue } switch opt.Name { case "group": group = opt.StringValue() case "barbarians": barbarians = opt.BoolValue() } } _, err := c.groupSvc.SetBarbarians(ctx, group, i.GuildID, barbarians) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } successMessageID := "cmd.group.set.barbarians.success" successMsg, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: successMessageID}) if err != nil { c.logger.Error("no message with the specified id", zap.String("id", successMessageID), zap.Error(err)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: successMsg, }, }) } func (c *groupCommand) handleTribe(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { name := i.ApplicationCommandData().Options[0].Options[0].Name switch name { case "add": c.handleTribeAdd(ctx, s, i) case "remove": c.handleTribeRemove(ctx, s, i) default: c.logger.Error("unknown subcommand", zap.String("command", "group tribe"), zap.String("subcommand", name)) } } func (c *groupCommand) handleTribeAdd(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { locale := string(i.Locale) group := "" tag := "" for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options { if opt == nil { continue } switch opt.Name { case "group": group = opt.StringValue() case "tag": tag = opt.StringValue() } } _, err := c.groupSvc.AddTribe(ctx, group, i.GuildID, tag) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } successMessageID := "cmd.group.tribe.add.success" successMsg, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: successMessageID}) if err != nil { c.logger.Error("no message with the specified id", zap.String("id", successMessageID), zap.Error(err)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: successMsg, }, }) } func (c *groupCommand) handleTribeRemove(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { locale := string(i.Locale) group := "" tag := "" for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options { if opt == nil { continue } switch opt.Name { case "group": group = opt.StringValue() case "tag": tag = opt.StringValue() } } _, err := c.groupSvc.RemoveTribe(ctx, group, i.GuildID, tag) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } successMessageID := "cmd.group.tribe.remove.success" successMsg, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: successMessageID}) if err != nil { c.logger.Error("no message with the specified id", zap.String("id", successMessageID), zap.Error(err)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: successMsg, }, }) } func (c *groupCommand) handleDelete(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { locale := string(i.Locale) group := i.ApplicationCommandData().Options[0].Options[0].StringValue() if err := c.groupSvc.Delete(ctx, group, i.GuildID); err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } successMessageID := "cmd.group.delete.success" successMsg, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: successMessageID}) if err != nil { c.logger.Error("no message with the specified id", zap.String("id", successMessageID), zap.Error(err)) _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: c.localizer.LocalizeError(locale, err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: successMsg, }, }) }