dcbot/internal/discord/command_group.go
Dawid Wysokiński e110807619
All checks were successful
continuous-integration/drone/push Build is passing
refactor: group & monitor refactor (#107)
Reviewed-on: #107
2023-06-18 06:47:51 +00:00

819 lines
23 KiB
Go

package discord
import (
"context"
"fmt"
"strings"
"time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"github.com/bwmarrin/discordgo"
)
const (
tribeTagMaxLength = 10
)
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: "details",
Description: "Displays group details (including tribes added to the group)",
Type: discordgo.ApplicationCommandOptionSubCommand,
Options: []*discordgo.ApplicationCommandOption{
{
Name: "group",
Description: "Group ID",
Type: discordgo.ApplicationCommandOptionString,
Required: true,
},
},
},
{
Name: "monitor",
Description: "Manages monitors",
Type: discordgo.ApplicationCommandOptionSubCommandGroup,
Options: []*discordgo.ApplicationCommandOption{
{
Name: "add",
Description: "Adds a new monitor to a group",
Type: discordgo.ApplicationCommandOptionSubCommand,
Options: []*discordgo.ApplicationCommandOption{
{
Name: "group",
Description: "Group ID",
Type: discordgo.ApplicationCommandOptionString,
Required: true,
},
{
Name: "tag",
Description: "Tribe tag",
Type: discordgo.ApplicationCommandOptionString,
Required: true,
MaxLength: tribeTagMaxLength,
},
},
},
{
Name: "delete",
Description: "Deletes a monitor",
Type: discordgo.ApplicationCommandOptionSubCommand,
Options: []*discordgo.ApplicationCommandOption{
{
Name: "group",
Description: "Group ID",
Type: discordgo.ApplicationCommandOptionString,
Required: true,
},
{
Name: "tag",
Description: "Tribe tag",
Type: discordgo.ApplicationCommandOptionString,
Required: true,
MaxLength: tribeTagMaxLength,
},
},
},
},
},
{
Name: "set",
Description: "Sets various properties in 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
}
ctx := context.Background()
switch cmdData.Options[0].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 "unset":
c.handleUnset(ctx, s, i)
case "monitor":
c.handleMonitor(ctx, s, i)
case "delete":
c.handleDelete(ctx, s, i)
}
}
func (c *groupCommand) handleCreate(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
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(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
groups, err := c.groupSvc.ListServer(ctx, 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\n **Number of monitored monitors**: %d",
g.ServerKey,
channelGains,
channelLosses,
boolToEmoji(g.Internals),
boolToEmoji(g.Barbarians),
len(g.Monitors),
),
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) handleDetails(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
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: messageFromError(err),
},
})
return
}
var builderMonitors strings.Builder
for i, m := range g.Monitors {
if i > 0 {
builderMonitors.WriteString(" ")
}
builderMonitors.WriteString(buildLink(m.Tribe.Tag, m.Tribe.ProfileURL))
}
if builderMonitors.Len() == 0 {
builderMonitors.WriteString("None")
}
channelGains := "Not set"
if g.ChannelGains != "" {
channelGains = buildChannelMention(g.ChannelGains)
}
channelLosses := "Not set"
if g.ChannelLosses != "" {
channelLosses = buildChannelMention(g.ChannelLosses)
}
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Embeds: []*discordgo.MessageEmbed{
{
Type: discordgo.EmbedTypeRich,
Title: g.ID,
Description: fmt.Sprintf(
"**Server**: %s\n**Channel gains**: %s\n**Channel losses**: %s\n**Internals**: %s\n **Barbarians**: %s\n **Monitored tribes**: %s",
g.ServerKey,
channelGains,
channelLosses,
boolToEmoji(g.Internals),
boolToEmoji(g.Barbarians),
builderMonitors.String(),
),
Timestamp: formatTimestamp(time.Now()),
},
},
},
})
}
func (c *groupCommand) handleSet(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
switch i.ApplicationCommandData().Options[0].Options[0].Name {
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)
}
}
func (c *groupCommand) handleSetChannelGains(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
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(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
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(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
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(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
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(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
switch i.ApplicationCommandData().Options[0].Options[0].Name {
case "channel-gains":
c.handleUnsetChannelGains(ctx, s, i)
case "channel-losses":
c.handleUnsetChannelLosses(ctx, s, i)
}
}
func (c *groupCommand) handleUnsetChannelGains(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
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(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
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) handleMonitor(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
switch i.ApplicationCommandData().Options[0].Options[0].Name {
case "add":
c.handleMonitorAdd(ctx, s, i)
case "delete":
c.handleMonitorDelete(ctx, s, i)
}
}
func (c *groupCommand) handleMonitorAdd(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
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.AddMonitor(ctx, group, i.GuildID, tag)
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: "monitor has been successfully added",
},
})
}
func (c *groupCommand) handleMonitorDelete(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
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.DeleteMonitor(ctx, group, i.GuildID, tag)
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: "monitor has been successfully deleted",
},
})
}
func (c *groupCommand) handleDelete(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
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",
},
})
}