Skip to content

Commit b6db5a6

Browse files
committed
feat: add support for additional select menu components in modals
^ Conflicts: ^ discord/select_menu.go
1 parent b1d3442 commit b6db5a6

File tree

6 files changed

+142
-52
lines changed

6 files changed

+142
-52
lines changed

_examples/application_commands/http/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ require (
1515
github.com/disgoorg/omit v1.0.0 // indirect
1616
github.com/gorilla/websocket v1.5.3 // indirect
1717
github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect
18-
golang.org/x/crypto v0.37.0 // indirect
19-
golang.org/x/sys v0.32.0 // indirect
18+
golang.org/x/crypto v0.39.0 // indirect
19+
golang.org/x/sys v0.33.0 // indirect
2020
)

_examples/application_commands/http/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
1818
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1919
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
2020
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
21+
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
2122
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
2223
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
24+
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
2325
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
2426
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

_examples/componentsv2/example.go

Lines changed: 64 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
"github.com/disgoorg/disgo"
1515
"github.com/disgoorg/disgo/bot"
1616
"github.com/disgoorg/disgo/discord"
17-
"github.com/disgoorg/disgo/events"
1817
"github.com/disgoorg/disgo/handler"
1918
)
2019

@@ -36,6 +35,15 @@ var (
3635
Required: false,
3736
},
3837
},
38+
IntegrationTypes: []discord.ApplicationIntegrationType{
39+
discord.ApplicationIntegrationTypeGuildInstall,
40+
discord.ApplicationIntegrationTypeUserInstall,
41+
},
42+
Contexts: []discord.InteractionContextType{
43+
discord.InteractionContextTypeGuild,
44+
discord.InteractionContextTypeBotDM,
45+
discord.InteractionContextTypePrivateChannel,
46+
},
3947
},
4048
}
4149
)
@@ -45,9 +53,14 @@ func main() {
4553
slog.Info("disgo version", slog.String("version", disgo.Version))
4654
slog.SetLogLoggerLevel(slog.LevelDebug)
4755

56+
r := handler.New()
57+
r.SlashCommand("/test", onTest)
58+
r.SlashCommand("/modal", onModal)
59+
r.Modal("/modal", onModalSubmit)
60+
4861
client, err := disgo.New(token,
4962
bot.WithDefaultGateway(),
50-
bot.WithEventListenerFunc(onCommand),
63+
bot.WithEventListeners(r),
5164
)
5265
if err != nil {
5366
slog.Error("error while building bot", slog.Any("err", err))
@@ -70,39 +83,54 @@ func main() {
7083
<-s
7184
}
7285

73-
func onCommand(e *events.ApplicationCommandInteractionCreate) {
74-
switch data := e.Data.(type) {
75-
case discord.SlashCommandInteractionData:
76-
flags := discord.MessageFlagIsComponentsV2
77-
if ephemeral, ok := data.OptBool("ephemeral"); !ok || ephemeral {
78-
flags = flags.Add(discord.MessageFlagEphemeral)
79-
}
80-
if err := e.CreateMessage(discord.MessageCreate{
81-
Flags: flags,
82-
Components: []discord.LayoutComponent{
83-
discord.NewContainer(
84-
discord.NewSection(
85-
discord.NewTextDisplay("**Name: [Seeing Red](https://open.spotify.com/track/65qBr6ToDUjTD1RiE1H4Gl)**"),
86-
discord.NewTextDisplay("**Artist: [Architects](https://open.spotify.com/artist/3ZztVuWxHzNpl0THurTFCv)**"),
87-
discord.NewTextDisplay("**Album: [The Sky, The Earth & All Between](https://open.spotify.com/album/2W82VyyIFAXigJEiLm5TT1)**"),
88-
).WithAccessory(discord.NewThumbnail("attachment://thumbnail.png")),
89-
discord.NewTextDisplay("`0:08`/`3:40`"),
90-
discord.NewTextDisplay("[🔘▬▬▬▬▬▬▬▬▬]"),
91-
discord.NewSmallSeparator(),
92-
discord.NewActionRow(
93-
discord.NewPrimaryButton("", "/player/previous").WithEmoji(discord.ComponentEmoji{Name: "⏮"}),
94-
discord.NewPrimaryButton("", "/player/pause_play").WithEmoji(discord.ComponentEmoji{Name: "⏯"}),
95-
discord.NewPrimaryButton("", "/player/next").WithEmoji(discord.ComponentEmoji{Name: "⏭"}),
96-
discord.NewDangerButton("", "/player/stop").WithEmoji(discord.ComponentEmoji{Name: "⏹"}),
97-
discord.NewPrimaryButton("", "/player/like").WithEmoji(discord.ComponentEmoji{Name: "❤️"}),
98-
),
99-
).WithAccentColor(0x5c5fea),
100-
},
101-
Files: []*discord.File{
102-
discord.NewFile("thumbnail.png", "", bytes.NewReader(thumbnail)),
103-
},
104-
}); err != nil {
105-
slog.Error("error while sending message", slog.Any("err", err))
106-
}
86+
func onTest(data discord.SlashCommandInteractionData, e *handler.CommandEvent) error {
87+
flags := discord.MessageFlagIsComponentsV2
88+
if ephemeral, ok := data.OptBool("ephemeral"); !ok || ephemeral {
89+
flags = flags.Add(discord.MessageFlagEphemeral)
10790
}
91+
92+
return e.CreateMessage(discord.MessageCreate{
93+
Flags: flags,
94+
Components: []discord.LayoutComponent{
95+
discord.NewContainer(
96+
discord.NewSection(
97+
discord.NewTextDisplay("**Name: [Seeing Red](https://open.spotify.com/track/65qBr6ToDUjTD1RiE1H4Gl)**"),
98+
discord.NewTextDisplay("**Artist: [Architects](https://open.spotify.com/artist/3ZztVuWxHzNpl0THurTFCv)**"),
99+
discord.NewTextDisplay("**Album: [The Sky, The Earth & All Between](https://open.spotify.com/album/2W82VyyIFAXigJEiLm5TT1)**"),
100+
).WithAccessory(discord.NewThumbnail("attachment://thumbnail.png")),
101+
discord.NewTextDisplay("`0:08`/`3:40`"),
102+
discord.NewTextDisplay("[🔘▬▬▬▬▬▬▬▬▬]"),
103+
discord.NewSmallSeparator(),
104+
discord.NewActionRow(
105+
discord.NewPrimaryButton("", "/player/previous").WithEmoji(discord.ComponentEmoji{Name: "⏮"}),
106+
discord.NewPrimaryButton("", "/player/pause_play").WithEmoji(discord.ComponentEmoji{Name: "⏯"}),
107+
discord.NewPrimaryButton("", "/player/next").WithEmoji(discord.ComponentEmoji{Name: "⏭"}),
108+
discord.NewDangerButton("", "/player/stop").WithEmoji(discord.ComponentEmoji{Name: "⏹"}),
109+
discord.NewPrimaryButton("", "/player/like").WithEmoji(discord.ComponentEmoji{Name: "❤️"}),
110+
),
111+
).WithAccentColor(0x5c5fea),
112+
},
113+
Files: []*discord.File{
114+
discord.NewFile("thumbnail.png", "", bytes.NewReader(thumbnail)),
115+
},
116+
})
117+
}
118+
119+
func onModal(_ discord.SlashCommandInteractionData, e *handler.CommandEvent) error {
120+
return e.Modal(discord.ModalCreate{
121+
CustomID: "/modal",
122+
Title: "Test Modal",
123+
Components: []discord.LayoutComponent{
124+
discord.NewTextDisplay("This is a modal"),
125+
discord.NewLabel("Test Input", discord.NewShortTextInput("test_input")),
126+
discord.NewLabel("Test Select", discord.NewUserSelectMenu("test_user", "Select a user")),
127+
},
128+
})
129+
}
130+
131+
func onModalSubmit(e *handler.ModalEvent) error {
132+
return e.CreateMessage(discord.MessageCreate{
133+
Content: "You submitted the modal!",
134+
Flags: discord.MessageFlagEphemeral,
135+
})
108136
}

_examples/test/listeners.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ func componentListener(event *events.ComponentInteractionCreate) {
5555
discord.TextInputComponent{
5656
CustomID: "test_input",
5757
Style: discord.TextInputStyleShort,
58-
Label: "qwq",
5958
Required: true,
6059
Placeholder: "test placeholder",
6160
Value: "uwu",

discord/component.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,11 @@ type ContainerSubComponent interface {
136136
// LabelSubComponent is an interface for all components that can be present in a [LabelComponent].
137137
// [StringSelectMenuComponent]
138138
// [TextInputComponent]
139+
// [UserSelectMenuComponent]
140+
// [RoleSelectMenuComponent]
141+
// [MentionableSelectMenuComponent]
142+
// [ChannelSelectMenuComponent]
143+
// [UnknownComponent]
139144
type LabelSubComponent interface {
140145
Component
141146
// labelSubComponent is a marker to simulate unions.
@@ -608,16 +613,14 @@ var (
608613
//
609614
// [Discord Docs]: https://discord.com/developers/docs/interactions/message-components#text-inputs
610615
type TextInputComponent struct {
611-
ID int `json:"id,omitempty"`
612-
CustomID string `json:"custom_id"`
613-
Style TextInputStyle `json:"style"`
614-
// Deprecated: Label is deprecated and will be removed in a future version. Use [LabelComponent] instead.
615-
Label string `json:"label,omitempty"`
616-
MinLength *int `json:"min_length,omitempty"`
617-
MaxLength int `json:"max_length,omitempty"`
618-
Required bool `json:"required"`
619-
Placeholder string `json:"placeholder,omitempty"`
620-
Value string `json:"value,omitempty"`
616+
ID int `json:"id,omitempty"`
617+
CustomID string `json:"custom_id"`
618+
Style TextInputStyle `json:"style"`
619+
MinLength *int `json:"min_length,omitempty"`
620+
MaxLength int `json:"max_length,omitempty"`
621+
Required bool `json:"required"`
622+
Placeholder string `json:"placeholder,omitempty"`
623+
Value string `json:"value,omitempty"`
621624
}
622625

623626
func (c TextInputComponent) MarshalJSON() ([]byte, error) {

discord/select_menu.go

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ var (
218218
_ Component = (*UserSelectMenuComponent)(nil)
219219
_ InteractiveComponent = (*UserSelectMenuComponent)(nil)
220220
_ SelectMenuComponent = (*UserSelectMenuComponent)(nil)
221+
_ LabelSubComponent = (*UserSelectMenuComponent)(nil)
221222
)
222223

223224
// NewUserSelectMenu builds a new SelectMenuComponent from the provided values
@@ -235,6 +236,8 @@ type UserSelectMenuComponent struct {
235236
DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"`
236237
MinValues *int `json:"min_values,omitempty"`
237238
MaxValues int `json:"max_values,omitempty"`
239+
// Required Indicates if the select menu is required to submit the Modal.
240+
Required bool `json:"required"`
238241
// Disabled whether the select menu is disabled (only supported in messages)
239242
Disabled bool `json:"disabled"`
240243
}
@@ -265,6 +268,7 @@ func (c UserSelectMenuComponent) GetCustomID() string {
265268
func (UserSelectMenuComponent) component() {}
266269
func (UserSelectMenuComponent) interactiveComponent() {}
267270
func (UserSelectMenuComponent) selectMenuComponent() {}
271+
func (UserSelectMenuComponent) labelSubComponent() {}
268272

269273
// WithCustomID returns a new UserSelectMenuComponent with the provided customID
270274
func (c UserSelectMenuComponent) WithCustomID(customID string) UserSelectMenuComponent {
@@ -338,10 +342,17 @@ func (c UserSelectMenuComponent) WithID(id int) UserSelectMenuComponent {
338342
return c
339343
}
340344

345+
// WithRequired returns a new UserSelectMenuComponent with the provided required value
346+
func (c UserSelectMenuComponent) WithRequired(required bool) UserSelectMenuComponent {
347+
c.Required = required
348+
return c
349+
}
350+
341351
var (
342-
_ Component = (*UserSelectMenuComponent)(nil)
343-
_ InteractiveComponent = (*UserSelectMenuComponent)(nil)
344-
_ SelectMenuComponent = (*UserSelectMenuComponent)(nil)
352+
_ Component = (*RoleSelectMenuComponent)(nil)
353+
_ InteractiveComponent = (*RoleSelectMenuComponent)(nil)
354+
_ SelectMenuComponent = (*RoleSelectMenuComponent)(nil)
355+
_ LabelSubComponent = (*RoleSelectMenuComponent)(nil)
345356
)
346357

347358
// NewRoleSelectMenu builds a new SelectMenuComponent from the provided values
@@ -359,6 +370,8 @@ type RoleSelectMenuComponent struct {
359370
DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"`
360371
MinValues *int `json:"min_values,omitempty"`
361372
MaxValues int `json:"max_values,omitempty"`
373+
// Required Indicates if the select menu is required to submit the Modal.
374+
Required bool `json:"required"`
362375
// Disabled whether the select menu is disabled (only supported in messages)
363376
Disabled bool `json:"disabled"`
364377
}
@@ -389,6 +402,7 @@ func (c RoleSelectMenuComponent) GetCustomID() string {
389402
func (RoleSelectMenuComponent) component() {}
390403
func (RoleSelectMenuComponent) interactiveComponent() {}
391404
func (RoleSelectMenuComponent) selectMenuComponent() {}
405+
func (RoleSelectMenuComponent) labelSubComponent() {}
392406

393407
// WithCustomID returns a new RoleSelectMenuComponent with the provided customID
394408
func (c RoleSelectMenuComponent) WithCustomID(customID string) RoleSelectMenuComponent {
@@ -462,10 +476,23 @@ func (c RoleSelectMenuComponent) WithID(id int) RoleSelectMenuComponent {
462476
return c
463477
}
464478

479+
// WithRequired returns a new RoleSelectMenuComponent with the provided required value
480+
func (c RoleSelectMenuComponent) WithRequired(required bool) RoleSelectMenuComponent {
481+
c.Required = required
482+
return c
483+
}
484+
485+
// WithID returns a new RoleSelectMenuComponent with the provided ID
486+
func (c RoleSelectMenuComponent) WithID(id int) RoleSelectMenuComponent {
487+
c.ID = id
488+
return c
489+
}
490+
465491
var (
466492
_ Component = (*MentionableSelectMenuComponent)(nil)
467493
_ InteractiveComponent = (*MentionableSelectMenuComponent)(nil)
468494
_ SelectMenuComponent = (*MentionableSelectMenuComponent)(nil)
495+
_ LabelSubComponent = (*MentionableSelectMenuComponent)(nil)
469496
)
470497

471498
// NewMentionableSelectMenu builds a new SelectMenuComponent from the provided values
@@ -483,6 +510,8 @@ type MentionableSelectMenuComponent struct {
483510
DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"`
484511
MinValues *int `json:"min_values,omitempty"`
485512
MaxValues int `json:"max_values,omitempty"`
513+
// Required Indicates if the select menu is required to submit the Modal.
514+
Required bool `json:"required"`
486515
// Disabled whether the select menu is disabled (only supported in messages)
487516
Disabled bool `json:"disabled"`
488517
}
@@ -513,6 +542,7 @@ func (c MentionableSelectMenuComponent) GetCustomID() string {
513542
func (MentionableSelectMenuComponent) component() {}
514543
func (MentionableSelectMenuComponent) interactiveComponent() {}
515544
func (MentionableSelectMenuComponent) selectMenuComponent() {}
545+
func (MentionableSelectMenuComponent) labelSubComponent() {}
516546

517547
// WithCustomID returns a new MentionableSelectMenuComponent with the provided customID
518548
func (c MentionableSelectMenuComponent) WithCustomID(customID string) MentionableSelectMenuComponent {
@@ -583,10 +613,23 @@ func (c MentionableSelectMenuComponent) WithID(id int) MentionableSelectMenuComp
583613
return c
584614
}
585615

616+
// WithID returns a new MentionableSelectMenuComponent with the provided ID
617+
func (c MentionableSelectMenuComponent) WithID(id int) MentionableSelectMenuComponent {
618+
c.ID = id
619+
return c
620+
}
621+
622+
// WithRequired returns a new MentionableSelectMenuComponent with the provided required value
623+
func (c MentionableSelectMenuComponent) WithRequired(required bool) MentionableSelectMenuComponent {
624+
c.Required = required
625+
return c
626+
}
627+
586628
var (
587629
_ Component = (*ChannelSelectMenuComponent)(nil)
588630
_ InteractiveComponent = (*ChannelSelectMenuComponent)(nil)
589631
_ SelectMenuComponent = (*ChannelSelectMenuComponent)(nil)
632+
_ LabelSubComponent = (*ChannelSelectMenuComponent)(nil)
590633
)
591634

592635
// NewChannelSelectMenu builds a new SelectMenuComponent from the provided values
@@ -605,6 +648,8 @@ type ChannelSelectMenuComponent struct {
605648
MinValues *int `json:"min_values,omitempty"`
606649
MaxValues int `json:"max_values,omitempty"`
607650
ChannelTypes []ChannelType `json:"channel_types,omitempty"`
651+
// Required Indicates if the select menu is required to submit the Modal.
652+
Required bool `json:"required"`
608653
// Disabled whether the select menu is disabled (only supported in messages)
609654
Disabled bool `json:"disabled"`
610655
}
@@ -635,6 +680,7 @@ func (c ChannelSelectMenuComponent) GetCustomID() string {
635680
func (ChannelSelectMenuComponent) component() {}
636681
func (ChannelSelectMenuComponent) interactiveComponent() {}
637682
func (ChannelSelectMenuComponent) selectMenuComponent() {}
683+
func (ChannelSelectMenuComponent) labelSubComponent() {}
638684

639685
// WithCustomID returns a new ChannelSelectMenuComponent with the provided customID
640686
func (c ChannelSelectMenuComponent) WithCustomID(customID string) ChannelSelectMenuComponent {
@@ -714,6 +760,18 @@ func (c ChannelSelectMenuComponent) WithID(id int) ChannelSelectMenuComponent {
714760
return c
715761
}
716762

763+
// WithID returns a new ChannelSelectMenuComponent with the provided ID
764+
func (c ChannelSelectMenuComponent) WithID(id int) ChannelSelectMenuComponent {
765+
c.ID = id
766+
return c
767+
}
768+
769+
// WithRequired returns a new ChannelSelectMenuComponent with the provided required value
770+
func (c ChannelSelectMenuComponent) WithRequired(required bool) ChannelSelectMenuComponent {
771+
c.Required = required
772+
return c
773+
}
774+
717775
type SelectMenuDefaultValue struct {
718776
Type SelectMenuDefaultValueType `json:"type"`
719777
ID snowflake.ID `json:"id"`

0 commit comments

Comments
 (0)