diff --git a/_examples/application_commands/gateway/example.go b/_examples/application_commands/gateway/example.go index 95328f8e..4ef13460 100644 --- a/_examples/application_commands/gateway/example.go +++ b/_examples/application_commands/gateway/example.go @@ -73,7 +73,7 @@ func main() { func commandListener(event *events.ApplicationCommandInteractionEvent) { data := event.SlashCommandInteractionData() if data.CommandName == "say" { - err := event.Create(discord.NewMessageCreateBuilder(). + err := event.CreateMessage(discord.NewMessageCreateBuilder(). SetContent(*data.Options.String("message")). Build(), ) diff --git a/_examples/application_commands/http/example.go b/_examples/application_commands/http/example.go index 59217036..b928a5fa 100644 --- a/_examples/application_commands/http/example.go +++ b/_examples/application_commands/http/example.go @@ -76,7 +76,7 @@ func main() { func commandListener(event *events.ApplicationCommandInteractionEvent) { data := event.SlashCommandInteractionData() if data.CommandName == "say" { - err := event.Create(discord.NewMessageCreateBuilder(). + err := event.CreateMessage(discord.NewMessageCreateBuilder(). SetContent(*data.Options.String("message")). Build(), ) diff --git a/_examples/components/example.go b/_examples/components/example.go index 84087597..e0350bea 100644 --- a/_examples/components/example.go +++ b/_examples/components/example.go @@ -40,7 +40,7 @@ func main() { }, OnComponentInteraction: func(event *events.ComponentInteractionEvent) { if event.ButtonInteractionData().CustomID == "danger" { - _ = event.Create(discord.NewMessageCreateBuilder().SetEphemeral(true).SetContent("Ey that was danger").Build()) + _ = event.CreateMessage(discord.NewMessageCreateBuilder().SetEphemeral(true).SetContent("Ey that was danger").Build()) } }, }), diff --git a/_examples/test/commands.go b/_examples/test/commands.go index 9f2f4f53..dee8b9e7 100644 --- a/_examples/test/commands.go +++ b/_examples/test/commands.go @@ -12,18 +12,6 @@ var commands = []discord.ApplicationCommandCreate{ Description: "return the guild & your locale", DefaultPermission: true, }, - discord.SlashCommandCreate{ - Name: "eval", - Description: "runs some go code", - DefaultPermission: true, - Options: []discord.ApplicationCommandOption{ - discord.ApplicationCommandOptionString{ - Name: "code", - Description: "the code to eval", - Required: true, - }, - }, - }, discord.SlashCommandCreate{ Name: "test", Description: "test", @@ -46,76 +34,6 @@ var commands = []discord.ApplicationCommandCreate{ }, }, }, - discord.SlashCommandCreate{ - Name: "attachment-test", - Description: "test attachment upload", - Options: []discord.ApplicationCommandOption{ - discord.ApplicationCommandOptionAttachment{ - Name: "test", - Description: "test attachment", - Required: true, - }, - }, - DefaultPermission: true, - }, - discord.SlashCommandCreate{ - Name: "addrole", - Description: "This command adds a role to a member", - DefaultPermission: true, - Options: []discord.ApplicationCommandOption{ - discord.ApplicationCommandOptionUser{ - Name: "member", - Description: "The member to add a role to", - Required: true, - }, - discord.ApplicationCommandOptionRole{ - Name: "role", - Description: "The role to add to a member", - Required: true, - }, - }, - }, - discord.SlashCommandCreate{ - Name: "removerole", - Description: "This command removes a role from a member", - DefaultPermission: true, - Options: []discord.ApplicationCommandOption{ - discord.ApplicationCommandOptionUser{ - Name: "member", - Description: "The member to removes a role from", - Required: true, - }, - discord.ApplicationCommandOptionRole{ - Name: "role", - Description: "The role to removes from a member", - Required: true, - }, - }, - }, - discord.SlashCommandCreate{ - Name: "root", - Description: "root command", - DefaultPermission: true, - Options: []discord.ApplicationCommandOption{ - discord.ApplicationCommandOptionSubCommandGroup{ - Name: "group", - Description: "group command", - Options: []discord.ApplicationCommandOptionSubCommand{ - { - Name: "sub", - Description: "sub command", - Options: []discord.ApplicationCommandOption{ - discord.ApplicationCommandOptionString{ - Name: "test", - Description: "test", - Required: true, - }, - }, - }, - }, - }, - }, - }, } func registerCommands(bot *core.Bot) { diff --git a/_examples/test/examplebot.go b/_examples/test/examplebot.go index e769a5d7..a01c39d0 100644 --- a/_examples/test/examplebot.go +++ b/_examples/test/examplebot.go @@ -16,17 +16,9 @@ import ( "github.com/DisgoOrg/snowflake" ) -const ( - red = 16711680 - orange = 16562691 - green = 65280 -) - var ( - token = os.Getenv("disgo_token") - guildID = snowflake.GetSnowflakeEnv("disgo_test_guild_id") - adminRoleID = snowflake.GetSnowflakeEnv("disgo_admin_role_id") - testRoleID = snowflake.GetSnowflakeEnv("disgo_test_role_id") + token = os.Getenv("disgo_token") + guildID = snowflake.GetSnowflakeEnv("disgo_test_guild_id") //go:embed gopher.png gopher []byte diff --git a/_examples/test/go.mod b/_examples/test/go.mod index 4cd22808..9cbc28d8 100644 --- a/_examples/test/go.mod +++ b/_examples/test/go.mod @@ -8,7 +8,6 @@ require ( github.com/DisgoOrg/disgo v0.5.6 github.com/DisgoOrg/log v1.1.3 github.com/DisgoOrg/snowflake v1.0.4 - github.com/PaesslerAG/gval v1.1.1 ) require ( diff --git a/_examples/test/go.sum b/_examples/test/go.sum index 4c746919..c0c51f06 100644 --- a/_examples/test/go.sum +++ b/_examples/test/go.sum @@ -2,10 +2,6 @@ github.com/DisgoOrg/log v1.1.3 h1:UN0ZfmPZ7yhCCDQ7iVvIedvZ2zEexNS5Ek76GBuffOE= github.com/DisgoOrg/log v1.1.3/go.mod h1:tSMofXaNhQNvzLRoL4tAiCG9yGY1ES5DLvduh7e9GRU= github.com/DisgoOrg/snowflake v1.0.4 h1:hgeXSaXRDVdoNJ0rfHXyVsgxQMzctP346S5WXlBmoOU= github.com/DisgoOrg/snowflake v1.0.4/go.mod h1:jIQVlVmElm2OGt6v52ITf/71ODaU09chUCflxt8+3yM= -github.com/PaesslerAG/gval v1.1.1 h1:4d7pprU9876+m3rc08X33UjGip8oV1kkm8Gh5GBuTss= -github.com/PaesslerAG/gval v1.1.1/go.mod h1:Fa8gfkCmUsELXgayr8sfL/sw+VzCVoa03dcOcR/if2w= -github.com/PaesslerAG/jsonpath v0.1.0 h1:gADYeifvlqK3R3i2cR5B4DGgxLXIPb3TRTH1mGi0jPI= -github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/_examples/test/listeners.go b/_examples/test/listeners.go index 1c90a8e1..f1f2b2dc 100644 --- a/_examples/test/listeners.go +++ b/_examples/test/listeners.go @@ -2,51 +2,91 @@ package main import ( "bytes" - "fmt" - "strconv" + "strings" "time" - "github.com/DisgoOrg/disgo/core/events" - "github.com/DisgoOrg/disgo/core" + "github.com/DisgoOrg/disgo/core/events" "github.com/DisgoOrg/disgo/discord" "github.com/DisgoOrg/log" - "github.com/PaesslerAG/gval" ) var listener = &events.ListenerAdapter{ OnGuildMessageCreate: messageListener, OnApplicationCommandInteraction: applicationCommandListener, OnComponentInteraction: componentListener, + OnModalSubmit: modalListener, +} + +func modalListener(event *events.ModalSubmitInteractionEvent) { + switch event.Data.CustomID { + case "test1": + value := event.Data.Components[0].Components()[0].(discord.TextInputComponent).Value + _ = event.CreateMessage(discord.MessageCreate{Content: value}) + + case "test2": + value := event.Data.Components[0].Components()[0].(discord.TextInputComponent).Value + _ = event.DeferCreateMessage(false) + go func() { + time.Sleep(time.Second * 5) + _, _ = event.UpdateOriginalMessage(discord.MessageUpdate{Content: &value}) + }() + + case "test3": + value := event.Data.Components[0].Components()[0].(discord.TextInputComponent).Value + _ = event.UpdateMessage(discord.MessageUpdate{Content: &value}) + + case "test4": + _ = event.DeferUpdateMessage() + } } func componentListener(event *events.ComponentInteractionEvent) { switch data := event.Data.(type) { - case *core.ButtonInteractionData: - switch data.CustomID { + case core.ButtonInteractionData: + ids := strings.Split(data.CustomID.String(), ":") + switch ids[0] { + case "modal": + _ = event.CreateModal(discord.ModalCreate{ + CustomID: discord.CustomID("test" + ids[1]), + Title: "Test" + ids[1] + " Modal", + Components: []discord.ContainerComponent{ + discord.ActionRowComponent{ + discord.TextInputComponent{ + CustomID: "test_input", + Style: discord.TextInputStyleShort, + Label: "qwq", + Required: true, + Placeholder: "test placeholder", + Value: "uwu", + }, + }, + }, + }) + case "test1": - _ = event.Create(discord.NewMessageCreateBuilder(). + _ = event.CreateMessage(discord.NewMessageCreateBuilder(). SetContent(data.CustomID.String()). Build(), ) case "test2": - _ = event.DeferCreate(false) + _ = event.DeferCreateMessage(false) case "test3": - _ = event.DeferUpdate() + _ = event.DeferUpdateMessage() case "test4": - _ = event.Update(discord.NewMessageUpdateBuilder(). + _ = event.UpdateMessage(discord.NewMessageUpdateBuilder(). SetContent(data.CustomID.String()). Build(), ) } - case *core.SelectMenuInteractionData: + case core.SelectMenuInteractionData: switch data.CustomID { case "test3": - if err := event.DeferUpdate(); err != nil { + if err := event.DeferUpdateMessage(); err != nil { log.Errorf("error sending interaction response: %s", err) } _, _ = event.CreateFollowupMessage(discord.NewMessageCreateBuilder(). @@ -62,7 +102,7 @@ func applicationCommandListener(event *events.ApplicationCommandInteractionEvent data := event.SlashCommandInteractionData() switch data.CommandName { case "locale": - err := event.Create(discord.NewMessageCreateBuilder(). + err := event.CreateMessage(discord.NewMessageCreateBuilder(). SetContentf("Guild Locale: %s\nLocale: %s", event.GuildLocale, event.Locale). Build(), ) @@ -70,57 +110,8 @@ func applicationCommandListener(event *events.ApplicationCommandInteractionEvent event.Bot().Logger.Error("error on sending response: ", err) } - case "eval": - go func() { - code := *data.Options.String("code") - embed := discord.NewEmbedBuilder(). - SetColor(orange). - AddField("Status", "...", true). - AddField("Time", "...", true). - AddField("Code", "```go\n"+code+"\n```", false). - AddField("Output", "```\n...\n```", false) - _ = event.Create(discord.NewMessageCreateBuilder().SetEmbeds(embed.Build()).Build()) - - start := time.Now() - output, err := gval.Evaluate(code, map[string]interface{}{ - "bot": event.Bot(), - "event": event, - }) - - elapsed := time.Since(start) - embed.SetField(1, "Time", strconv.Itoa(int(elapsed.Milliseconds()))+"ms", true) - - if err != nil { - _, err = event.UpdateResponse(discord.NewMessageUpdateBuilder(). - SetEmbeds(embed. - SetColor(red). - SetField(0, "Status", "Failed", true). - SetField(3, "Output", "```"+err.Error()+"```", false). - Build(), - ). - Build(), - ) - if err != nil { - log.Errorf("error sending interaction response: %s", err) - } - return - } - _, err = event.UpdateResponse(discord.NewMessageUpdateBuilder(). - SetEmbeds(embed. - SetColor(green). - SetField(0, "Status", "Success", true). - SetField(3, "Output", "```"+fmt.Sprintf("%+v", output)+"```", false). - Build(), - ). - Build(), - ) - if err != nil { - log.Errorf("error sending interaction response: %s", err) - } - }() - case "say": - _ = event.Create(discord.NewMessageCreateBuilder(). + _ = event.CreateMessage(discord.NewMessageCreateBuilder(). SetContent(*data.Options.String("message")). SetEphemeral(*data.Options.Bool("ephemeral")). ClearAllowedMentions(). @@ -128,46 +119,16 @@ func applicationCommandListener(event *events.ApplicationCommandInteractionEvent ) case "test": - go func() { - _ = event.DeferCreate(true) - members, err := event.Guild().RequestMembersWithQuery("", 0) - if err != nil { - _, _ = event.UpdateResponse(discord.NewMessageUpdateBuilder().SetContentf("failed to load members. error: %s", err).Build()) - return - } - _, _ = event.UpdateResponse(discord.NewMessageUpdateBuilder(). - SetContentf("loaded %d members", len(members)). - Build(), - ) - }() - - case "addrole": - user := data.Options.User("member") - role := data.Options.Role("role") - - if err := event.Bot().RestServices.GuildService().AddMemberRole(*event.GuildID, user.ID, role.ID); err == nil { - _ = event.Create(discord.NewMessageCreateBuilder().AddEmbeds( - discord.NewEmbedBuilder().SetColor(green).SetDescriptionf("Added %s to %s", role, user).Build(), - ).Build()) - } else { - _ = event.Create(discord.NewMessageCreateBuilder().AddEmbeds( - discord.NewEmbedBuilder().SetColor(red).SetDescriptionf("Failed to add %s to %s", role, user).Build(), - ).Build()) - } - - case "removerole": - user := data.Options.User("member") - role := data.Options.Role("role") - - if err := event.Bot().RestServices.GuildService().RemoveMemberRole(*event.GuildID, user.ID, role.ID); err == nil { - _ = event.Create(discord.NewMessageCreateBuilder().AddEmbeds( - discord.NewEmbedBuilder().SetColor(65280).SetDescriptionf("Removed %s from %s", role, user).Build(), - ).Build()) - } else { - _ = event.Create(discord.NewMessageCreateBuilder().AddEmbeds( - discord.NewEmbedBuilder().SetColor(16711680).SetDescriptionf("Failed to remove %s from %s", role, user).Build(), - ).Build()) - } + _ = event.CreateMessage(discord.NewMessageCreateBuilder(). + SetContent("test"). + AddActionRow( + discord.NewPrimaryButton("test1", "modal:1"), + discord.NewPrimaryButton("test2", "modal:2"), + discord.NewPrimaryButton("test3", "modal:3"), + discord.NewPrimaryButton("test4", "modal:4"), + ). + Build(), + ) } } diff --git a/core/application_command_interaction.go b/core/application_command_interaction.go index e02c2359..e9fe3f13 100644 --- a/core/application_command_interaction.go +++ b/core/application_command_interaction.go @@ -2,14 +2,17 @@ package core import ( "github.com/DisgoOrg/disgo/discord" + "github.com/DisgoOrg/disgo/rest" "github.com/DisgoOrg/snowflake" ) type ApplicationCommandInteractionFilter func(interaction *ApplicationCommandInteraction) bool +var _ Interaction = (*ApplicationCommandInteraction)(nil) + // ApplicationCommandInteraction represents a generic ApplicationCommandInteraction received from discord type ApplicationCommandInteraction struct { - *ReplyInteraction + CreateInteraction Data ApplicationCommandInteractionData } @@ -18,16 +21,20 @@ func (i ApplicationCommandInteraction) Type() discord.InteractionType { return discord.InteractionTypeApplicationCommand } -func (i ApplicationCommandInteraction) SlashCommandInteractionData() *SlashCommandInteractionData { - return i.Data.(*SlashCommandInteractionData) +func (i ApplicationCommandInteraction) SlashCommandInteractionData() SlashCommandInteractionData { + return i.Data.(SlashCommandInteractionData) +} + +func (i ApplicationCommandInteraction) UserCommandInteractionData() UserCommandInteractionData { + return i.Data.(UserCommandInteractionData) } -func (i ApplicationCommandInteraction) UserCommandInteractionData() *UserCommandInteractionData { - return i.Data.(*UserCommandInteractionData) +func (i ApplicationCommandInteraction) MessageCommandInteractionData() MessageCommandInteractionData { + return i.Data.(MessageCommandInteractionData) } -func (i ApplicationCommandInteraction) MessageCommandInteractionData() *MessageCommandInteractionData { - return i.Data.(*MessageCommandInteractionData) +func (i ApplicationCommandInteraction) CreateModal(modalCreate discord.ModalCreate, opts ...rest.RequestOpt) error { + return i.Respond(discord.InteractionCallbackTypeModal, modalCreate, opts...) } type ApplicationCommandInteractionData interface { diff --git a/core/autocomplete_interaction.go b/core/autocomplete_interaction.go index 8aebaeae..7704f63f 100644 --- a/core/autocomplete_interaction.go +++ b/core/autocomplete_interaction.go @@ -8,6 +8,8 @@ import ( type AutocompleteInteractionFilter func(autocompleteInteraction *AutocompleteInteraction) bool +var _ Interaction = (*AutocompleteInteraction)(nil) + type AutocompleteInteraction struct { *BaseInteraction Data AutocompleteInteractionData diff --git a/core/component_interaction.go b/core/component_interaction.go index b0c4fa49..fe7f53fb 100644 --- a/core/component_interaction.go +++ b/core/component_interaction.go @@ -7,9 +7,11 @@ import ( type ComponentInteractionFilter func(interaction *ComponentInteraction) bool +var _ Interaction = (*ComponentInteraction)(nil) + // ComponentInteraction represents a generic ComponentInteraction received from discord type ComponentInteraction struct { - *ReplyInteraction + CreateInteraction Data ComponentInteractionData Message *Message } @@ -19,18 +21,22 @@ func (i ComponentInteraction) Type() discord.InteractionType { return discord.InteractionTypeComponent } -func (i ComponentInteraction) ButtonInteractionData() *ButtonInteractionData { - return i.Data.(*ButtonInteractionData) +func (i ComponentInteraction) ButtonInteractionData() ButtonInteractionData { + return i.Data.(ButtonInteractionData) } -func (i ComponentInteraction) SelectMenuInteractionData() *SelectMenuInteractionData { - return i.Data.(*SelectMenuInteractionData) +func (i ComponentInteraction) SelectMenuInteractionData() SelectMenuInteractionData { + return i.Data.(SelectMenuInteractionData) } -func (i ComponentInteraction) Update(messageUpdate discord.MessageUpdate, opts ...rest.RequestOpt) error { +func (i ComponentInteraction) UpdateMessage(messageUpdate discord.MessageUpdate, opts ...rest.RequestOpt) error { return i.Respond(discord.InteractionCallbackTypeUpdateMessage, messageUpdate, opts...) } +func (i ComponentInteraction) DeferUpdateMessage(opts ...rest.RequestOpt) error { + return i.Respond(discord.InteractionCallbackTypeDeferredUpdateMessage, nil, opts...) +} + func (i ComponentInteraction) UpdateComponent(customID discord.CustomID, component discord.InteractiveComponent, opts ...rest.RequestOpt) error { containerComponents := make([]discord.ContainerComponent, len(i.Message.Components)) for ii := range i.Message.Components { @@ -44,11 +50,11 @@ func (i ComponentInteraction) UpdateComponent(customID discord.CustomID, compone } } - return i.Update(discord.NewMessageUpdateBuilder().SetContainerComponents(containerComponents...).Build(), opts...) + return i.UpdateMessage(discord.NewMessageUpdateBuilder().SetContainerComponents(containerComponents...).Build(), opts...) } -func (i ComponentInteraction) DeferUpdate(opts ...rest.RequestOpt) error { - return i.Respond(discord.InteractionCallbackTypeDeferredUpdateMessage, nil, opts...) +func (i ComponentInteraction) CreateModal(modalCreate discord.ModalCreate, opts ...rest.RequestOpt) error { + return i.Respond(discord.InteractionCallbackTypeModal, modalCreate, opts...) } type ComponentInteractionData interface { diff --git a/core/entity_builder.go b/core/entity_builder.go index 7574c772..c79778ad 100644 --- a/core/entity_builder.go +++ b/core/entity_builder.go @@ -101,7 +101,7 @@ func (b *entityBuilderImpl) CreateInteraction(interaction discord.Interaction, c var interactionData ApplicationCommandInteractionData switch d := i.Data.(type) { case discord.SlashCommandInteractionData: - data := &SlashCommandInteractionData{ + data := SlashCommandInteractionData{ SlashCommandInteractionData: d, Resolved: &SlashCommandResolved{ Users: map[snowflake.Snowflake]*User{}, @@ -209,7 +209,7 @@ func (b *entityBuilderImpl) CreateInteraction(interaction discord.Interaction, c interactionData = data case discord.UserCommandInteractionData: - data := &UserCommandInteractionData{ + data := UserCommandInteractionData{ UserCommandInteractionData: d, Resolved: &UserCommandResolved{ Users: map[snowflake.Snowflake]*User{}, @@ -228,7 +228,7 @@ func (b *entityBuilderImpl) CreateInteraction(interaction discord.Interaction, c interactionData = data case discord.MessageCommandInteractionData: - data := &MessageCommandInteractionData{ + data := MessageCommandInteractionData{ MessageCommandInteractionData: d, Resolved: &MessageCommandResolved{ Messages: map[snowflake.Snowflake]*Message{}, @@ -239,25 +239,27 @@ func (b *entityBuilderImpl) CreateInteraction(interaction discord.Interaction, c } interactionData = data } + baseInteraction := b.baseInteraction(i.BaseInteraction, c, updateCache) return &ApplicationCommandInteraction{ - ReplyInteraction: &ReplyInteraction{BaseInteraction: b.baseInteraction(i.BaseInteraction, c, updateCache)}, - Data: interactionData, + CreateInteraction: CreateInteraction{BaseInteraction: baseInteraction}, + Data: interactionData, } case discord.ComponentInteraction: + baseInteraction := b.baseInteraction(i.BaseInteraction, c, updateCache) componentInteraction := &ComponentInteraction{ - ReplyInteraction: &ReplyInteraction{BaseInteraction: b.baseInteraction(i.BaseInteraction, c, updateCache)}, - Message: b.CreateMessage(i.Message, updateCache), + CreateInteraction: CreateInteraction{BaseInteraction: baseInteraction}, + Message: b.CreateMessage(i.Message, updateCache), } switch d := i.Data.(type) { case discord.ButtonInteractionData: - componentInteraction.Data = &ButtonInteractionData{ + componentInteraction.Data = ButtonInteractionData{ ButtonInteractionData: d, interaction: componentInteraction, } case discord.SelectMenuInteractionData: - componentInteraction.Data = &SelectMenuInteractionData{ + componentInteraction.Data = SelectMenuInteractionData{ SelectMenuInteractionData: d, interaction: componentInteraction, } @@ -296,6 +298,15 @@ func (b *entityBuilderImpl) CreateInteraction(interaction discord.Interaction, c return autocompleteInteraction + case discord.ModalSubmitInteraction: + baseInteraction := b.baseInteraction(i.BaseInteraction, c, updateCache) + modalSubmitInteraction := &ModalSubmitInteraction{ + CreateInteraction: CreateInteraction{BaseInteraction: baseInteraction}, + Data: i.Data, + } + + return modalSubmitInteraction + default: b.Bot().Logger.Error("unknown interaction type %d received", interaction.Type()) return nil diff --git a/core/events/events_interactions.go b/core/events/events_interactions.go index 468ca5b1..d802e9cd 100644 --- a/core/events/events_interactions.go +++ b/core/events/events_interactions.go @@ -21,3 +21,8 @@ type AutocompleteInteractionEvent struct { *GenericEvent *core.AutocompleteInteraction } + +type ModalSubmitInteractionEvent struct { + *GenericEvent + *core.ModalSubmitInteraction +} diff --git a/core/events/listener_adapter.go b/core/events/listener_adapter.go index 0a74e38c..16f25484 100644 --- a/core/events/listener_adapter.go +++ b/core/events/listener_adapter.go @@ -120,6 +120,7 @@ type ListenerAdapter struct { OnApplicationCommandInteraction func(event *ApplicationCommandInteractionEvent) OnComponentInteraction func(event *ComponentInteractionEvent) OnAutocompleteInteraction func(event *AutocompleteInteractionEvent) + OnModalSubmit func(event *ModalSubmitInteractionEvent) // Message Events OnMessageCreate func(event *MessageCreateEvent) @@ -498,6 +499,10 @@ func (l ListenerAdapter) OnEvent(event core.Event) { if listener := l.OnAutocompleteInteraction; listener != nil { listener(e) } + case *ModalSubmitInteractionEvent: + if listener := l.OnModalSubmit; listener != nil { + listener(e) + } // Message Events case *MessageCreateEvent: diff --git a/core/handlers/gateway_handler_interaction_create.go b/core/handlers/gateway_handler_interaction_create.go index 7bab3819..428426a3 100644 --- a/core/handlers/gateway_handler_interaction_create.go +++ b/core/handlers/gateway_handler_interaction_create.go @@ -53,6 +53,12 @@ func HandleInteraction(bot *core.Bot, sequenceNumber discord.GatewaySequence, c AutocompleteInteraction: i, }) + case *core.ModalSubmitInteraction: + bot.EventManager.Dispatch(&events.ModalSubmitInteractionEvent{ + GenericEvent: genericEvent, + ModalSubmitInteraction: i, + }) + default: bot.Logger.Errorf("unknown interaction with type %d received", interaction.Type()) } diff --git a/core/interaction.go b/core/interaction.go index 389c281c..c52e4728 100644 --- a/core/interaction.go +++ b/core/interaction.go @@ -1,6 +1,8 @@ package core import ( + "time" + "github.com/DisgoOrg/disgo/discord" "github.com/DisgoOrg/disgo/rest" "github.com/DisgoOrg/snowflake" @@ -37,6 +39,10 @@ func (i *BaseInteraction) Respond(callbackType discord.InteractionCallbackType, } i.Acknowledged = true + if time.Now().After(i.ID.Time().Add(3 * time.Second)) { + return discord.ErrInteractionExpired + } + response := discord.InteractionResponse{ Type: callbackType, Data: callbackData, @@ -66,11 +72,35 @@ func (i *BaseInteraction) Channel() MessageChannel { return nil } -type ReplyInteraction struct { +type UpdateInteraction struct { *BaseInteraction } -func (i ReplyInteraction) GetResponse(opts ...rest.RequestOpt) (*Message, error) { +func (i UpdateInteraction) UpdateMessage(messageUpdate discord.MessageUpdate, opts ...rest.RequestOpt) error { + return i.Respond(discord.InteractionCallbackTypeUpdateMessage, messageUpdate, opts...) +} + +func (i UpdateInteraction) DeferUpdateMessage(opts ...rest.RequestOpt) error { + return i.Respond(discord.InteractionCallbackTypeDeferredUpdateMessage, nil, opts...) +} + +type CreateInteraction struct { + *BaseInteraction +} + +func (i CreateInteraction) CreateMessage(messageCreate discord.MessageCreate, opts ...rest.RequestOpt) error { + return i.Respond(discord.InteractionCallbackTypeChannelMessageWithSource, messageCreate, opts...) +} + +func (i CreateInteraction) DeferCreateMessage(ephemeral bool, opts ...rest.RequestOpt) error { + var data discord.InteractionCallbackData + if ephemeral { + data = discord.MessageCreate{Flags: discord.MessageFlagEphemeral} + } + return i.Respond(discord.InteractionCallbackTypeDeferredChannelMessageWithSource, data, opts...) +} + +func (i CreateInteraction) GetOriginalMessage(opts ...rest.RequestOpt) (*Message, error) { message, err := i.Bot.RestServices.InteractionService().GetInteractionResponse(i.ApplicationID, i.Token, opts...) if err != nil { return nil, err @@ -78,7 +108,7 @@ func (i ReplyInteraction) GetResponse(opts ...rest.RequestOpt) (*Message, error) return i.Bot.EntityBuilder.CreateMessage(*message, CacheStrategyNoWs), nil } -func (i ReplyInteraction) UpdateResponse(messageUpdate discord.MessageUpdate, opts ...rest.RequestOpt) (*Message, error) { +func (i CreateInteraction) UpdateOriginalMessage(messageUpdate discord.MessageUpdate, opts ...rest.RequestOpt) (*Message, error) { message, err := i.Bot.RestServices.InteractionService().UpdateInteractionResponse(i.ApplicationID, i.Token, messageUpdate, opts...) if err != nil { return nil, err @@ -86,23 +116,11 @@ func (i ReplyInteraction) UpdateResponse(messageUpdate discord.MessageUpdate, op return i.Bot.EntityBuilder.CreateMessage(*message, CacheStrategyNoWs), nil } -func (i ReplyInteraction) DeleteResponse(opts ...rest.RequestOpt) error { +func (i CreateInteraction) DeleteOriginalMessage(opts ...rest.RequestOpt) error { return i.Bot.RestServices.InteractionService().DeleteInteractionResponse(i.ApplicationID, i.Token, opts...) } -func (i ReplyInteraction) Create(messageCreate discord.MessageCreate, opts ...rest.RequestOpt) error { - return i.Respond(discord.InteractionCallbackTypeChannelMessageWithSource, messageCreate, opts...) -} - -func (i ReplyInteraction) DeferCreate(ephemeral bool, opts ...rest.RequestOpt) error { - var data discord.InteractionCallbackData - if ephemeral { - data = discord.MessageCreate{Flags: discord.MessageFlagEphemeral} - } - return i.Respond(discord.InteractionCallbackTypeDeferredChannelMessageWithSource, data, opts...) -} - -func (i ReplyInteraction) GetFollowupMessage(messageID snowflake.Snowflake, opts ...rest.RequestOpt) (*Message, error) { +func (i CreateInteraction) GetFollowupMessage(messageID snowflake.Snowflake, opts ...rest.RequestOpt) (*Message, error) { message, err := i.Bot.RestServices.InteractionService().GetFollowupMessage(i.ApplicationID, i.Token, messageID, opts...) if err != nil { return nil, err @@ -110,7 +128,7 @@ func (i ReplyInteraction) GetFollowupMessage(messageID snowflake.Snowflake, opts return i.Bot.EntityBuilder.CreateMessage(*message, CacheStrategyNoWs), nil } -func (i ReplyInteraction) CreateFollowupMessage(messageCreate discord.MessageCreate, opts ...rest.RequestOpt) (*Message, error) { +func (i CreateInteraction) CreateFollowupMessage(messageCreate discord.MessageCreate, opts ...rest.RequestOpt) (*Message, error) { message, err := i.Bot.RestServices.InteractionService().CreateFollowupMessage(i.ApplicationID, i.Token, messageCreate, opts...) if err != nil { return nil, err @@ -118,7 +136,7 @@ func (i ReplyInteraction) CreateFollowupMessage(messageCreate discord.MessageCre return i.Bot.EntityBuilder.CreateMessage(*message, CacheStrategyNoWs), nil } -func (i ReplyInteraction) UpdateFollowupMessage(messageID snowflake.Snowflake, messageUpdate discord.MessageUpdate, opts ...rest.RequestOpt) (*Message, error) { +func (i CreateInteraction) UpdateFollowupMessage(messageID snowflake.Snowflake, messageUpdate discord.MessageUpdate, opts ...rest.RequestOpt) (*Message, error) { message, err := i.Bot.RestServices.InteractionService().UpdateFollowupMessage(i.ApplicationID, i.Token, messageID, messageUpdate, opts...) if err != nil { return nil, err @@ -126,6 +144,6 @@ func (i ReplyInteraction) UpdateFollowupMessage(messageID snowflake.Snowflake, m return i.Bot.EntityBuilder.CreateMessage(*message, CacheStrategyNoWs), nil } -func (i ReplyInteraction) DeleteFollowupMessage(messageID snowflake.Snowflake, opts ...rest.RequestOpt) error { +func (i CreateInteraction) DeleteFollowupMessage(messageID snowflake.Snowflake, opts ...rest.RequestOpt) error { return i.Bot.RestServices.InteractionService().DeleteFollowupMessage(i.ApplicationID, i.Token, messageID, opts...) } diff --git a/core/modal_submit_interaction.go b/core/modal_submit_interaction.go new file mode 100644 index 00000000..a815c162 --- /dev/null +++ b/core/modal_submit_interaction.go @@ -0,0 +1,28 @@ +package core + +import ( + "github.com/DisgoOrg/disgo/discord" + "github.com/DisgoOrg/disgo/rest" +) + +type ModalSubmitInteractionFilter func(ModalSubmitInteraction *ModalSubmitInteraction) bool + +var _ Interaction = (*ModalSubmitInteraction)(nil) + +type ModalSubmitInteraction struct { + CreateInteraction + Data discord.ModalSubmitInteractionData +} + +func (i ModalSubmitInteraction) interaction() {} +func (i ModalSubmitInteraction) Type() discord.InteractionType { + return discord.InteractionTypeModalSubmit +} + +func (i ModalSubmitInteraction) UpdateMessage(messageUpdate discord.MessageUpdate, opts ...rest.RequestOpt) error { + return i.Respond(discord.InteractionCallbackTypeUpdateMessage, messageUpdate, opts...) +} + +func (i ModalSubmitInteraction) DeferUpdateMessage(opts ...rest.RequestOpt) error { + return i.Respond(discord.InteractionCallbackTypeDeferredUpdateMessage, nil, opts...) +} diff --git a/discord/action_row.go b/discord/action_row.go deleted file mode 100644 index 49e8f326..00000000 --- a/discord/action_row.go +++ /dev/null @@ -1,101 +0,0 @@ -package discord - -import "github.com/DisgoOrg/disgo/json" - -var ( - _ Component = (*ActionRowComponent)(nil) - _ ContainerComponent = (*ActionRowComponent)(nil) -) - -//goland:noinspection GoUnusedExportedFunction -func NewActionRow(components ...InteractiveComponent) ActionRowComponent { - return components -} - -type ActionRowComponent []InteractiveComponent - -func (c ActionRowComponent) MarshalJSON() ([]byte, error) { - return json.Marshal(struct { - Type ComponentType `json:"type"` - Components []InteractiveComponent `json:"components"` - }{ - Type: c.Type(), - Components: c, - }) -} - -func (c *ActionRowComponent) UnmarshalJSON(data []byte) error { - var actionRow struct { - Components []UnmarshalComponent `json:"components"` - } - - if err := json.Unmarshal(data, &actionRow); err != nil { - return err - } - - if len(actionRow.Components) > 0 { - *c = make([]InteractiveComponent, len(actionRow.Components)) - for i, component := range actionRow.Components { - (*c)[i] = component.Component.(InteractiveComponent) - } - } - - return nil -} - -func (c ActionRowComponent) Type() ComponentType { - return ComponentTypeActionRow -} - -func (c ActionRowComponent) component() {} -func (c ActionRowComponent) containerComponent() {} - -func (c ActionRowComponent) Components() []InteractiveComponent { - return c -} - -// Buttons returns all ButtonComponent(s) in the ActionRowComponent -func (c ActionRowComponent) Buttons() []ButtonComponent { - var buttons []ButtonComponent - for i := range c { - if button, ok := c[i].(ButtonComponent); ok { - buttons = append(buttons, button) - } - } - return buttons -} - -// SelectMenus returns all SelectMenuComponent(s) in the ActionRowComponent -func (c ActionRowComponent) SelectMenus() []SelectMenuComponent { - var selectMenus []SelectMenuComponent - for i := range c { - if selectMenu, ok := c[i].(SelectMenuComponent); ok { - selectMenus = append(selectMenus, selectMenu) - } - } - return selectMenus -} - -// UpdateComponent returns a new ActionRowComponent with the Component which has the customID replaced -func (c ActionRowComponent) UpdateComponent(customID CustomID, component InteractiveComponent) ActionRowComponent { - for i, cc := range c { - if cc.ID() == customID { - c[i] = component - return c - } - } - return c -} - -// AddComponents returns a new ActionRowComponent with the provided Component(s) added -func (c ActionRowComponent) AddComponents(components ...InteractiveComponent) ActionRowComponent { - return append(c, components...) -} - -// RemoveComponent returns a new ActionRowComponent with the provided Component at the index removed -func (c ActionRowComponent) RemoveComponent(index int) ActionRowComponent { - if len(c) > index { - return append(c[:index], c[index+1:]...) - } - return c -} diff --git a/discord/application_command_autocomplete_result.go b/discord/application_command_autocomplete_result.go deleted file mode 100644 index 128aa029..00000000 --- a/discord/application_command_autocomplete_result.go +++ /dev/null @@ -1,32 +0,0 @@ -package discord - -type AutocompleteResult struct { - Choices []AutocompleteChoice `json:"choices"` -} - -func (AutocompleteResult) interactionCallbackData() {} - -type AutocompleteChoice interface { - autoCompleteChoice() -} - -type AutocompleteChoiceString struct { - Name string `json:"name"` - Value string `json:"value"` -} - -func (AutocompleteChoiceString) autoCompleteChoice() {} - -type AutocompleteChoiceInt struct { - Name string `json:"name"` - Value int `json:"value"` -} - -func (AutocompleteChoiceInt) autoCompleteChoice() {} - -type AutocompleteChoiceFloat struct { - Name string `json:"name"` - Value float64 `json:"value"` -} - -func (AutocompleteChoiceFloat) autoCompleteChoice() {} diff --git a/discord/button.go b/discord/button.go deleted file mode 100644 index bab51f16..00000000 --- a/discord/button.go +++ /dev/null @@ -1,140 +0,0 @@ -package discord - -import "github.com/DisgoOrg/disgo/json" - -// ButtonStyle defines how the ButtonComponent looks like (https://discord.com/assets/7bb017ce52cfd6575e21c058feb3883b.png) -type ButtonStyle int - -// Supported ButtonStyle(s) -const ( - ButtonStylePrimary = iota + 1 - ButtonStyleSecondary - ButtonStyleSuccess - ButtonStyleDanger - ButtonStyleLink -) - -// NewButton creates a new ButtonComponent with the provided parameters. Link ButtonComponent(s) need a URL and other ButtonComponent(s) need a customID -//goland:noinspection GoUnusedExportedFunction -func NewButton(style ButtonStyle, label string, customID CustomID, url string) ButtonComponent { - return ButtonComponent{ - Style: style, - CustomID: customID, - URL: url, - Label: label, - } -} - -// NewPrimaryButton creates a new ButtonComponent with ButtonStylePrimary & the provided parameters -//goland:noinspection GoUnusedExportedFunction -func NewPrimaryButton(label string, customID CustomID) ButtonComponent { - return NewButton(ButtonStylePrimary, label, customID, "") -} - -// NewSecondaryButton creates a new ButtonComponent with ButtonStyleSecondary & the provided parameters -//goland:noinspection GoUnusedExportedFunction -func NewSecondaryButton(label string, customID CustomID) ButtonComponent { - return NewButton(ButtonStyleSecondary, label, customID, "") -} - -// NewSuccessButton creates a new ButtonComponent with ButtonStyleSuccess & the provided parameters -//goland:noinspection GoUnusedExportedFunction -func NewSuccessButton(label string, customID CustomID) ButtonComponent { - return NewButton(ButtonStyleSuccess, label, customID, "") -} - -// NewDangerButton creates a new ButtonComponent with ButtonStyleDanger & the provided parameters -//goland:noinspection GoUnusedExportedFunction -func NewDangerButton(label string, customID CustomID) ButtonComponent { - return NewButton(ButtonStyleDanger, label, customID, "") -} - -// NewLinkButton creates a new link ButtonComponent with ButtonStyleLink & the provided parameters -//goland:noinspection GoUnusedExportedFunction -func NewLinkButton(label string, url string) ButtonComponent { - return NewButton(ButtonStyleLink, label, "", url) -} - -var ( - _ Component = (*ButtonComponent)(nil) - _ InteractiveComponent = (*ButtonComponent)(nil) -) - -type ButtonComponent struct { - Style ButtonStyle `json:"style"` - Label string `json:"label,omitempty"` - Emoji *ComponentEmoji `json:"emoji,omitempty"` - CustomID CustomID `json:"custom_id,omitempty"` - URL string `json:"url,omitempty"` - Disabled bool `json:"disabled,omitempty"` -} - -func (c ButtonComponent) MarshalJSON() ([]byte, error) { - type buttonComponent ButtonComponent - return json.Marshal(struct { - Type ComponentType `json:"type"` - buttonComponent - }{ - Type: c.Type(), - buttonComponent: buttonComponent(c), - }) -} - -func (c ButtonComponent) Type() ComponentType { - return ComponentTypeButton -} - -func (c ButtonComponent) ID() CustomID { - return c.CustomID -} - -func (c ButtonComponent) component() {} -func (c ButtonComponent) interactiveComponent() {} - -// WithStyle returns a new ButtonComponent with the provided style -func (c ButtonComponent) WithStyle(style ButtonStyle) ButtonComponent { - c.Style = style - return c -} - -// WithLabel returns a new ButtonComponent with the provided label -func (c ButtonComponent) WithLabel(label string) ButtonComponent { - c.Label = label - return c -} - -// WithEmoji returns a new ButtonComponent with the provided Emoji -func (c ButtonComponent) WithEmoji(emoji ComponentEmoji) ButtonComponent { - c.Emoji = &emoji - return c -} - -// WithCustomID returns a new ButtonComponent with the provided custom id -func (c ButtonComponent) WithCustomID(customID CustomID) ButtonComponent { - c.CustomID = customID - return c -} - -// WithURL returns a new ButtonComponent with the provided URL -func (c ButtonComponent) WithURL(url string) ButtonComponent { - c.URL = url - return c -} - -// AsEnabled returns a new ButtonComponent but enabled -func (c ButtonComponent) AsEnabled() ButtonComponent { - c.Disabled = false - return c -} - -// AsDisabled returns a new ButtonComponent but disabled -func (c ButtonComponent) AsDisabled() ButtonComponent { - c.Disabled = true - return c -} - -// WithDisabled returns a new ButtonComponent but disabled/enabled -func (c ButtonComponent) WithDisabled(disabled bool) ButtonComponent { - c.Disabled = disabled - return c -} diff --git a/discord/component.go b/discord/component.go index ffd5a90b..1db38fb8 100644 --- a/discord/component.go +++ b/discord/component.go @@ -16,6 +16,7 @@ const ( ComponentTypeActionRow = iota + 1 ComponentTypeButton ComponentTypeSelectMenu + ComponentTypeTextInput ) type CustomID string @@ -76,6 +77,11 @@ func (u *UnmarshalComponent) UnmarshalJSON(data []byte) error { err = json.Unmarshal(data, &v) component = v + case ComponentTypeTextInput: + v := TextInputComponent{} + err = json.Unmarshal(data, &v) + component = v + default: err = fmt.Errorf("unkown component with type %d received", cType.Type) } @@ -92,3 +98,451 @@ type ComponentEmoji struct { Name string `json:"name,omitempty"` Animated bool `json:"animated,omitempty"` } + +var ( + _ Component = (*ActionRowComponent)(nil) + _ ContainerComponent = (*ActionRowComponent)(nil) +) + +//goland:noinspection GoUnusedExportedFunction +func NewActionRow(components ...InteractiveComponent) ActionRowComponent { + return components +} + +type ActionRowComponent []InteractiveComponent + +func (c ActionRowComponent) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type ComponentType `json:"type"` + Components []InteractiveComponent `json:"components"` + }{ + Type: c.Type(), + Components: c, + }) +} + +func (c *ActionRowComponent) UnmarshalJSON(data []byte) error { + var actionRow struct { + Components []UnmarshalComponent `json:"components"` + } + + if err := json.Unmarshal(data, &actionRow); err != nil { + return err + } + + if len(actionRow.Components) > 0 { + *c = make([]InteractiveComponent, len(actionRow.Components)) + for i, component := range actionRow.Components { + (*c)[i] = component.Component.(InteractiveComponent) + } + } + + return nil +} + +func (c ActionRowComponent) Type() ComponentType { + return ComponentTypeActionRow +} + +func (c ActionRowComponent) component() {} +func (c ActionRowComponent) containerComponent() {} + +func (c ActionRowComponent) Components() []InteractiveComponent { + return c +} + +// Buttons returns all ButtonComponent(s) in the ActionRowComponent +func (c ActionRowComponent) Buttons() []ButtonComponent { + var buttons []ButtonComponent + for i := range c { + if button, ok := c[i].(ButtonComponent); ok { + buttons = append(buttons, button) + } + } + return buttons +} + +// SelectMenus returns all SelectMenuComponent(s) in the ActionRowComponent +func (c ActionRowComponent) SelectMenus() []SelectMenuComponent { + var selectMenus []SelectMenuComponent + for i := range c { + if selectMenu, ok := c[i].(SelectMenuComponent); ok { + selectMenus = append(selectMenus, selectMenu) + } + } + return selectMenus +} + +// UpdateComponent returns a new ActionRowComponent with the Component which has the customID replaced +func (c ActionRowComponent) UpdateComponent(customID CustomID, component InteractiveComponent) ActionRowComponent { + for i, cc := range c { + if cc.ID() == customID { + c[i] = component + return c + } + } + return c +} + +// AddComponents returns a new ActionRowComponent with the provided Component(s) added +func (c ActionRowComponent) AddComponents(components ...InteractiveComponent) ActionRowComponent { + return append(c, components...) +} + +// RemoveComponent returns a new ActionRowComponent with the provided Component at the index removed +func (c ActionRowComponent) RemoveComponent(index int) ActionRowComponent { + if len(c) > index { + return append(c[:index], c[index+1:]...) + } + return c +} + +// ButtonStyle defines how the ButtonComponent looks like (https://discord.com/assets/7bb017ce52cfd6575e21c058feb3883b.png) +type ButtonStyle int + +// Supported ButtonStyle(s) +const ( + ButtonStylePrimary = iota + 1 + ButtonStyleSecondary + ButtonStyleSuccess + ButtonStyleDanger + ButtonStyleLink +) + +// NewButton creates a new ButtonComponent with the provided parameters. Link ButtonComponent(s) need a URL and other ButtonComponent(s) need a customID +//goland:noinspection GoUnusedExportedFunction +func NewButton(style ButtonStyle, label string, customID CustomID, url string) ButtonComponent { + return ButtonComponent{ + Style: style, + CustomID: customID, + URL: url, + Label: label, + } +} + +// NewPrimaryButton creates a new ButtonComponent with ButtonStylePrimary & the provided parameters +//goland:noinspection GoUnusedExportedFunction +func NewPrimaryButton(label string, customID CustomID) ButtonComponent { + return NewButton(ButtonStylePrimary, label, customID, "") +} + +// NewSecondaryButton creates a new ButtonComponent with ButtonStyleSecondary & the provided parameters +//goland:noinspection GoUnusedExportedFunction +func NewSecondaryButton(label string, customID CustomID) ButtonComponent { + return NewButton(ButtonStyleSecondary, label, customID, "") +} + +// NewSuccessButton creates a new ButtonComponent with ButtonStyleSuccess & the provided parameters +//goland:noinspection GoUnusedExportedFunction +func NewSuccessButton(label string, customID CustomID) ButtonComponent { + return NewButton(ButtonStyleSuccess, label, customID, "") +} + +// NewDangerButton creates a new ButtonComponent with ButtonStyleDanger & the provided parameters +//goland:noinspection GoUnusedExportedFunction +func NewDangerButton(label string, customID CustomID) ButtonComponent { + return NewButton(ButtonStyleDanger, label, customID, "") +} + +// NewLinkButton creates a new link ButtonComponent with ButtonStyleLink & the provided parameters +//goland:noinspection GoUnusedExportedFunction +func NewLinkButton(label string, url string) ButtonComponent { + return NewButton(ButtonStyleLink, label, "", url) +} + +var ( + _ Component = (*ButtonComponent)(nil) + _ InteractiveComponent = (*ButtonComponent)(nil) +) + +type ButtonComponent struct { + Style ButtonStyle `json:"style"` + Label string `json:"label,omitempty"` + Emoji *ComponentEmoji `json:"emoji,omitempty"` + CustomID CustomID `json:"custom_id,omitempty"` + URL string `json:"url,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +func (c ButtonComponent) MarshalJSON() ([]byte, error) { + type buttonComponent ButtonComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + buttonComponent + }{ + Type: c.Type(), + buttonComponent: buttonComponent(c), + }) +} + +func (c ButtonComponent) Type() ComponentType { + return ComponentTypeButton +} + +func (c ButtonComponent) ID() CustomID { + return c.CustomID +} + +func (c ButtonComponent) component() {} +func (c ButtonComponent) interactiveComponent() {} + +// WithStyle returns a new ButtonComponent with the provided style +func (c ButtonComponent) WithStyle(style ButtonStyle) ButtonComponent { + c.Style = style + return c +} + +// WithLabel returns a new ButtonComponent with the provided label +func (c ButtonComponent) WithLabel(label string) ButtonComponent { + c.Label = label + return c +} + +// WithEmoji returns a new ButtonComponent with the provided Emoji +func (c ButtonComponent) WithEmoji(emoji ComponentEmoji) ButtonComponent { + c.Emoji = &emoji + return c +} + +// WithCustomID returns a new ButtonComponent with the provided custom id +func (c ButtonComponent) WithCustomID(customID CustomID) ButtonComponent { + c.CustomID = customID + return c +} + +// WithURL returns a new ButtonComponent with the provided URL +func (c ButtonComponent) WithURL(url string) ButtonComponent { + c.URL = url + return c +} + +// AsEnabled returns a new ButtonComponent but enabled +func (c ButtonComponent) AsEnabled() ButtonComponent { + c.Disabled = false + return c +} + +// AsDisabled returns a new ButtonComponent but disabled +func (c ButtonComponent) AsDisabled() ButtonComponent { + c.Disabled = true + return c +} + +// WithDisabled returns a new ButtonComponent but disabled/enabled +func (c ButtonComponent) WithDisabled(disabled bool) ButtonComponent { + c.Disabled = disabled + return c +} + +// NewSelectMenu builds a new SelectMenuComponent from the provided values +//goland:noinspection GoUnusedExportedFunction +func NewSelectMenu(customID CustomID, placeholder string, options ...SelectMenuOption) SelectMenuComponent { + return SelectMenuComponent{ + CustomID: customID, + Placeholder: placeholder, + Options: options, + } +} + +var ( + _ Component = (*SelectMenuComponent)(nil) + _ InteractiveComponent = (*SelectMenuComponent)(nil) +) + +type SelectMenuComponent struct { + CustomID CustomID `json:"custom_id"` + Placeholder string `json:"placeholder,omitempty"` + MinValues json.NullInt `json:"min_values,omitempty"` + MaxValues json.NullInt `json:"max_values,omitempty"` + Disabled bool `json:"disabled,omitempty"` + Options []SelectMenuOption `json:"options,omitempty"` +} + +func (c SelectMenuComponent) MarshalJSON() ([]byte, error) { + type selectMenuComponent SelectMenuComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + selectMenuComponent + }{ + Type: c.Type(), + selectMenuComponent: selectMenuComponent(c), + }) +} + +func (c SelectMenuComponent) Type() ComponentType { + return ComponentTypeSelectMenu +} + +func (c SelectMenuComponent) ID() CustomID { + return c.CustomID +} + +func (c SelectMenuComponent) component() {} +func (c SelectMenuComponent) interactiveComponent() {} + +// WithCustomID returns a new SelectMenuComponent with the provided customID +func (c SelectMenuComponent) WithCustomID(customID CustomID) SelectMenuComponent { + c.CustomID = customID + return c +} + +// WithPlaceholder returns a new SelectMenuComponent with the provided placeholder +func (c SelectMenuComponent) WithPlaceholder(placeholder string) SelectMenuComponent { + c.Placeholder = placeholder + return c +} + +// WithMinValues returns a new SelectMenuComponent with the provided minValue +func (c SelectMenuComponent) WithMinValues(minValue int) SelectMenuComponent { + c.MinValues = *json.NewInt(minValue) + return c +} + +// WithMaxValues returns a new SelectMenuComponent with the provided maxValue +func (c SelectMenuComponent) WithMaxValues(maxValue int) SelectMenuComponent { + c.MaxValues = *json.NewInt(maxValue) + return c +} + +// AsEnabled returns a new SelectMenuComponent but enabled +func (c SelectMenuComponent) AsEnabled() SelectMenuComponent { + c.Disabled = false + return c +} + +// AsDisabled returns a new SelectMenuComponent but disabled +func (c SelectMenuComponent) AsDisabled() SelectMenuComponent { + c.Disabled = true + return c +} + +// WithDisabled returns a new SelectMenuComponent with the provided disabled +func (c SelectMenuComponent) WithDisabled(disabled bool) SelectMenuComponent { + c.Disabled = disabled + return c +} + +// SetOptions returns a new SelectMenuComponent with the provided SelectMenuOption(s) +func (c SelectMenuComponent) SetOptions(options ...SelectMenuOption) SelectMenuComponent { + c.Options = options + return c +} + +// SetOption returns a new SelectMenuComponent with the SelectMenuOption which has the value replaced +func (c SelectMenuComponent) SetOption(value string, option SelectMenuOption) SelectMenuComponent { + for i, o := range c.Options { + if o.Value == value { + c.Options[i] = option + break + } + } + return c +} + +// AddOptions returns a new SelectMenuComponent with the provided SelectMenuOption(s) added +func (c SelectMenuComponent) AddOptions(options ...SelectMenuOption) SelectMenuComponent { + c.Options = append(c.Options, options...) + return c +} + +// RemoveOption returns a new SelectMenuComponent with the provided SelectMenuOption at the index removed +func (c SelectMenuComponent) RemoveOption(index int) SelectMenuComponent { + if len(c.Options) > index { + c.Options = append(c.Options[:index], c.Options[index+1:]...) + } + return c +} + +// NewSelectMenuOption builds a new SelectMenuOption +//goland:noinspection GoUnusedExportedFunction +func NewSelectMenuOption(label string, value string) SelectMenuOption { + return SelectMenuOption{ + Label: label, + Value: value, + } +} + +// SelectMenuOption represents an option in a SelectMenuComponent +type SelectMenuOption struct { + Label string `json:"label"` + Value string `json:"value"` + Description string `json:"description,omitempty"` + Emoji *ComponentEmoji `json:"emoji,omitempty"` + Default bool `json:"default,omitempty"` +} + +// WithLabel returns a new SelectMenuOption with the provided label +func (o SelectMenuOption) WithLabel(label string) SelectMenuOption { + o.Label = label + return o +} + +// WithValue returns a new SelectMenuOption with the provided value +func (o SelectMenuOption) WithValue(value string) SelectMenuOption { + o.Value = value + return o +} + +// WithDescription returns a new SelectMenuOption with the provided description +func (o SelectMenuOption) WithDescription(description string) SelectMenuOption { + o.Description = description + return o +} + +// WithEmoji returns a new SelectMenuOption with the provided Emoji +func (o SelectMenuOption) WithEmoji(emoji ComponentEmoji) SelectMenuOption { + o.Emoji = &emoji + return o +} + +// WithDefault returns a new SelectMenuOption as default/non-default +func (o SelectMenuOption) WithDefault(defaultOption bool) SelectMenuOption { + o.Default = defaultOption + return o +} + +var ( + _ Component = (*TextInputComponent)(nil) + _ InteractiveComponent = (*TextInputComponent)(nil) +) + +type TextInputComponent struct { + CustomID CustomID `json:"custom_id"` + Style TextInputStyle `json:"style"` + Label string `json:"label,omitempty"` + MinLength int `json:"min_length,omitempty"` + MaxLength int `json:"max_length,omitempty"` + Required bool `json:"required,omitempty"` + Placeholder string `json:"placeholder,omitempty"` + Value string `json:"value,omitempty"` +} + +func (t TextInputComponent) MarshalJSON() ([]byte, error) { + type textInput TextInputComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + textInput + }{ + Type: t.Type(), + textInput: textInput(t), + }) +} + +func (t TextInputComponent) Type() ComponentType { + return ComponentTypeTextInput +} + +func (t TextInputComponent) ID() CustomID { + return t.CustomID +} + +func (t TextInputComponent) component() {} +func (t TextInputComponent) interactiveComponent() {} + +type TextInputStyle int + +//goland:noinspection GoUnusedConst +const ( + TextInputStyleShort = iota + 1 + TextInputStyleParagraph +) diff --git a/discord/errors.go b/discord/errors.go index ae8c9242..e177ff33 100644 --- a/discord/errors.go +++ b/discord/errors.go @@ -24,6 +24,7 @@ var ( ErrSelfDM = errors.New("can't open a dm channel to yourself") ErrInteractionAlreadyReplied = errors.New("you already replied to this interaction") + ErrInteractionExpired = errors.New("this interaction has expired") ErrChannelNotTypeNews = errors.New("channel type is not 'NEWS'") diff --git a/discord/interaction.go b/discord/interaction.go index 7e3c2c82..ff345855 100644 --- a/discord/interaction.go +++ b/discord/interaction.go @@ -16,6 +16,7 @@ const ( InteractionTypeApplicationCommand InteractionTypeComponent InteractionTypeAutocomplete + InteractionTypeModalSubmit ) // Interaction is used for easier unmarshalling of different Interaction(s) @@ -76,6 +77,11 @@ func (i *UnmarshalInteraction) UnmarshalJSON(data []byte) error { err = json.Unmarshal(data, &v) interaction = v + case InteractionTypeModalSubmit: + v := ModalSubmitInteraction{} + err = json.Unmarshal(data, &v) + interaction = v + default: return fmt.Errorf("unkown interaction with type %d received", iType.Type) } @@ -413,6 +419,49 @@ func (d *AutocompleteInteractionData) UnmarshalJSON(data []byte) error { return nil } +var ( + _ Interaction = (*ModalSubmitInteraction)(nil) +) + +type ModalSubmitInteraction struct { + BaseInteraction + Data ModalSubmitInteractionData `json:"data"` +} + +func (ModalSubmitInteraction) interaction() {} + +func (ModalSubmitInteraction) Type() InteractionType { + return InteractionTypeModalSubmit +} + +type ModalSubmitInteractionData struct { + CustomID CustomID `json:"custom_id"` + Components []ContainerComponent `json:"components"` +} + +func (d *ModalSubmitInteractionData) UnmarshalJSON(data []byte) error { + type modalSubmitInteractionData ModalSubmitInteractionData + var iData struct { + Components []UnmarshalComponent `json:"components"` + modalSubmitInteractionData + } + + if err := json.Unmarshal(data, &iData); err != nil { + return err + } + + *d = ModalSubmitInteractionData(iData.modalSubmitInteractionData) + + if len(iData.Components) > 0 { + d.Components = make([]ContainerComponent, len(iData.Components)) + for i := range iData.Components { + d.Components[i] = iData.Components[i].Component.(ContainerComponent) + } + } + + return nil +} + // to consider using them in Resolved /* type ResolvedMember struct { diff --git a/discord/interaction_callback.go b/discord/interaction_callback.go index 028495a6..6223d456 100644 --- a/discord/interaction_callback.go +++ b/discord/interaction_callback.go @@ -13,6 +13,7 @@ const ( InteractionCallbackTypeDeferredUpdateMessage InteractionCallbackTypeUpdateMessage InteractionCallbackTypeAutocompleteResult + InteractionCallbackTypeModal ) // InteractionResponse is how you answer interactions. If an answer is not sent within 3 seconds of receiving it, the interaction is failed, and you will be unable to respond to it. @@ -36,3 +37,34 @@ type InteractionCallbackData interface { type InteractionResponseCreator interface { ToResponseBody(response InteractionResponse) (interface{}, error) } + +type AutocompleteResult struct { + Choices []AutocompleteChoice `json:"choices"` +} + +func (AutocompleteResult) interactionCallbackData() {} + +type AutocompleteChoice interface { + autoCompleteChoice() +} + +type AutocompleteChoiceString struct { + Name string `json:"name"` + Value string `json:"value"` +} + +func (AutocompleteChoiceString) autoCompleteChoice() {} + +type AutocompleteChoiceInt struct { + Name string `json:"name"` + Value int `json:"value"` +} + +func (AutocompleteChoiceInt) autoCompleteChoice() {} + +type AutocompleteChoiceFloat struct { + Name string `json:"name"` + Value float64 `json:"value"` +} + +func (AutocompleteChoiceFloat) autoCompleteChoice() {} diff --git a/discord/modal_create.go b/discord/modal_create.go new file mode 100644 index 00000000..9e224a4a --- /dev/null +++ b/discord/modal_create.go @@ -0,0 +1,73 @@ +package discord + +var _ InteractionCallbackData = (*ModalCreate)(nil) + +type ModalCreate struct { + CustomID CustomID `json:"custom_id"` + Title string `json:"title"` + Components []ContainerComponent `json:"components"` +} + +func (ModalCreate) interactionCallbackData() {} + +// NewModalCreateBuilder creates a new ModalCreateBuilder to be built later +//goland:noinspection GoUnusedExportedFunction +func NewModalCreateBuilder() *ModalCreateBuilder { + return &ModalCreateBuilder{} +} + +type ModalCreateBuilder struct { + ModalCreate +} + +// SetCustomID sets the CustomID of the ModalCreate +func (b *ModalCreateBuilder) SetCustomID(customID CustomID) *ModalCreateBuilder { + b.CustomID = customID + return b +} + +// SetTitle sets the title of the ModalCreate +func (b *ModalCreateBuilder) SetTitle(title string) *ModalCreateBuilder { + b.Title = title + return b +} + +// SetContainerComponents sets the discord.ContainerComponent(s) of the ModalCreate +func (b *ModalCreateBuilder) SetContainerComponents(containerComponents ...ContainerComponent) *ModalCreateBuilder { + b.Components = containerComponents + return b +} + +// SetContainerComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) +func (b *ModalCreateBuilder) SetContainerComponent(i int, container ContainerComponent) *ModalCreateBuilder { + if len(b.Components) > i { + b.Components[i] = container + } + return b +} + +// AddActionRow adds a new discord.ActionRowComponent with the provided discord.InteractiveComponent(s) to the ModalCreate +func (b *ModalCreateBuilder) AddActionRow(components ...InteractiveComponent) *ModalCreateBuilder { + b.Components = append(b.Components, ActionRowComponent(components)) + return b +} + +// AddContainerComponents adds the discord.ContainerComponent(s) to the ModalCreate +func (b *ModalCreateBuilder) AddContainerComponents(containers ...ContainerComponent) *ModalCreateBuilder { + b.Components = append(b.Components, containers...) + return b +} + +// RemoveContainerComponent removes a discord.ActionRowComponent from the ModalCreate +func (b *ModalCreateBuilder) RemoveContainerComponent(i int) *ModalCreateBuilder { + if len(b.Components) > i { + b.Components = append(b.Components[:i], b.Components[i+1:]...) + } + return b +} + +// ClearContainerComponents removes all the discord.ContainerComponent(s) of the ModalCreate +func (b *ModalCreateBuilder) ClearContainerComponents() *ModalCreateBuilder { + b.Components = []ContainerComponent{} + return b +} diff --git a/discord/select_menu.go b/discord/select_menu.go deleted file mode 100644 index 189fc180..00000000 --- a/discord/select_menu.go +++ /dev/null @@ -1,170 +0,0 @@ -package discord - -import "github.com/DisgoOrg/disgo/json" - -// NewSelectMenu builds a new SelectMenuComponent from the provided values -//goland:noinspection GoUnusedExportedFunction -func NewSelectMenu(customID CustomID, placeholder string, options ...SelectMenuOption) SelectMenuComponent { - return SelectMenuComponent{ - CustomID: customID, - Placeholder: placeholder, - Options: options, - } -} - -var ( - _ Component = (*SelectMenuComponent)(nil) - _ InteractiveComponent = (*SelectMenuComponent)(nil) -) - -type SelectMenuComponent struct { - CustomID CustomID `json:"custom_id"` - Placeholder string `json:"placeholder,omitempty"` - MinValues json.NullInt `json:"min_values,omitempty"` - MaxValues json.NullInt `json:"max_values,omitempty"` - Disabled bool `json:"disabled,omitempty"` - Options []SelectMenuOption `json:"options,omitempty"` -} - -func (c SelectMenuComponent) MarshalJSON() ([]byte, error) { - type selectMenuComponent SelectMenuComponent - return json.Marshal(struct { - Type ComponentType `json:"type"` - selectMenuComponent - }{ - Type: c.Type(), - selectMenuComponent: selectMenuComponent(c), - }) -} - -func (c SelectMenuComponent) Type() ComponentType { - return ComponentTypeSelectMenu -} - -func (c SelectMenuComponent) ID() CustomID { - return c.CustomID -} - -func (c SelectMenuComponent) component() {} -func (c SelectMenuComponent) interactiveComponent() {} - -// WithCustomID returns a new SelectMenuComponent with the provided customID -func (c SelectMenuComponent) WithCustomID(customID CustomID) SelectMenuComponent { - c.CustomID = customID - return c -} - -// WithPlaceholder returns a new SelectMenuComponent with the provided placeholder -func (c SelectMenuComponent) WithPlaceholder(placeholder string) SelectMenuComponent { - c.Placeholder = placeholder - return c -} - -// WithMinValues returns a new SelectMenuComponent with the provided minValue -func (c SelectMenuComponent) WithMinValues(minValue int) SelectMenuComponent { - c.MinValues = *json.NewInt(minValue) - return c -} - -// WithMaxValues returns a new SelectMenuComponent with the provided maxValue -func (c SelectMenuComponent) WithMaxValues(maxValue int) SelectMenuComponent { - c.MaxValues = *json.NewInt(maxValue) - return c -} - -// AsEnabled returns a new SelectMenuComponent but enabled -func (c SelectMenuComponent) AsEnabled() SelectMenuComponent { - c.Disabled = false - return c -} - -// AsDisabled returns a new SelectMenuComponent but disabled -func (c SelectMenuComponent) AsDisabled() SelectMenuComponent { - c.Disabled = true - return c -} - -// WithDisabled returns a new SelectMenuComponent with the provided disabled -func (c SelectMenuComponent) WithDisabled(disabled bool) SelectMenuComponent { - c.Disabled = disabled - return c -} - -// SetOptions returns a new SelectMenuComponent with the provided SelectMenuOption(s) -func (c SelectMenuComponent) SetOptions(options ...SelectMenuOption) SelectMenuComponent { - c.Options = options - return c -} - -// SetOption returns a new SelectMenuComponent with the SelectMenuOption which has the value replaced -func (c SelectMenuComponent) SetOption(value string, option SelectMenuOption) SelectMenuComponent { - for i, o := range c.Options { - if o.Value == value { - c.Options[i] = option - break - } - } - return c -} - -// AddOptions returns a new SelectMenuComponent with the provided SelectMenuOption(s) added -func (c SelectMenuComponent) AddOptions(options ...SelectMenuOption) SelectMenuComponent { - c.Options = append(c.Options, options...) - return c -} - -// RemoveOption returns a new SelectMenuComponent with the provided SelectMenuOption at the index removed -func (c SelectMenuComponent) RemoveOption(index int) SelectMenuComponent { - if len(c.Options) > index { - c.Options = append(c.Options[:index], c.Options[index+1:]...) - } - return c -} - -// NewSelectMenuOption builds a new SelectMenuOption -//goland:noinspection GoUnusedExportedFunction -func NewSelectMenuOption(label string, value string) SelectMenuOption { - return SelectMenuOption{ - Label: label, - Value: value, - } -} - -// SelectMenuOption represents an option in a SelectMenuComponent -type SelectMenuOption struct { - Label string `json:"label"` - Value string `json:"value"` - Description string `json:"description,omitempty"` - Emoji *ComponentEmoji `json:"emoji,omitempty"` - Default bool `json:"default,omitempty"` -} - -// WithLabel returns a new SelectMenuOption with the provided label -func (o SelectMenuOption) WithLabel(label string) SelectMenuOption { - o.Label = label - return o -} - -// WithValue returns a new SelectMenuOption with the provided value -func (o SelectMenuOption) WithValue(value string) SelectMenuOption { - o.Value = value - return o -} - -// WithDescription returns a new SelectMenuOption with the provided description -func (o SelectMenuOption) WithDescription(description string) SelectMenuOption { - o.Description = description - return o -} - -// WithEmoji returns a new SelectMenuOption with the provided Emoji -func (o SelectMenuOption) WithEmoji(emoji ComponentEmoji) SelectMenuOption { - o.Emoji = &emoji - return o -} - -// WithDefault returns a new SelectMenuOption as default/non-default -func (o SelectMenuOption) WithDefault(defaultOption bool) SelectMenuOption { - o.Default = defaultOption - return o -}