From d90653d3a02b1491665c8c4a4d5d992e1f05a1a7 Mon Sep 17 00:00:00 2001 From: Matthew Rawlings Date: Tue, 6 Feb 2024 21:01:16 -0800 Subject: [PATCH] refactor(discord): bot options --- cmd/discord/main.go | 15 ++- .../app/ui/adapters/primary/discord/bot.go | 116 ++++-------------- .../primary/discord/command/execution.go | 3 +- .../adapters/primary/discord/command/node.go | 17 ++- .../primary/discord/command/options.go | 10 ++ .../primary/discord/command/provider.go | 3 +- .../primary/discord/command/tailnet.go | 3 +- .../primary/discord/config/discord.go | 2 +- .../discord/handler/deprovision_node.go | 4 +- .../discord/handler/describe_execution.go | 4 +- .../primary/discord/handler/describe_node.go | 4 +- .../primary/discord/handler/provider.go | 4 +- .../primary/discord/handler/provision_node.go | 10 +- .../primary/discord/handler/start_node.go | 4 +- .../primary/discord/handler/stop_node.go | 4 +- .../primary/discord/handler/tailnet.go | 4 +- .../app/ui/adapters/primary/discord/option.go | 26 ++++ .../primary/discord/option/options.go | 10 -- .../ui/adapters/primary/discord/preflight.go | 76 ++++++++++++ 19 files changed, 175 insertions(+), 144 deletions(-) create mode 100644 internal/app/ui/adapters/primary/discord/command/options.go create mode 100644 internal/app/ui/adapters/primary/discord/option.go delete mode 100644 internal/app/ui/adapters/primary/discord/option/options.go create mode 100644 internal/app/ui/adapters/primary/discord/preflight.go diff --git a/cmd/discord/main.go b/cmd/discord/main.go index 5c9a006..3039e72 100644 --- a/cmd/discord/main.go +++ b/cmd/discord/main.go @@ -113,15 +113,18 @@ func main() { authorized = append(authorized, s) } - var guild *tempest.Snowflake - if cfg.Discord.GuildId != "" { - g, err := tempest.StringToSnowflake(cfg.Discord.GuildId) - panicOnErr(err) - guild = &g + var guilds []tempest.Snowflake + guilds = nil + if len(cfg.Discord.GuildIds) > 0 { + for _, id := range cfg.Discord.GuildIds { + s, err := tempest.StringToSnowflake(id) + panicOnErr(err) + guilds = append(guilds, s) + } } log.Info().Msg("Initing Discord Bot") - bot := discord.NewBot(lis, hdl, client, zerolog.DebugLevel, authorized, guild) + bot := discord.NewBot(lis, hdl, client, discord.WithAuthorizedUsers(authorized), discord.WithGuilds(guilds), discord.WithLogLevel(zerolog.DebugLevel)) go func() { log.Info().Msg("Starting Bot") diff --git a/internal/app/ui/adapters/primary/discord/bot.go b/internal/app/ui/adapters/primary/discord/bot.go index 3ceeefd..d6152ef 100644 --- a/internal/app/ui/adapters/primary/discord/bot.go +++ b/internal/app/ui/adapters/primary/discord/bot.go @@ -9,7 +9,6 @@ import ( "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/command" comctx "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/context" "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/handler" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" "github.com/awlsring/texit/internal/pkg/logger" "github.com/rs/zerolog" ) @@ -20,7 +19,7 @@ type Bot struct { hdl *handler.Handler lis net.Listener authorized []tempest.Snowflake - guildId *tempest.Snowflake + guildIds []tempest.Snowflake } func (b *Bot) Handler() *handler.Handler { @@ -35,17 +34,22 @@ func (b *Bot) LogLevel() zerolog.Level { return b.logLevel } -func NewBot(lis net.Listener, hdl *handler.Handler, tmpst *tempest.Client, lvl zerolog.Level, authorized []tempest.Snowflake, guild *tempest.Snowflake) *Bot { - return &Bot{ - logLevel: lvl, - lis: lis, - tmpst: tmpst, - hdl: hdl, - authorized: authorized, +func NewBot(lis net.Listener, hdl *handler.Handler, tmpst *tempest.Client, opts ...BotOption) *Bot { + b := &Bot{ + logLevel: zerolog.InfoLevel, + lis: lis, + tmpst: tmpst, + hdl: hdl, } + for _, opt := range opts { + opt(b) + } + + return b } func (b *Bot) registerCommands() error { + // TODO: This is gross and should be refactored if err := b.tmpst.RegisterCommand(command.NewServerHealthCommand(b.CommandPreflight(b.hdl.ServerHealthCheck))); err != nil { return err } @@ -61,11 +65,11 @@ func (b *Bot) registerCommands() error { if err := b.tmpst.RegisterCommand(command.NewProvisionNodeCommand(b.CommandPreflight(b.hdl.ProvisionNode), b.AutoCompletePreflight(b.logLevel, func(ctx *comctx.CommandContext) []tempest.Choice { field, input := ctx.GetFocusedValue() switch field { - case option.ProviderName: + case command.OptionProviderName: return b.hdl.ProviderNameAutoComplete(ctx, field, input.(string)) - case option.TailnetName: + case command.OptionTailnetName: return b.hdl.TailnetNameAutoComplete(ctx, field, input.(string)) - case option.ProviderLocation: + case command.OptionProviderLocation: return b.hdl.ProviderLocationAutoComplete(ctx, field, input.(string)) } return nil @@ -76,7 +80,7 @@ func (b *Bot) registerCommands() error { if err := b.tmpst.RegisterCommand(command.NewDeprovisionNodeCommand(b.CommandPreflight(b.hdl.DeprovisionNode), b.AutoCompletePreflight(b.logLevel, func(ctx *comctx.CommandContext) []tempest.Choice { field, input := ctx.GetFocusedValue() switch field { - case option.NodeId: + case command.OptionNodeId: return b.hdl.NodeIdAutoComplete(ctx, field, input.(string)) } return nil @@ -91,7 +95,7 @@ func (b *Bot) registerCommands() error { if err := b.tmpst.RegisterCommand(command.NewDescribeNodeCommand(b.CommandPreflight(b.hdl.DescribeNode), b.AutoCompletePreflight(b.logLevel, func(ctx *comctx.CommandContext) []tempest.Choice { field, input := ctx.GetFocusedValue() switch field { - case option.NodeId: + case command.OptionNodeId: return b.hdl.NodeIdAutoComplete(ctx, field, input.(string)) } return nil @@ -102,7 +106,7 @@ func (b *Bot) registerCommands() error { if err := b.tmpst.RegisterCommand(command.NewStartNodeCommand(b.CommandPreflight(b.hdl.StartNode), b.AutoCompletePreflight(b.logLevel, func(ctx *comctx.CommandContext) []tempest.Choice { field, input := ctx.GetFocusedValue() switch field { - case option.NodeId: + case command.OptionNodeId: return b.hdl.NodeIdAutoComplete(ctx, field, input.(string)) } return nil @@ -113,7 +117,7 @@ func (b *Bot) registerCommands() error { if err := b.tmpst.RegisterCommand(command.NewStopNodeCommand(b.CommandPreflight(b.hdl.StopNode), b.AutoCompletePreflight(b.logLevel, func(ctx *comctx.CommandContext) []tempest.Choice { field, input := ctx.GetFocusedValue() switch field { - case option.NodeId: + case command.OptionNodeId: return b.hdl.NodeIdAutoComplete(ctx, field, input.(string)) } return nil @@ -128,7 +132,7 @@ func (b *Bot) registerCommands() error { if err := b.tmpst.RegisterCommand(command.NewDescribeProviderCommand(b.CommandPreflight(b.hdl.DescribeProvider), b.AutoCompletePreflight(b.logLevel, func(ctx *comctx.CommandContext) []tempest.Choice { field, input := ctx.GetFocusedValue() switch field { - case option.ProviderName: + case command.OptionProviderName: return b.hdl.ProviderNameAutoComplete(ctx, field, input.(string)) } return nil @@ -143,7 +147,7 @@ func (b *Bot) registerCommands() error { if err := b.tmpst.RegisterCommand(command.NewDescribeTailnetCommand(b.CommandPreflight(b.hdl.DescribeTailnet), b.AutoCompletePreflight(b.logLevel, func(ctx *comctx.CommandContext) []tempest.Choice { field, input := ctx.GetFocusedValue() switch field { - case option.TailnetName: + case command.OptionTailnetName: return b.hdl.TailnetNameAutoComplete(ctx, field, input.(string)) } return nil @@ -154,74 +158,6 @@ func (b *Bot) registerCommands() error { return nil } -type CommandFunc func(*comctx.CommandContext) - -func (b *Bot) auth(ctx *comctx.CommandContext) bool { - log := logger.FromContext(ctx) - log.Debug().Msg("Checking authorization") - - if len(b.authorized) == 0 { - log.Debug().Msg("No authorized users, allowing") - return true - } - - for _, id := range b.authorized { - if ctx.Requester() == id { - log.Debug().Msg("User is authorized") - return true - } - if ctx.RequesterRoles() != nil { - for _, r := range ctx.RequesterRoles() { - if r == id { - log.Debug().Msg("User is in authorized role") - return true - } - } - } - } - - log.Debug().Msgf("User %d is not authorized", ctx.Requester()) - return false -} - -func (b *Bot) CommandPreflight(comFunc CommandFunc) func(itx *tempest.CommandInteraction) { - return func(itx *tempest.CommandInteraction) { - ctx, err := comctx.InitContext(b.tmpst, itx, b.logLevel) - if err != nil { - return - } - if !b.auth(ctx) { - _ = ctx.SendLinearReply("You are not authorized to use this command", true) - return - } - log := ctx.Logger() - log.Debug().Msg("Deferring command interaction") - if err := itx.Defer(true); err != nil { - log.Error().Err(err).Msg("Failed to defer command interaction") - if err = ctx.SendLinearReply("Command failed with an unknown error!", true); err != nil { - log.Error().Err(err).Msg("Failed to write bot response") - } - return - } - comFunc(ctx) - } -} - -type AutoCompleteFunc func(*comctx.CommandContext) []tempest.Choice - -func (b *Bot) AutoCompletePreflight(logLevel zerolog.Level, comFunc AutoCompleteFunc) func(itx tempest.CommandInteraction) []tempest.Choice { - return func(itx tempest.CommandInteraction) []tempest.Choice { - ctx, err := comctx.InitContext(b.tmpst, &itx, logLevel) - if err != nil { - return nil - } - if !b.auth(ctx) { - return nil - } - return comFunc(ctx) - } -} - func (b *Bot) Start(ctx context.Context) error { log := logger.FromContext(ctx) @@ -230,13 +166,7 @@ func (b *Bot) Start(ctx context.Context) error { return err } - var guilds []tempest.Snowflake - if b.guildId != nil { - guilds = append(guilds, *b.guildId) - } else { - guilds = nil - } - if err := b.tmpst.SyncCommands(guilds, nil, false); err != nil { + if err := b.tmpst.SyncCommands(b.guildIds, nil, false); err != nil { return err } diff --git a/internal/app/ui/adapters/primary/discord/command/execution.go b/internal/app/ui/adapters/primary/discord/command/execution.go index 4fea439..532805a 100644 --- a/internal/app/ui/adapters/primary/discord/command/execution.go +++ b/internal/app/ui/adapters/primary/discord/command/execution.go @@ -2,7 +2,6 @@ package command import ( tempest "github.com/Amatsagu/Tempest" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" ) func NewDescribeExecutionCommand(slash func(itx *tempest.CommandInteraction)) tempest.Command { @@ -13,7 +12,7 @@ func NewDescribeExecutionCommand(slash func(itx *tempest.CommandInteraction)) te Options: []tempest.CommandOption{ { Type: tempest.STRING_OPTION_TYPE, - Name: option.ExecutionId, + Name: OptionExecutionId, Description: "The ID of the execution to describe", Required: true, MinValue: 32, diff --git a/internal/app/ui/adapters/primary/discord/command/node.go b/internal/app/ui/adapters/primary/discord/command/node.go index b95a698..3329feb 100644 --- a/internal/app/ui/adapters/primary/discord/command/node.go +++ b/internal/app/ui/adapters/primary/discord/command/node.go @@ -2,7 +2,6 @@ package command import ( tempest "github.com/Amatsagu/Tempest" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" ) func NewProvisionNodeCommand(slash func(itx *tempest.CommandInteraction), auto func(itx tempest.CommandInteraction) []tempest.Choice) tempest.Command { @@ -13,7 +12,7 @@ func NewProvisionNodeCommand(slash func(itx *tempest.CommandInteraction), auto f Options: []tempest.CommandOption{ { Type: tempest.STRING_OPTION_TYPE, - Name: option.ProviderName, + Name: OptionProviderName, Description: "The name of the provider to create the exit node in", Required: true, MinValue: 1, @@ -21,7 +20,7 @@ func NewProvisionNodeCommand(slash func(itx *tempest.CommandInteraction), auto f }, { Type: tempest.STRING_OPTION_TYPE, - Name: option.TailnetName, + Name: OptionTailnetName, Description: "The tailnet to add the exit node to.", Required: true, MinValue: 1, @@ -29,7 +28,7 @@ func NewProvisionNodeCommand(slash func(itx *tempest.CommandInteraction), auto f }, { Type: tempest.STRING_OPTION_TYPE, - Name: option.ProviderLocation, + Name: OptionProviderLocation, Description: "The location of the provider to create the exit node on", Required: true, MinValue: 3, @@ -37,7 +36,7 @@ func NewProvisionNodeCommand(slash func(itx *tempest.CommandInteraction), auto f }, { Type: tempest.BOOLEAN_OPTION_TYPE, - Name: option.Ephemeral, + Name: OptionEphemeral, Description: "Whether the created exit node should be ephemeral or not. Defaults to false.", Required: false, }, @@ -55,7 +54,7 @@ func NewDeprovisionNodeCommand(slash func(itx *tempest.CommandInteraction), auto Options: []tempest.CommandOption{ { Type: tempest.STRING_OPTION_TYPE, - Name: option.NodeId, + Name: OptionNodeId, Description: "The ID of the node to delete", Required: true, MinValue: 1, @@ -84,7 +83,7 @@ func NewDescribeNodeCommand(slash func(itx *tempest.CommandInteraction), auto fu Options: []tempest.CommandOption{ { Type: tempest.STRING_OPTION_TYPE, - Name: option.NodeId, + Name: OptionNodeId, Description: "The ID of the node to describe", Required: true, MinValue: 1, @@ -104,7 +103,7 @@ func NewStartNodeCommand(slash func(itx *tempest.CommandInteraction), auto func( Options: []tempest.CommandOption{ { Type: tempest.STRING_OPTION_TYPE, - Name: option.NodeId, + Name: OptionNodeId, Description: "The ID of the node to start", Required: true, MinValue: 1, @@ -124,7 +123,7 @@ func NewStopNodeCommand(slash func(itx *tempest.CommandInteraction), auto func(i Options: []tempest.CommandOption{ { Type: tempest.STRING_OPTION_TYPE, - Name: option.NodeId, + Name: OptionNodeId, Description: "The ID of the node to stop", Required: true, MinValue: 1, diff --git a/internal/app/ui/adapters/primary/discord/command/options.go b/internal/app/ui/adapters/primary/discord/command/options.go new file mode 100644 index 0000000..dffeb95 --- /dev/null +++ b/internal/app/ui/adapters/primary/discord/command/options.go @@ -0,0 +1,10 @@ +package command + +const ( + OptionProviderName = "provider-name" + OptionProviderLocation = "provider-location" + OptionTailnetName = "tailnet-name" + OptionExecutionId = "execution-id" + OptionNodeId = "node-id" + OptionEphemeral = "ephemeral" +) diff --git a/internal/app/ui/adapters/primary/discord/command/provider.go b/internal/app/ui/adapters/primary/discord/command/provider.go index a4e2069..691cd7a 100644 --- a/internal/app/ui/adapters/primary/discord/command/provider.go +++ b/internal/app/ui/adapters/primary/discord/command/provider.go @@ -2,7 +2,6 @@ package command import ( tempest "github.com/Amatsagu/Tempest" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" ) func NewListProvidersCommand(slash func(itx *tempest.CommandInteraction)) tempest.Command { @@ -22,7 +21,7 @@ func NewDescribeProviderCommand(slash func(itx *tempest.CommandInteraction), aut Options: []tempest.CommandOption{ { Type: tempest.STRING_OPTION_TYPE, - Name: option.ProviderName, + Name: OptionProviderName, Description: "The name of the provider to describe", Required: true, MinValue: 1, diff --git a/internal/app/ui/adapters/primary/discord/command/tailnet.go b/internal/app/ui/adapters/primary/discord/command/tailnet.go index 2423943..c2e50e1 100644 --- a/internal/app/ui/adapters/primary/discord/command/tailnet.go +++ b/internal/app/ui/adapters/primary/discord/command/tailnet.go @@ -2,7 +2,6 @@ package command import ( tempest "github.com/Amatsagu/Tempest" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" ) func NewListTailnetsCommand(slash func(itx *tempest.CommandInteraction)) tempest.Command { @@ -22,7 +21,7 @@ func NewDescribeTailnetCommand(slash func(itx *tempest.CommandInteraction), auto Options: []tempest.CommandOption{ { Type: tempest.STRING_OPTION_TYPE, - Name: option.TailnetName, + Name: OptionTailnetName, Description: "The name of the tailnet to describe", Required: true, MinValue: 1, diff --git a/internal/app/ui/adapters/primary/discord/config/discord.go b/internal/app/ui/adapters/primary/discord/config/discord.go index d73af66..219f92f 100644 --- a/internal/app/ui/adapters/primary/discord/config/discord.go +++ b/internal/app/ui/adapters/primary/discord/config/discord.go @@ -11,7 +11,7 @@ var ( type DiscordBotConfig struct { ApplicationId string `yaml:"applicationId"` PublicKey string `yaml:"publicKey"` - GuildId string `yaml:"guildId"` + GuildIds []string `yaml:"guildIds"` Token string `yaml:"token"` Authorized []string `yaml:"authorized"` } diff --git a/internal/app/ui/adapters/primary/discord/handler/deprovision_node.go b/internal/app/ui/adapters/primary/discord/handler/deprovision_node.go index 9958dc8..bcf5b0e 100644 --- a/internal/app/ui/adapters/primary/discord/handler/deprovision_node.go +++ b/internal/app/ui/adapters/primary/discord/handler/deprovision_node.go @@ -5,8 +5,8 @@ import ( "strings" "time" + "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/command" "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/context" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" "github.com/awlsring/texit/internal/pkg/domain/node" "github.com/awlsring/texit/internal/pkg/domain/workflow" "github.com/awlsring/texit/internal/pkg/logger" @@ -22,7 +22,7 @@ func (h *Handler) DeprovisionNode(ctx *context.CommandContext) { log.Debug().Msg("Deprovisioning node") log.Debug().Msg("Getting node id") - nodeId, ok := ctx.GetOptionValue(option.NodeId) + nodeId, ok := ctx.GetOptionValue(command.OptionNodeId) if !ok { log.Error().Msg("Failed to get node id from interaction") _ = ctx.EditResponse("Please specify a node id.", true) diff --git a/internal/app/ui/adapters/primary/discord/handler/describe_execution.go b/internal/app/ui/adapters/primary/discord/handler/describe_execution.go index 35ac7f2..d00fbbc 100644 --- a/internal/app/ui/adapters/primary/discord/handler/describe_execution.go +++ b/internal/app/ui/adapters/primary/discord/handler/describe_execution.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" + "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/command" "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/context" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" "github.com/awlsring/texit/internal/pkg/domain/workflow" "github.com/awlsring/texit/internal/pkg/logger" ) @@ -14,7 +14,7 @@ func (h *Handler) DescribeExecution(ctx *context.CommandContext) { log := logger.FromContext(ctx) log.Debug().Msg("Getting execution") - exIdStr, ok := ctx.GetOptionValue(option.ExecutionId) + exIdStr, ok := ctx.GetOptionValue(command.OptionExecutionId) if !ok { log.Error().Msg("Failed to get execution ID from interaction") _ = ctx.EditResponse("Please specify an execution id to describe", true) diff --git a/internal/app/ui/adapters/primary/discord/handler/describe_node.go b/internal/app/ui/adapters/primary/discord/handler/describe_node.go index c22e2de..4497bb7 100644 --- a/internal/app/ui/adapters/primary/discord/handler/describe_node.go +++ b/internal/app/ui/adapters/primary/discord/handler/describe_node.go @@ -4,9 +4,9 @@ import ( "fmt" tempest "github.com/Amatsagu/Tempest" + "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/command" "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/context" "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/embed" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" "github.com/awlsring/texit/internal/pkg/domain/node" ) @@ -14,7 +14,7 @@ func (h *Handler) DescribeNode(ctx *context.CommandContext) { log := ctx.Logger() log.Debug().Msg("describing node") - nodeIdStr, ok := ctx.GetOptionValue(option.NodeId) + nodeIdStr, ok := ctx.GetOptionValue(command.OptionNodeId) if !ok { log.Error().Msg("Failed to get node ID from interaction") _ = ctx.EditResponse("Please specify an node id to describe", true) diff --git a/internal/app/ui/adapters/primary/discord/handler/provider.go b/internal/app/ui/adapters/primary/discord/handler/provider.go index 31df6e0..d7f6158 100644 --- a/internal/app/ui/adapters/primary/discord/handler/provider.go +++ b/internal/app/ui/adapters/primary/discord/handler/provider.go @@ -3,8 +3,8 @@ package handler import ( "fmt" + "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/command" "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/context" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" "github.com/awlsring/texit/internal/pkg/domain/provider" ) @@ -13,7 +13,7 @@ func (h *Handler) DescribeProvider(ctx *context.CommandContext) { log.Debug().Msg("Describing provider") log.Debug().Msg("Getting provider name") - providerName, ok := ctx.GetOptionValue(option.ProviderName) + providerName, ok := ctx.GetOptionValue(command.OptionProviderName) if !ok { log.Error().Msg("Failed to get provider name from interaction") _ = ctx.EditResponse("Please specify a provider name.", true) diff --git a/internal/app/ui/adapters/primary/discord/handler/provision_node.go b/internal/app/ui/adapters/primary/discord/handler/provision_node.go index 0d7400e..d4fa5a2 100644 --- a/internal/app/ui/adapters/primary/discord/handler/provision_node.go +++ b/internal/app/ui/adapters/primary/discord/handler/provision_node.go @@ -5,8 +5,8 @@ import ( "strings" "time" + "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/command" "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/context" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" "github.com/awlsring/texit/internal/pkg/domain/provider" "github.com/awlsring/texit/internal/pkg/domain/tailnet" "github.com/awlsring/texit/internal/pkg/domain/workflow" @@ -23,7 +23,7 @@ func (h *Handler) ProvisionNode(ctx *context.CommandContext) { log.Debug().Msg("Provisioning node") log.Debug().Msg("Getting provider name") - providerName, ok := ctx.GetOptionValue(option.ProviderName) + providerName, ok := ctx.GetOptionValue(command.OptionProviderName) if !ok { log.Error().Msg("Failed to get provider name from interaction") _ = ctx.EditResponse("Please specify a provider name.", true) @@ -37,7 +37,7 @@ func (h *Handler) ProvisionNode(ctx *context.CommandContext) { } log.Debug().Msg("Getting tailnet name") - tailnetName, ok := ctx.GetOptionValue(option.TailnetName) + tailnetName, ok := ctx.GetOptionValue(command.OptionTailnetName) if !ok { log.Error().Msg("Failed to get tailnet name from interaction") _ = ctx.EditResponse("Please specify a tailnet name.", true) @@ -51,7 +51,7 @@ func (h *Handler) ProvisionNode(ctx *context.CommandContext) { } log.Debug().Msg("Getting provider location") - providerLocation, ok := ctx.GetOptionValue(option.ProviderLocation) + providerLocation, ok := ctx.GetOptionValue(command.OptionProviderLocation) if !ok { log.Error().Msg("Failed to get provider location from interaction") _ = ctx.EditResponse("Please specify a provider location.", true) @@ -60,7 +60,7 @@ func (h *Handler) ProvisionNode(ctx *context.CommandContext) { pl := provider.Location(providerLocation.(string)) ephemeral := false - ephRaw, ok := ctx.GetOptionValue(option.Ephemeral) + ephRaw, ok := ctx.GetOptionValue(command.OptionEphemeral) if ok { ephemeral = ephRaw.(bool) } diff --git a/internal/app/ui/adapters/primary/discord/handler/start_node.go b/internal/app/ui/adapters/primary/discord/handler/start_node.go index bd00b2c..e4d3ebb 100644 --- a/internal/app/ui/adapters/primary/discord/handler/start_node.go +++ b/internal/app/ui/adapters/primary/discord/handler/start_node.go @@ -3,8 +3,8 @@ package handler import ( "fmt" + "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/command" "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/context" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" "github.com/awlsring/texit/internal/pkg/domain/node" ) @@ -12,7 +12,7 @@ func (h *Handler) StartNode(ctx *context.CommandContext) { log := ctx.Logger() log.Debug().Msg("starting node handler") - nodeIdStr, ok := ctx.GetOptionValue(option.NodeId) + nodeIdStr, ok := ctx.GetOptionValue(command.OptionNodeId) if !ok { log.Error().Msg("Failed to get node ID from interaction") _ = ctx.EditResponse("Please specify an node id to start", true) diff --git a/internal/app/ui/adapters/primary/discord/handler/stop_node.go b/internal/app/ui/adapters/primary/discord/handler/stop_node.go index d0c4cb2..7269959 100644 --- a/internal/app/ui/adapters/primary/discord/handler/stop_node.go +++ b/internal/app/ui/adapters/primary/discord/handler/stop_node.go @@ -3,8 +3,8 @@ package handler import ( "fmt" + "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/command" "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/context" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" "github.com/awlsring/texit/internal/pkg/domain/node" ) @@ -12,7 +12,7 @@ func (h *Handler) StopNode(ctx *context.CommandContext) { log := ctx.Logger() log.Debug().Msg("stopping node handler") - nodeIdStr, ok := ctx.GetOptionValue(option.NodeId) + nodeIdStr, ok := ctx.GetOptionValue(command.OptionNodeId) if !ok { log.Error().Msg("Failed to get node ID from interaction") _ = ctx.EditResponse("Please specify an node id to stop", true) diff --git a/internal/app/ui/adapters/primary/discord/handler/tailnet.go b/internal/app/ui/adapters/primary/discord/handler/tailnet.go index b70f762..b998da5 100644 --- a/internal/app/ui/adapters/primary/discord/handler/tailnet.go +++ b/internal/app/ui/adapters/primary/discord/handler/tailnet.go @@ -3,8 +3,8 @@ package handler import ( "fmt" + "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/command" "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/context" - "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/option" "github.com/awlsring/texit/internal/pkg/domain/tailnet" ) @@ -13,7 +13,7 @@ func (h *Handler) DescribeTailnet(ctx *context.CommandContext) { log.Debug().Msg("Describing tailnet") log.Debug().Msg("Getting tailnet name") - tailnetName, ok := ctx.GetOptionValue(option.TailnetName) + tailnetName, ok := ctx.GetOptionValue(command.OptionTailnetName) if !ok { log.Error().Msg("Failed to get tailnet name from interaction") _ = ctx.EditResponse("Please specify a tailnet name.", true) diff --git a/internal/app/ui/adapters/primary/discord/option.go b/internal/app/ui/adapters/primary/discord/option.go new file mode 100644 index 0000000..0e99d15 --- /dev/null +++ b/internal/app/ui/adapters/primary/discord/option.go @@ -0,0 +1,26 @@ +package discord + +import ( + tempest "github.com/Amatsagu/Tempest" + "github.com/rs/zerolog" +) + +type BotOption func(*Bot) + +func WithLogLevel(level zerolog.Level) BotOption { + return func(b *Bot) { + b.logLevel = level + } +} + +func WithAuthorizedUsers(users []tempest.Snowflake) BotOption { + return func(b *Bot) { + b.authorized = users + } +} + +func WithGuilds(guilds []tempest.Snowflake) BotOption { + return func(b *Bot) { + b.guildIds = guilds + } +} diff --git a/internal/app/ui/adapters/primary/discord/option/options.go b/internal/app/ui/adapters/primary/discord/option/options.go deleted file mode 100644 index 3cdc31d..0000000 --- a/internal/app/ui/adapters/primary/discord/option/options.go +++ /dev/null @@ -1,10 +0,0 @@ -package option - -const ( - ProviderName = "provider-name" - ProviderLocation = "provider-location" - TailnetName = "tailnet-name" - ExecutionId = "execution-id" - NodeId = "node-id" - Ephemeral = "ephemeral" -) diff --git a/internal/app/ui/adapters/primary/discord/preflight.go b/internal/app/ui/adapters/primary/discord/preflight.go new file mode 100644 index 0000000..75da446 --- /dev/null +++ b/internal/app/ui/adapters/primary/discord/preflight.go @@ -0,0 +1,76 @@ +package discord + +import ( + tempest "github.com/Amatsagu/Tempest" + comctx "github.com/awlsring/texit/internal/app/ui/adapters/primary/discord/context" + "github.com/awlsring/texit/internal/pkg/logger" + "github.com/rs/zerolog" +) + +func (b *Bot) auth(ctx *comctx.CommandContext) bool { + log := logger.FromContext(ctx) + log.Debug().Msg("Checking authorization") + + if len(b.authorized) == 0 { + log.Debug().Msg("No authorized users, allowing") + return true + } + + for _, id := range b.authorized { + if ctx.Requester() == id { + log.Debug().Msg("User is authorized") + return true + } + if ctx.RequesterRoles() != nil { + for _, r := range ctx.RequesterRoles() { + if r == id { + log.Debug().Msg("User is in authorized role") + return true + } + } + } + } + + log.Debug().Msgf("User %d is not authorized", ctx.Requester()) + return false +} + +type CommandFunc func(*comctx.CommandContext) + +func (b *Bot) CommandPreflight(comFunc CommandFunc) func(itx *tempest.CommandInteraction) { + return func(itx *tempest.CommandInteraction) { + ctx, err := comctx.InitContext(b.tmpst, itx, b.logLevel) + if err != nil { + return + } + if !b.auth(ctx) { + _ = ctx.SendLinearReply("You are not authorized to use this command", true) + return + } + log := ctx.Logger() + log.Debug().Msg("Deferring command interaction") + if err := itx.Defer(true); err != nil { + log.Error().Err(err).Msg("Failed to defer command interaction") + if err = ctx.SendLinearReply("Command failed with an unknown error!", true); err != nil { + log.Error().Err(err).Msg("Failed to write bot response") + } + return + } + comFunc(ctx) + } +} + +type AutoCompleteFunc func(*comctx.CommandContext) []tempest.Choice + +func (b *Bot) AutoCompletePreflight(logLevel zerolog.Level, comFunc AutoCompleteFunc) func(itx tempest.CommandInteraction) []tempest.Choice { + return func(itx tempest.CommandInteraction) []tempest.Choice { + ctx, err := comctx.InitContext(b.tmpst, &itx, logLevel) + if err != nil { + return nil + } + if !b.auth(ctx) { + return nil + } + return comFunc(ctx) + } +}