package discord import ( "context" "fmt" "time" "gitea.dwysokinski.me/twhelp/dcbot/internal/domain" "github.com/bwmarrin/discordgo" ) type groupCommand struct { 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 { versionChoices, err := c.getVersionChoices() if err != nil { return err } var perm int64 = discordgo.PermissionAdministrator dm := false _, err = s.ApplicationCommandCreate(s.State.User.ID, "", &discordgo.ApplicationCommand{ Name: c.name(), Description: "Manages groups on this server", DefaultMemberPermissions: &perm, DMPermission: &dm, Options: []*discordgo.ApplicationCommandOption{ { Name: "create", Description: "Creates a new monitor group", Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "version", Description: "e.g. www.tribalwars.net, www.plemiona.pl", Type: discordgo.ApplicationCommandOptionString, Choices: versionChoices, Required: true, }, { Name: "server", Description: "Tribal Wars server (e.g. en115, pl170)", Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "internals", Description: "Show conquers in the same group", Type: discordgo.ApplicationCommandOptionBoolean, Required: true, }, { Name: "barbarians", Description: "Show barbarian conquers", Type: discordgo.ApplicationCommandOptionBoolean, Required: true, }, { Name: "channel-gains", Description: "Specifies on which channel notifications of gained villages will appear", Type: discordgo.ApplicationCommandOptionChannel, ChannelTypes: []discordgo.ChannelType{ discordgo.ChannelTypeGuildText, }, Required: false, }, { Name: "channel-losses", Description: "Specifies on which channel notifications of lost villages will appear", Type: discordgo.ApplicationCommandOptionChannel, ChannelTypes: []discordgo.ChannelType{ discordgo.ChannelTypeGuildText, }, Required: false, }, }, }, { Name: "list", Description: "Lists all created groups", Type: discordgo.ApplicationCommandOptionSubCommand, }, { Name: "set", Description: "Sets various properties in a group configuration", Type: discordgo.ApplicationCommandOptionSubCommandGroup, Options: []*discordgo.ApplicationCommandOption{ { Name: "channel-gains", Description: "Specifies on which channel notifications of gained villages will appear", Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: "Group ID", Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "channel", Description: "Channel", Type: discordgo.ApplicationCommandOptionChannel, ChannelTypes: []discordgo.ChannelType{ discordgo.ChannelTypeGuildText, }, Required: true, }, }, }, { Name: "channel-losses", Description: "Specifies on which channel notifications of lost villages will appear", Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: "Group ID", Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "channel", Description: "Channel ID", Type: discordgo.ApplicationCommandOptionChannel, ChannelTypes: []discordgo.ChannelType{ discordgo.ChannelTypeGuildText, }, Required: true, }, }, }, { Name: "internals", Description: "Enables/disables notifications of internal conquers", Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: "Group ID", Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "internals", Description: "Show conquers in the same group", Type: discordgo.ApplicationCommandOptionBoolean, Required: true, }, }, }, { Name: "barbarians", Description: "Enables/disables notifications of barbarian conquers", Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: "Group ID", Type: discordgo.ApplicationCommandOptionString, Required: true, }, { Name: "barbarians", Description: "Show barbarian conquers", Type: discordgo.ApplicationCommandOptionBoolean, Required: true, }, }, }, }, }, { Name: "unset", Description: "Unsets various properties in a group configuration", Type: discordgo.ApplicationCommandOptionSubCommandGroup, Options: []*discordgo.ApplicationCommandOption{ { Name: "channel-gains", Description: "Disables notifications of gained villages", Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: "Group ID", Type: discordgo.ApplicationCommandOptionString, Required: true, }, }, }, { Name: "channel-losses", Description: "Disables notifications of lost villages", Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: "Group ID", Type: discordgo.ApplicationCommandOptionString, Required: true, }, }, }, }, }, { Name: "delete", Description: "Deletes a group", Type: discordgo.ApplicationCommandOptionSubCommand, Options: []*discordgo.ApplicationCommandOption{ { Name: "group", Description: "Group ID", Type: discordgo.ApplicationCommandOptionString, Required: true, }, }, }, }, }) if err != nil { return fmt.Errorf("s.ApplicationCommandCreate: %w", err) } return nil } func (c *groupCommand) getVersionChoices() ([]*discordgo.ApplicationCommandOptionChoice, error) { choices, err := c.choiceSvc.Versions(context.Background()) if err != nil { return nil, fmt.Errorf("ChoiceService.Versions: %w", err) } dcChoices := make([]*discordgo.ApplicationCommandOptionChoice, 0, len(choices)) for _, v := range choices { dcChoices = append(dcChoices, &discordgo.ApplicationCommandOptionChoice{ Name: v.Name, Value: v.Value, }) } return dcChoices, nil } func (c *groupCommand) handle(s *discordgo.Session, i *discordgo.InteractionCreate) { cmdData := i.ApplicationCommandData() if cmdData.Name != c.name() { return } switch cmdData.Options[0].Name { case "create": c.handleCreate(s, i) return case "list": c.handleList(s, i) return case "set": c.handleSet(s, i) return case "unset": c.handleUnset(s, i) return case "delete": c.handleDelete(s, i) return default: } } func (c *groupCommand) handleCreate(s *discordgo.Session, i *discordgo.InteractionCreate) { ctx := context.Background() version := "" server := "" channelGains := "" channelLosses := "" internals := false barbarians := false for _, opt := range i.ApplicationCommandData().Options[0].Options { switch opt.Name { case "version": version = opt.StringValue() case "server": server = 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() } } params, err := domain.NewCreateGroupParams( i.GuildID, version, server, channelGains, channelLosses, barbarians, internals, ) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: messageFromError(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: messageFromError(err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "group has been successfully created (ID=" + group.ID + ")", }, }) } func (c *groupCommand) handleList(s *discordgo.Session, i *discordgo.InteractionCreate) { ctx := context.Background() groups, err := c.groupSvc.List(ctx, domain.ListGroupsParams{ ServerIDs: []string{i.GuildID}, }) if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: messageFromError(err), }, }) return } fields := make([]*discordgo.MessageEmbedField, 0, len(groups)) for _, g := range groups { channelGains := "Not set" if g.ChannelGains != "" { channelGains = buildChannelMention(g.ChannelGains) } channelLosses := "Not set" if g.ChannelLosses != "" { channelLosses = buildChannelMention(g.ChannelLosses) } fields = append(fields, &discordgo.MessageEmbedField{ Name: g.ID, Value: fmt.Sprintf( "**Server**: %s\n**Channel gains**: %s\n**Channel losses**: %s\n**Internals**: %s\n **Barbarians**: %s", g.ServerKey, channelGains, channelLosses, boolToEmoji(g.Internals), boolToEmoji(g.Barbarians), ), Inline: false, }) } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Embeds: []*discordgo.MessageEmbed{ { Type: discordgo.EmbedTypeRich, Title: "Group list", Fields: fields, Timestamp: formatTimestamp(time.Now()), }, }, }, }) } func (c *groupCommand) handleSet(s *discordgo.Session, i *discordgo.InteractionCreate) { switch i.ApplicationCommandData().Options[0].Options[0].Name { case "channel-gains": c.handleSetChannelGains(s, i) return case "channel-losses": c.handleSetChannelLosses(s, i) return case "internals": c.handleSetInternals(s, i) return case "barbarians": c.handleSetBarbarians(s, i) return default: } } func (c *groupCommand) handleSetChannelGains(s *discordgo.Session, i *discordgo.InteractionCreate) { ctx := context.Background() 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: messageFromError(err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "group has been successfully updated", }, }) } func (c *groupCommand) handleSetChannelLosses(s *discordgo.Session, i *discordgo.InteractionCreate) { ctx := context.Background() 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: messageFromError(err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "group has been successfully updated", }, }) } func (c *groupCommand) handleSetInternals(s *discordgo.Session, i *discordgo.InteractionCreate) { ctx := context.Background() 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: messageFromError(err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "group has been successfully updated", }, }) } func (c *groupCommand) handleSetBarbarians(s *discordgo.Session, i *discordgo.InteractionCreate) { ctx := context.Background() 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: messageFromError(err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "group has been successfully updated", }, }) } func (c *groupCommand) handleUnset(s *discordgo.Session, i *discordgo.InteractionCreate) { switch i.ApplicationCommandData().Options[0].Options[0].Name { case "channel-gains": c.handleUnsetChannelGains(s, i) return case "channel-losses": c.handleUnsetChannelLosses(s, i) return default: } } func (c *groupCommand) handleUnsetChannelGains(s *discordgo.Session, i *discordgo.InteractionCreate) { ctx := context.Background() group := i.ApplicationCommandData().Options[0].Options[0].Options[0].StringValue() _, err := c.groupSvc.SetChannelGains(ctx, group, i.GuildID, "") if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: messageFromError(err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "group has been successfully updated", }, }) } func (c *groupCommand) handleUnsetChannelLosses(s *discordgo.Session, i *discordgo.InteractionCreate) { ctx := context.Background() group := i.ApplicationCommandData().Options[0].Options[0].Options[0].StringValue() _, err := c.groupSvc.SetChannelLosses(ctx, group, i.GuildID, "") if err != nil { _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: messageFromError(err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "group has been successfully updated", }, }) } func (c *groupCommand) handleDelete(s *discordgo.Session, i *discordgo.InteractionCreate) { ctx := context.Background() 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: messageFromError(err), }, }) return } _ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: "group has been successfully deleted", }, }) }