From c3daca874e0aed1af32f8520fbaf548162cecbf7 Mon Sep 17 00:00:00 2001 From: DownloadableFox Date: Sun, 4 Aug 2024 16:11:02 -0500 Subject: [PATCH] added feature command --- core/types.go | 13 +++ modules/debug/commands.go | 176 +++++++++++++++++++++++++++++++++++ modules/debug/middlewares.go | 23 +++++ modules/debug/register.go | 16 ++++ 4 files changed, 228 insertions(+) diff --git a/core/types.go b/core/types.go index e638102..00986b5 100644 --- a/core/types.go +++ b/core/types.go @@ -54,3 +54,16 @@ func IsValidNamespace(namespace string) bool { func IsValidId(id string) bool { return IdRegex.MatchString(id) } + +func ParseIdentifier(identifier string) (*Identifier, error) { + parts := regexp.MustCompile(`:`).Split(identifier, 2) + if len(parts) != 2 { + return nil, ErrInvalidIdentifier + } + + if !IsValidNamespace(parts[0]) || !IsValidId(parts[1]) { + return nil, ErrInvalidIdentifier + } + + return NewIdentifier(parts[0], parts[1]), nil +} diff --git a/modules/debug/commands.go b/modules/debug/commands.go index 9616871..15ea10c 100644 --- a/modules/debug/commands.go +++ b/modules/debug/commands.go @@ -155,3 +155,179 @@ func HandlePingCommand(_ context.Context, s *discordgo.Session, e *discordgo.Int return nil } + +var FeatureCommandPermissions int64 = discordgo.PermissionAdministrator + +var FeatureCommand = &discordgo.ApplicationCommand{ + Name: "feature", + Description: "Manage features for the bot.", + DefaultMemberPermissions: &FeatureCommandPermissions, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "get", + Description: "Get the state of a feature.", + Type: discordgo.ApplicationCommandOptionSubCommand, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "feature", + Description: "The feature to get the state of.", + Type: discordgo.ApplicationCommandOptionString, + Required: true, + Autocomplete: true, + }, + }, + }, + { + Name: "set", + Description: "Set the state of a feature.", + Type: discordgo.ApplicationCommandOptionSubCommand, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "feature", + Description: "The feature to set the state of.", + Type: discordgo.ApplicationCommandOptionString, + Required: true, + Autocomplete: true, + }, + { + Name: "state", + Description: "The state to set the feature to.", + Type: discordgo.ApplicationCommandOptionBoolean, + Required: true, + }, + }, + }, + }, +} + +var ( + _ core.EventFunc[discordgo.InteractionCreate] = HandleFeatureCommand + _ core.EventFunc[discordgo.InteractionCreate] = HandleFeatureAutocomplete +) + +func HandleFeatureCommand(c context.Context, s *discordgo.Session, e *discordgo.InteractionCreate) error { + data := e.ApplicationCommandData() + + fs, ok := c.Value(FeatureServiceKey).(FeatureService) + if !ok { + return ErrFeatureServiceNotFound + } + + options := data.Options + switch options[0].Name { + case "get": + featureName := options[0].Options[0].StringValue() + identifier, err := core.ParseIdentifier(featureName) + if err != nil { + return err + } + + enabled, err := fs.GetFeature(context.Background(), identifier, e.GuildID) + if err != nil { + if errors.Is(err, ErrFeatureNotRegistered) { + response := &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Flags: discordgo.MessageFlagsEphemeral, + Embeds: []*discordgo.MessageEmbed{ + { + Title: "Feature not registered!", + Color: core.ColorError, + Description: fmt.Sprintf("The feature `%s` is not registered for this guild.", featureName), + }, + }, + }, + } + + if err := s.InteractionRespond(e.Interaction, response); err != nil { + return err + } + + return nil + } + + return err + } + + response := &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Flags: discordgo.MessageFlagsEphemeral, + Embeds: []*discordgo.MessageEmbed{ + { + Title: "Feature state", + Color: core.ColorInfo, + Description: fmt.Sprintf("The feature `%s` is currently %s.", featureName, map[bool]string{true: "enabled", false: "disabled"}[enabled]), + }, + }, + }, + } + + if err := s.InteractionRespond(e.Interaction, response); err != nil { + return err + } + case "set": + featureName := options[0].Options[0].StringValue() + identifier, err := core.ParseIdentifier(featureName) + if err != nil { + return err + } + + state := options[0].Options[1].BoolValue() + + if err := fs.SetFeature(context.Background(), identifier, e.GuildID, state); err != nil { + return err + } + + response := &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Flags: discordgo.MessageFlagsEphemeral, + Embeds: []*discordgo.MessageEmbed{ + { + Title: "Feature state updated!", + Color: core.ColorSuccess, + Description: fmt.Sprintf("The feature `%s` is now %s.", featureName, map[bool]string{true: "enabled", false: "disabled"}[state]), + }, + }, + }, + } + + if err := s.InteractionRespond(e.Interaction, response); err != nil { + return err + } + } + + return fmt.Errorf("unknown subcommand %q", options[0].Name) +} + +func HandleFeatureAutocomplete(c context.Context, s *discordgo.Session, e *discordgo.InteractionCreate) error { + fs, ok := c.Value(FeatureServiceKey).(FeatureService) + if !ok { + return ErrFeatureServiceNotFound + } + + features, err := fs.ListFeatures() + if err != nil { + return err + } + + choices := make([]*discordgo.ApplicationCommandOptionChoice, 0, len(features)) + for _, feature := range features { + choices = append(choices, &discordgo.ApplicationCommandOptionChoice{ + Name: feature.Identifier.String(), + Value: feature.Identifier.String(), + }) + } + + if err := s.InteractionRespond(e.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionApplicationCommandAutocompleteResult, + Data: &discordgo.InteractionResponseData{ + Choices: choices, + }, + }); err != nil { + return err + } + + return nil +} diff --git a/modules/debug/middlewares.go b/modules/debug/middlewares.go index 6db08ef..e5ffc09 100644 --- a/modules/debug/middlewares.go +++ b/modules/debug/middlewares.go @@ -192,6 +192,29 @@ func MidwareForCommand(command *discordgo.ApplicationCommand) core.MiddlewareFun // Register command return func(next core.EventFunc[discordgo.InteractionCreate]) core.EventFunc[discordgo.InteractionCreate] { return func(c context.Context, s *discordgo.Session, e *discordgo.InteractionCreate) error { + if e.Type != discordgo.InteractionApplicationCommand { + return nil + } + + data := e.ApplicationCommandData() + + // Ignore if not the correct command + if data.Name != command.Name { + return nil + } + + return next(c, s, e) + } + } +} + +func MidwareForAutocomplete(command *discordgo.ApplicationCommand) core.MiddlewareFunc[discordgo.InteractionCreate] { + return func(next core.EventFunc[discordgo.InteractionCreate]) core.EventFunc[discordgo.InteractionCreate] { + return func(c context.Context, s *discordgo.Session, e *discordgo.InteractionCreate) error { + if e.Type != discordgo.InteractionApplicationCommandAutocomplete { + return nil + } + data := e.ApplicationCommandData() // Ignore if not the correct command diff --git a/modules/debug/register.go b/modules/debug/register.go index 164688c..2b0790a 100644 --- a/modules/debug/register.go +++ b/modules/debug/register.go @@ -22,6 +22,22 @@ func RegisterModule(client *discordgo.Session, featureService FeatureService) { ) client.AddHandler(core.HandleEvent(featureSetupEvent)) + featureCommandIdent := core.NewIdentifier("debug", "commands/feature") + featureCommand := core.ApplyMiddlewares( + HandleFeatureCommand, + MidwareContextInject[discordgo.InteractionCreate](FeatureServiceKey, featureService), + MidwareForCommand(FeatureCommand), + MidwareErrorWrap(featureCommandIdent), + ) + client.AddHandler(core.HandleEvent(featureCommand)) + + featureCommandAutoComplete := core.ApplyMiddlewares( + HandleFeatureAutocomplete, + MidwareContextInject[discordgo.InteractionCreate](FeatureServiceKey, featureService), + MidwareForAutocomplete(FeatureCommand), + ) + client.AddHandler(core.HandleEvent(featureCommandAutoComplete)) + pingCommandIdent := core.NewIdentifier("debug", "commands/ping") pingCommand := core.ApplyMiddlewares( HandlePingCommand,