Skip to content

Commit

Permalink
added feature command
Browse files Browse the repository at this point in the history
  • Loading branch information
DownloadableFox committed Aug 4, 2024
1 parent f46d18b commit c3daca8
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 0 deletions.
13 changes: 13 additions & 0 deletions core/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
176 changes: 176 additions & 0 deletions modules/debug/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
23 changes: 23 additions & 0 deletions modules/debug/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions modules/debug/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit c3daca8

Please sign in to comment.