diff --git a/_examples/componentsv2/example.go b/_examples/componentsv2/example.go new file mode 100644 index 000000000..71557db5c --- /dev/null +++ b/_examples/componentsv2/example.go @@ -0,0 +1,108 @@ +package main + +import ( + "bytes" + "context" + _ "embed" + "log/slog" + "os" + "os/signal" + "syscall" + + "github.com/disgoorg/snowflake/v2" + + "github.com/disgoorg/disgo" + "github.com/disgoorg/disgo/bot" + "github.com/disgoorg/disgo/discord" + "github.com/disgoorg/disgo/events" + "github.com/disgoorg/disgo/handler" +) + +var ( + token = os.Getenv("disgo_token") + guildID = snowflake.GetEnv("disgo_guild_id") + + //go:embed thumbnail.jpg + thumbnail []byte + + commands = []discord.ApplicationCommandCreate{ + discord.SlashCommandCreate{ + Name: "test", + Description: "test", + Options: []discord.ApplicationCommandOption{ + discord.ApplicationCommandOptionBool{ + Name: "ephemeral", + Description: "if the message should be ephemeral", + Required: false, + }, + }, + }, + } +) + +func main() { + slog.Info("starting example...") + slog.Info("disgo version", slog.String("version", disgo.Version)) + slog.SetLogLoggerLevel(slog.LevelDebug) + + client, err := disgo.New(token, + bot.WithDefaultGateway(), + bot.WithEventListenerFunc(onCommand), + ) + if err != nil { + slog.Error("error while building bot", slog.Any("err", err)) + return + } + defer client.Close(context.TODO()) + + if err = handler.SyncCommands(client, commands, []snowflake.ID{guildID}); err != nil { + slog.Error("error while syncing commands", slog.Any("err", err)) + } + + if err = client.OpenGateway(context.TODO()); err != nil { + slog.Error("error while connecting to gateway", slog.Any("err", err)) + return + } + + slog.Info("example is now running. Press CTRL-C to exit.") + s := make(chan os.Signal, 1) + signal.Notify(s, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) + <-s +} + +func onCommand(e *events.ApplicationCommandInteractionCreate) { + switch data := e.Data.(type) { + case discord.SlashCommandInteractionData: + flags := discord.MessageFlagIsComponentsV2 + if ephemeral, ok := data.OptBool("ephemeral"); !ok || ephemeral { + flags = flags.Add(discord.MessageFlagEphemeral) + } + if err := e.CreateMessage(discord.MessageCreate{ + Flags: flags, + Components: []discord.LayoutComponent{ + discord.NewContainer( + discord.NewSection( + discord.NewTextDisplay("**Name: [Seeing Red](https://open.spotify.com/track/65qBr6ToDUjTD1RiE1H4Gl)**"), + discord.NewTextDisplay("**Artist: [Architects](https://open.spotify.com/artist/3ZztVuWxHzNpl0THurTFCv)**"), + discord.NewTextDisplay("**Album: [The Sky, The Earth & All Between](https://open.spotify.com/album/2W82VyyIFAXigJEiLm5TT1)**"), + ).WithAccessory(discord.NewThumbnail("attachment://thumbnail.png")), + discord.NewTextDisplay("`0:08`/`3:40`"), + discord.NewTextDisplay("[🔘▬▬▬▬▬▬▬▬▬]"), + discord.NewSmallSeparator(), + discord.NewActionRow( + discord.NewPrimaryButton("", "/player/previous").WithEmoji(discord.ComponentEmoji{Name: "⏮"}), + discord.NewPrimaryButton("", "/player/pause_play").WithEmoji(discord.ComponentEmoji{Name: "⏯"}), + discord.NewPrimaryButton("", "/player/next").WithEmoji(discord.ComponentEmoji{Name: "⏭"}), + discord.NewDangerButton("", "/player/stop").WithEmoji(discord.ComponentEmoji{Name: "⏹"}), + discord.NewPrimaryButton("", "/player/like").WithEmoji(discord.ComponentEmoji{Name: "❤️"}), + ), + ).WithAccentColor(0x5c5fea), + }, + Files: []*discord.File{ + discord.NewFile("thumbnail.png", "", bytes.NewReader(thumbnail)), + }, + }); err != nil { + slog.Error("error while sending message", slog.Any("err", err)) + } + } +} diff --git a/_examples/componentsv2/thumbnail.jpg b/_examples/componentsv2/thumbnail.jpg new file mode 100644 index 000000000..68a49cf63 Binary files /dev/null and b/_examples/componentsv2/thumbnail.jpg differ diff --git a/_examples/test/examplebot.go b/_examples/test/examplebot.go index 0d72c9776..4cf513a2c 100644 --- a/_examples/test/examplebot.go +++ b/_examples/test/examplebot.go @@ -8,12 +8,13 @@ import ( "os/signal" "syscall" + "github.com/disgoorg/snowflake/v2" + "github.com/disgoorg/disgo" "github.com/disgoorg/disgo/bot" "github.com/disgoorg/disgo/cache" "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/gateway" - "github.com/disgoorg/snowflake/v2" ) var ( diff --git a/discord/component.go b/discord/component.go index f43611e74..5e7dad7a4 100644 --- a/discord/component.go +++ b/discord/component.go @@ -2,6 +2,7 @@ package discord import ( "fmt" + "iter" "github.com/disgoorg/json" "github.com/disgoorg/snowflake/v2" @@ -20,26 +21,116 @@ const ( ComponentTypeRoleSelectMenu ComponentTypeMentionableSelectMenu ComponentTypeChannelSelectMenu + ComponentTypeSection + ComponentTypeTextDisplay + ComponentTypeThumbnail + ComponentTypeMediaGallery + ComponentTypeFile + ComponentTypeSeparator + _ + // ComponentTypeContentInventoryEntry cannot be used by bots. + ComponentTypeContentInventoryEntry + ComponentTypeContainer ) +// Component is an interface for all components. +// [ActionRowComponent] +// [ButtonComponent] +// [StringSelectMenuComponent] +// [TextInputComponent] +// [UserSelectMenuComponent] +// [RoleSelectMenuComponent] +// [MentionableSelectMenuComponent] +// [ChannelSelectMenuComponent] +// [SectionComponent] +// [TextDisplayComponent] +// [ThumbnailComponent] +// [MediaGalleryComponent] +// [FileComponent] +// [SeparatorComponent] +// [ContainerComponent] +// [UnknownComponent] type Component interface { json.Marshaler + // Type returns the ComponentType of the Component. Type() ComponentType + // GetID returns the id of the Component. This is used to uniquely identify a Component in a [Message] and needs to be unique. + GetID() int + // component is a marker to simulate unions. component() } -type ContainerComponent interface { - Component - Components() []InteractiveComponent - containerComponent() +// ComponentIter is an optional interface a Component can implement to return an iterator over its sub components. +type ComponentIter interface { + SubComponents() iter.Seq[Component] } +// InteractiveComponent is an interface for all components that can be present in an [ActionRowComponent]. +// [ButtonComponent] +// [StringSelectMenuComponent] +// [TextInputComponent] (currently only supported in modals) +// [UserSelectMenuComponent] +// [RoleSelectMenuComponent] +// [MentionableSelectMenuComponent] +// [ChannelSelectMenuComponent] +// [ButtonComponent] +// [SelectMenuComponent] +// [UnknownComponent] type InteractiveComponent interface { Component - ID() string + // GetCustomID returns the customID of the Component. This can be used to identify or transport data with the Component. + GetCustomID() string + // interactiveComponent is a marker to simulate unions. interactiveComponent() } +// LayoutComponent is an interface for all components that can be present as a top level component in a [Message]. +// [ActionRowComponent] +// [SectionComponent] +// [TextDisplayComponent] +// [MediaGalleryComponent] +// [FileComponent] +// [SeparatorComponent] +// [ContainerComponent] +// [UnknownComponent] +type LayoutComponent interface { + Component + layoutComponent() +} + +// SectionSubComponent is an interface for all components that can be present in a [SectionComponent]. +// [TextDisplayComponent] +// [UnknownComponent] +type SectionSubComponent interface { + Component + // sectionSubComponent is a marker to simulate unions. + sectionSubComponent() +} + +// SectionAccessoryComponent is an interface for all components that can be present as an accessory in [SectionComponent.Accessory]. +// [ButtonComponent] +// [ThumbnailComponent] +// [UnknownComponent] +type SectionAccessoryComponent interface { + Component + // sectionAccessoryComponent is a marker to simulate unions. + sectionAccessoryComponent() +} + +// ContainerSubComponent is an interface for all components that can be present in a [ContainerComponent]. +// [ActionRowComponent] +// [SectionComponent] +// [TextDisplayComponent] +// [MediaGalleryComponent] +// [FileComponent] +// [SeparatorComponent] +// [UnknownComponent] +type ContainerSubComponent interface { + Component + // containerSubComponent is a marker to simulate unions. + containerSubComponent() +} + type UnmarshalComponent struct { Component } @@ -60,42 +151,77 @@ func (u *UnmarshalComponent) UnmarshalJSON(data []byte) error { switch cType.Type { case ComponentTypeActionRow: - v := ActionRowComponent{} + var v ActionRowComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeButton: - v := ButtonComponent{} + var v ButtonComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeStringSelectMenu: - v := StringSelectMenuComponent{} + var v StringSelectMenuComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeTextInput: - v := TextInputComponent{} + var v TextInputComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeUserSelectMenu: - v := UserSelectMenuComponent{} + var v UserSelectMenuComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeRoleSelectMenu: - v := RoleSelectMenuComponent{} + var v RoleSelectMenuComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeMentionableSelectMenu: - v := MentionableSelectMenuComponent{} + var v MentionableSelectMenuComponent err = json.Unmarshal(data, &v) component = v case ComponentTypeChannelSelectMenu: - v := ChannelSelectMenuComponent{} + var v ChannelSelectMenuComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeSection: + var v SectionComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeTextDisplay: + var v TextDisplayComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeThumbnail: + var v ThumbnailComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeMediaGallery: + var v MediaGalleryComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeFile: + var v FileComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeSeparator: + var v SeparatorComponent + err = json.Unmarshal(data, &v) + component = v + + case ComponentTypeContainer: + var v ContainerComponent err = json.Unmarshal(data, &v) component = v @@ -116,43 +242,50 @@ type ComponentEmoji struct { Animated bool `json:"animated,omitempty"` } +func NewActionRow(components ...InteractiveComponent) ActionRowComponent { + return ActionRowComponent{ + Components: components, + } +} + var ( - _ Component = (*ActionRowComponent)(nil) - _ ContainerComponent = (*ActionRowComponent)(nil) + _ Component = (*ActionRowComponent)(nil) + _ LayoutComponent = (*ActionRowComponent)(nil) + _ ContainerSubComponent = (*ActionRowComponent)(nil) + _ ComponentIter = (*ActionRowComponent)(nil) ) -func NewActionRow(components ...InteractiveComponent) ActionRowComponent { - return components +type ActionRowComponent struct { + ID int `json:"id,omitempty"` + Components []InteractiveComponent `json:"components"` } -type ActionRowComponent []InteractiveComponent - func (c ActionRowComponent) MarshalJSON() ([]byte, error) { + type actionRowComponent ActionRowComponent return json.Marshal(struct { - Type ComponentType `json:"type"` - Components []InteractiveComponent `json:"components"` + Type ComponentType `json:"type"` + actionRowComponent }{ - Type: c.Type(), - Components: c, + Type: c.Type(), + actionRowComponent: actionRowComponent(c), }) } func (c *ActionRowComponent) UnmarshalJSON(data []byte) error { - var actionRow struct { + var actionRowComponent struct { + ID int `json:"id,omitempty"` Components []UnmarshalComponent `json:"components"` } - - if err := json.Unmarshal(data, &actionRow); err != nil { + if err := json.Unmarshal(data, &actionRowComponent); 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) - } + c.ID = actionRowComponent.ID + components := make([]InteractiveComponent, 0, len(actionRowComponent.Components)) + for _, component := range actionRowComponent.Components { + components = append(components, component.Component.(InteractiveComponent)) } - + c.Components = components return nil } @@ -160,51 +293,42 @@ func (ActionRowComponent) Type() ComponentType { return ComponentTypeActionRow } -func (ActionRowComponent) component() {} -func (ActionRowComponent) containerComponent() {} - -func (c ActionRowComponent) Components() []InteractiveComponent { - return c +func (c ActionRowComponent) GetID() int { + return c.ID } -// 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) +func (ActionRowComponent) component() {} +func (ActionRowComponent) layoutComponent() {} +func (ActionRowComponent) containerSubComponent() {} + +// SubComponents returns an [iter.Seq[Component]] over the sub Component(s) of the ActionRowComponent. +func (c ActionRowComponent) SubComponents() iter.Seq[Component] { + return func(yield func(Component) bool) { + for _, cc := range c.Components { + if !yield(cc) { + return + } } } - 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 +// WithID returns a new ActionRowComponent with the provided id +func (c ActionRowComponent) WithID(id int) ActionRowComponent { + c.ID = id + return c } -// TextInputs returns all TextInputComponent(s) in the ActionRowComponent -func (c ActionRowComponent) TextInputs() []TextInputComponent { - var textInputs []TextInputComponent - for i := range c { - if textInput, ok := c[i].(TextInputComponent); ok { - textInputs = append(textInputs, textInput) - } - } - return textInputs +// WithComponents returns a new ActionRowComponent with the provided Component(s) +func (c ActionRowComponent) WithComponents(components ...InteractiveComponent) ActionRowComponent { + c.Components = components + return c } -// UpdateComponent returns a new ActionRowComponent with the Component which has the customID replaced -func (c ActionRowComponent) UpdateComponent(customID string, component InteractiveComponent) ActionRowComponent { - for i, cc := range c { - if cc.ID() == customID { - c[i] = component +// UpdateComponent returns a new ActionRowComponent with the Component which has the id replaced with the provided Component. +func (c ActionRowComponent) UpdateComponent(id int, component InteractiveComponent) ActionRowComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components[i] = component return c } } @@ -213,18 +337,24 @@ func (c ActionRowComponent) UpdateComponent(customID string, component Interacti // AddComponents returns a new ActionRowComponent with the provided Component(s) added func (c ActionRowComponent) AddComponents(components ...InteractiveComponent) ActionRowComponent { - return append(c, components...) + c.Components = append(c.Components, components...) + return c } -// 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:]...) +// RemoveComponent returns a new ActionRowComponent with the provided Component which has the provided id removed. +func (c ActionRowComponent) RemoveComponent(id int) ActionRowComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components = append(c.Components[:i], c.Components[i+1:]...) + return c + } } return c } -// ButtonStyle defines how the ButtonComponent looks like (https://discord.com/assets/7bb017ce52cfd6575e21c058feb3883b.png) +// ButtonStyle defines how the ButtonComponent looks like. [Discord Docs] +// +// [Discord Docs]: https://discord.com/developers/docs/interactions/message-components#button-object-button-styles type ButtonStyle int // Supported ButtonStyle(s) @@ -237,7 +367,7 @@ const ( ButtonStylePremium ) -// NewButton creates a new ButtonComponent with the provided parameters. Link ButtonComponent(s) need a URL and other ButtonComponent(s) need a customID +// NewButton creates a new [ButtonComponent] with the provided parameters. Link ButtonComponent(s) need a URL and other ButtonComponent(s) need a customID func NewButton(style ButtonStyle, label string, customID string, url string, skuID snowflake.ID) ButtonComponent { return ButtonComponent{ Style: style, @@ -302,11 +432,13 @@ func NewPremiumButton(skuID snowflake.ID) ButtonComponent { } var ( - _ Component = (*ButtonComponent)(nil) - _ InteractiveComponent = (*ButtonComponent)(nil) + _ Component = (*ButtonComponent)(nil) + _ InteractiveComponent = (*ButtonComponent)(nil) + _ SectionAccessoryComponent = (*ButtonComponent)(nil) ) type ButtonComponent struct { + ID int `json:"id,omitempty"` Style ButtonStyle `json:"style"` Label string `json:"label,omitempty"` Emoji *ComponentEmoji `json:"emoji,omitempty"` @@ -331,18 +463,24 @@ func (ButtonComponent) Type() ComponentType { return ComponentTypeButton } -func (c ButtonComponent) ID() string { +func (c ButtonComponent) GetID() int { + return c.ID +} + +func (c ButtonComponent) GetCustomID() string { return c.CustomID } -func (c ButtonComponent) SetID(id string) InteractiveComponent { - c.CustomID = id +func (ButtonComponent) component() {} +func (ButtonComponent) interactiveComponent() {} +func (ButtonComponent) sectionAccessoryComponent() {} + +// WithID returns a new ButtonComponent with the provided id +func (c ButtonComponent) WithID(id int) ButtonComponent { + c.ID = id return c } -func (ButtonComponent) component() {} -func (ButtonComponent) interactiveComponent() {} - // WithStyle returns a new ButtonComponent with the provided style func (c ButtonComponent) WithStyle(style ButtonStyle) ButtonComponent { c.Style = style @@ -397,11 +535,14 @@ func (c ButtonComponent) WithDisabled(disabled bool) ButtonComponent { return c } -var ( - _ Component = (*TextInputComponent)(nil) - _ InteractiveComponent = (*TextInputComponent)(nil) +type TextInputStyle int + +const ( + TextInputStyleShort TextInputStyle = iota + 1 + TextInputStyleParagraph ) +// NewTextInput creates a new [TextInputComponent] with the provided parameters. func NewTextInput(customID string, style TextInputStyle, label string) TextInputComponent { return TextInputComponent{ CustomID: customID, @@ -410,15 +551,25 @@ func NewTextInput(customID string, style TextInputStyle, label string) TextInput } } +// NewShortTextInput creates a new [TextInputComponent] with [TextInputStyleShort] & the provided parameters func NewShortTextInput(customID string, label string) TextInputComponent { return NewTextInput(customID, TextInputStyleShort, label) } +// NewParagraphTextInput creates a new [TextInputComponent] with [TextInputStyleParagraph] & the provided parameters func NewParagraphTextInput(customID string, label string) TextInputComponent { return NewTextInput(customID, TextInputStyleParagraph, label) } +var ( + _ Component = (*TextInputComponent)(nil) +) + +// TextInputComponent is a component that allows users to input text. [Discord Docs] +// +// [Discord Docs]: https://discord.com/developers/docs/interactions/message-components#text-inputs type TextInputComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Style TextInputStyle `json:"style"` Label string `json:"label"` @@ -444,13 +595,23 @@ func (TextInputComponent) Type() ComponentType { return ComponentTypeTextInput } -func (c TextInputComponent) ID() string { +func (c TextInputComponent) GetID() int { + return c.ID +} + +func (c TextInputComponent) GetCustomID() string { return c.CustomID } func (TextInputComponent) component() {} func (TextInputComponent) interactiveComponent() {} +// WithID returns a new TextInputComponent with the provided id +func (c TextInputComponent) WithID(id int) TextInputComponent { + c.ID = id + return c +} + // WithCustomID returns a new SelectMenuComponent with the provided customID func (c TextInputComponent) WithCustomID(customID string) TextInputComponent { c.CustomID = customID @@ -493,9 +654,678 @@ func (c TextInputComponent) WithValue(value string) TextInputComponent { return c } -type TextInputStyle int +type UnfurledMediaItemLoadingState int const ( - TextInputStyleShort TextInputStyle = iota + 1 - TextInputStyleParagraph + UnfurledMediaItemLoadingStateUnknown UnfurledMediaItemLoadingState = iota + UnfurledMediaItemLoadingStateLoading + UnfurledMediaItemLoadingStateLoadedSuccess + UnfurledMediaItemLoadingStateLoadedNotFound +) + +// UnfurledMediaItem is a media item that can be displayed in a [ThumbnailComponent] or [FileComponent]. +type UnfurledMediaItem struct { + // URL supports arbitrary urls and attachment:// references + URL string `json:"url"` + // ProxyURL is a proxied version of the URL. This can't be set by bots. + ProxyURL string `json:"proxy_url,omitempty"` + // Height is the height of the media item in pixels. This can't be set by bots. + Height int `json:"height,omitempty"` + // Width is the width of the media item in pixels. This can't be set by bots. + Width int `json:"width,omitempty"` + // ContentType is the content type of the media item. This can't be set by bots. + ContentType string `json:"content_type,omitempty"` + // LoadingState is the loading state of the media item. This can't be set by bots. + LoadingState UnfurledMediaItemLoadingState `json:"loading_state,omitempty"` +} + +// NewSection creates a new [SectionComponent] with the provided components. +func NewSection(components ...SectionSubComponent) SectionComponent { + return SectionComponent{ + Components: components, + } +} + +var ( + _ Component = (*SectionComponent)(nil) + _ LayoutComponent = (*SectionComponent)(nil) + _ ContainerSubComponent = (*SectionComponent)(nil) ) + +// SectionComponent is a component that can contain up to 3 TextDisplayComponent(s) and an optional SectionAccessoryComponent. +type SectionComponent struct { + ID int `json:"id,omitempty"` + Components []SectionSubComponent `json:"components"` + Accessory SectionAccessoryComponent `json:"accessory"` +} + +func (c SectionComponent) MarshalJSON() ([]byte, error) { + type sectionComponent SectionComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + sectionComponent + }{ + Type: c.Type(), + sectionComponent: sectionComponent(c), + }) +} + +func (c *SectionComponent) UnmarshalJSON(data []byte) error { + var sectionComponent struct { + ID int `json:"id,omitempty"` + Components []UnmarshalComponent `json:"components"` + Accessory UnmarshalComponent `json:"accessory"` + } + if err := json.Unmarshal(data, §ionComponent); err != nil { + return err + } + + c.ID = sectionComponent.ID + + components := make([]SectionSubComponent, 0, len(sectionComponent.Components)) + for _, component := range sectionComponent.Components { + components = append(components, component.Component.(SectionSubComponent)) + } + c.Components = components + + c.Accessory = sectionComponent.Accessory.Component.(SectionAccessoryComponent) + return nil +} + +func (SectionComponent) Type() ComponentType { + return ComponentTypeSection +} + +func (c SectionComponent) GetID() int { + return c.ID +} + +func (SectionComponent) component() {} +func (SectionComponent) layoutComponent() {} +func (SectionComponent) containerSubComponent() {} + +// SubComponents returns an [iter.Seq[Component]] over the sub Component(s) and Accessory of the SectionComponent. +func (c SectionComponent) SubComponents() iter.Seq[Component] { + return func(yield func(Component) bool) { + for _, cc := range c.Components { + if !yield(cc) { + return + } + } + + if c.Accessory != nil { + if !yield(c.Accessory) { + return + } + } + } +} + +// WithID returns a new SectionComponent with the provided id +func (c SectionComponent) WithID(id int) SectionComponent { + c.ID = id + return c +} + +// WithAccessory returns a new SectionComponent with the provided accessory +func (c SectionComponent) WithAccessory(accessory SectionAccessoryComponent) SectionComponent { + c.Accessory = accessory + return c +} + +// WithComponents returns a new SectionComponent with the provided components +func (c SectionComponent) WithComponents(components ...SectionSubComponent) SectionComponent { + c.Components = components + return c +} + +// UpdateComponent returns a new SectionComponent with the Component which has the id replaced with the provided Component. +func (c SectionComponent) UpdateComponent(id int, component SectionSubComponent) SectionComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components[i] = component + return c + } + } + return c +} + +// AddComponents returns a new SectionComponent with the provided Component(s) added +func (c SectionComponent) AddComponents(components ...SectionSubComponent) SectionComponent { + c.Components = append(c.Components, components...) + return c +} + +// RemoveComponent returns a new SectionComponent with the provided Component which has the provided id removed. +func (c SectionComponent) RemoveComponent(id int) SectionComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components = append(c.Components[:i], c.Components[i+1:]...) + return c + } + } + return c +} + +// NewTextDisplay creates a new [TextDisplayComponent] with the provided content. +func NewTextDisplay(content string) TextDisplayComponent { + return TextDisplayComponent{ + Content: content, + } +} + +var ( + _ Component = (*TextDisplayComponent)(nil) + _ ContainerSubComponent = (*TextDisplayComponent)(nil) + _ SectionSubComponent = (*TextDisplayComponent)(nil) +) + +// TextDisplayComponent is a component that displays text. +type TextDisplayComponent struct { + ID int `json:"id,omitempty"` + Content string `json:"content"` +} + +func (c TextDisplayComponent) MarshalJSON() ([]byte, error) { + type textDisplayComponent TextDisplayComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + textDisplayComponent + }{ + Type: c.Type(), + textDisplayComponent: textDisplayComponent(c), + }) +} + +func (TextDisplayComponent) Type() ComponentType { + return ComponentTypeTextDisplay +} + +func (c TextDisplayComponent) GetID() int { + return c.ID +} + +func (TextDisplayComponent) component() {} +func (TextDisplayComponent) sectionSubComponent() {} +func (TextDisplayComponent) containerSubComponent() {} + +func (c TextDisplayComponent) WithID(i int) SectionSubComponent { + c.ID = i + return c +} + +func (c TextDisplayComponent) WithContent(content string) TextDisplayComponent { + c.Content = content + return c +} + +func NewThumbnail(url string) ThumbnailComponent { + return ThumbnailComponent{ + Media: UnfurledMediaItem{ + URL: url, + }, + } +} + +var ( + _ Component = (*ThumbnailComponent)(nil) + _ SectionAccessoryComponent = (*ThumbnailComponent)(nil) +) + +type ThumbnailComponent struct { + ID int `json:"id,omitempty"` + Media UnfurledMediaItem `json:"media"` + Description string `json:"description,omitempty"` + Spoiler bool `json:"spoiler,omitempty"` +} + +func (c ThumbnailComponent) MarshalJSON() ([]byte, error) { + type thumbnailComponent ThumbnailComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + thumbnailComponent + }{ + Type: c.Type(), + thumbnailComponent: thumbnailComponent(c), + }) +} + +func (ThumbnailComponent) Type() ComponentType { + return ComponentTypeThumbnail +} + +func (c ThumbnailComponent) GetID() int { + return c.ID +} + +func (ThumbnailComponent) component() {} +func (ThumbnailComponent) sectionAccessoryComponent() {} + +func (c ThumbnailComponent) WithID(id int) ThumbnailComponent { + c.ID = id + return c +} + +func (c ThumbnailComponent) WithMediaURL(url string) ThumbnailComponent { + c.Media.URL = url + return c +} + +func (c ThumbnailComponent) WithDescription(description string) ThumbnailComponent { + c.Description = description + return c +} + +func (c ThumbnailComponent) WithSpoiler(spoiler bool) ThumbnailComponent { + c.Spoiler = spoiler + return c +} + +type MediaGalleryItem struct { + Media UnfurledMediaItem `json:"media"` + Description string `json:"description,omitempty"` + Spoiler bool `json:"spoiler,omitempty"` +} + +// NewMediaGallery creates a new [MediaGalleryComponent] with the provided items. +func NewMediaGallery(items ...MediaGalleryItem) MediaGalleryComponent { + return MediaGalleryComponent{ + Items: items, + } +} + +var ( + _ Component = (*MediaGalleryComponent)(nil) + _ LayoutComponent = (*MediaGalleryComponent)(nil) + _ ContainerSubComponent = (*MediaGalleryComponent)(nil) +) + +// MediaGalleryComponent is a component that displays a gallery of media. +type MediaGalleryComponent struct { + ID int `json:"id,omitempty"` + Items []MediaGalleryItem `json:"items"` +} + +func (c MediaGalleryComponent) MarshalJSON() ([]byte, error) { + type mediaGalleryComponent MediaGalleryComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + mediaGalleryComponent + }{ + Type: c.Type(), + mediaGalleryComponent: mediaGalleryComponent(c), + }) +} + +func (MediaGalleryComponent) Type() ComponentType { + return ComponentTypeMediaGallery +} + +func (c MediaGalleryComponent) GetID() int { + return c.ID +} + +func (MediaGalleryComponent) component() {} +func (MediaGalleryComponent) layoutComponent() {} +func (MediaGalleryComponent) containerSubComponent() {} + +// WithID returns a new MediaGalleryComponent with the provided id +func (c MediaGalleryComponent) WithID(id int) MediaGalleryComponent { + c.ID = id + return c +} + +// WithItems returns a new MediaGalleryComponent with the provided items +func (c MediaGalleryComponent) WithItems(items ...MediaGalleryItem) MediaGalleryComponent { + c.Items = items + return c +} + +type SeparatorSpacingSize int + +const ( + SeparatorSpacingSizeSmall SeparatorSpacingSize = iota + 1 + SeparatorSpacingSizeLarge +) + +// NewSmallSeparator creates a new [SeparatorComponent] with the small spacing. +func NewSmallSeparator() SeparatorComponent { + return NewSeparator(SeparatorSpacingSizeSmall) +} + +// NewLargeSeparator creates a new [SeparatorComponent] with the large spacing. +func NewLargeSeparator() SeparatorComponent { + return NewSeparator(SeparatorSpacingSizeLarge) +} + +// NewSeparator creates a new [SeparatorComponent] with the provided spacing. +func NewSeparator(spacing SeparatorSpacingSize) SeparatorComponent { + return SeparatorComponent{ + Spacing: spacing, + } +} + +var ( + _ Component = (*SeparatorComponent)(nil) + _ LayoutComponent = (*MediaGalleryComponent)(nil) + _ ContainerSubComponent = (*MediaGalleryComponent)(nil) +) + +// SeparatorComponent is a component that adds a visual divider between components. +type SeparatorComponent struct { + ID int `json:"id,omitempty"` + // Divider determines if the separator should have a divider line. (default: true) + Divider *bool `json:"divider,omitempty"` + Spacing SeparatorSpacingSize `json:"spacing,omitempty"` +} + +func (c SeparatorComponent) MarshalJSON() ([]byte, error) { + type separatorComponent SeparatorComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + separatorComponent + }{ + Type: c.Type(), + separatorComponent: separatorComponent(c), + }) +} + +func (SeparatorComponent) Type() ComponentType { + return ComponentTypeSeparator +} + +func (c SeparatorComponent) GetID() int { + return c.ID +} + +func (SeparatorComponent) component() {} +func (SeparatorComponent) layoutComponent() {} +func (SeparatorComponent) containerSubComponent() {} + +func (c SeparatorComponent) WithID(i int) LayoutComponent { + c.ID = i + return c +} + +func (c SeparatorComponent) WithDivider(divider bool) SeparatorComponent { + c.Divider = ÷r + return c +} + +func (c SeparatorComponent) WithSpacing(spacing SeparatorSpacingSize) SeparatorComponent { + c.Spacing = spacing + return c +} + +// NewFileComponent creates a new [FileComponent] with the provided url. +func NewFileComponent(url string) FileComponent { + return FileComponent{ + File: UnfurledMediaItem{ + URL: url, + }, + } +} + +var ( + _ Component = (*FileComponent)(nil) + _ ContainerSubComponent = (*FileComponent)(nil) + _ LayoutComponent = (*FileComponent)(nil) +) + +// FileComponent is a component that displays a file. +type FileComponent struct { + ID int `json:"id,omitempty"` + // File only supports attachment:// references + File UnfurledMediaItem `json:"file"` + Spoiler bool `json:"spoiler,omitempty"` +} + +func (c FileComponent) MarshalJSON() ([]byte, error) { + type fileComponent FileComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + fileComponent + }{ + Type: c.Type(), + fileComponent: fileComponent(c), + }) +} + +func (FileComponent) Type() ComponentType { + return ComponentTypeFile +} + +func (c FileComponent) GetID() int { + return c.ID +} + +func (FileComponent) component() {} +func (FileComponent) layoutComponent() {} +func (FileComponent) containerSubComponent() {} + +func (c FileComponent) WithID(id int) FileComponent { + c.ID = id + return c +} + +func (c FileComponent) WithMediaURL(url string) FileComponent { + c.File.URL = url + return c +} + +func (c FileComponent) WithSpoiler(spoiler bool) FileComponent { + c.Spoiler = spoiler + return c +} + +// NewContainer creates a new [ContainerComponent] with the provided components. +func NewContainer(components ...ContainerSubComponent) ContainerComponent { + return ContainerComponent{ + Components: components, + } +} + +var ( + _ Component = (*ContainerComponent)(nil) + _ LayoutComponent = (*ContainerComponent)(nil) +) + +// ContainerComponent is a component which lets you group components together. +// It looks similar to embeds and supports an accent color and spoiler state. +type ContainerComponent struct { + ID int `json:"id,omitempty"` + // AccentColor is the color of the left border of the container. (use 0 for no color) + AccentColor int `json:"accent_color,omitempty"` + Spoiler bool `json:"spoiler,omitempty"` + Components []ContainerSubComponent `json:"components"` +} + +func (c ContainerComponent) MarshalJSON() ([]byte, error) { + type containerComponent ContainerComponent + return json.Marshal(struct { + Type ComponentType `json:"type"` + containerComponent + }{ + Type: c.Type(), + containerComponent: containerComponent(c), + }) +} + +func (c *ContainerComponent) UnmarshalJSON(data []byte) error { + var containerComponent struct { + ID int `json:"id,omitempty"` + AccentColor int `json:"accent_color,omitempty"` + Spoiler bool `json:"spoiler,omitempty"` + Components []UnmarshalComponent `json:"components"` + } + if err := json.Unmarshal(data, &containerComponent); err != nil { + return err + } + + c.ID = containerComponent.ID + c.AccentColor = containerComponent.AccentColor + c.Spoiler = containerComponent.Spoiler + + components := make([]ContainerSubComponent, 0, len(containerComponent.Components)) + for _, component := range containerComponent.Components { + components = append(components, component.Component.(ContainerSubComponent)) + } + c.Components = components + return nil +} + +func (ContainerComponent) Type() ComponentType { + return ComponentTypeContainer +} + +func (c ContainerComponent) GetID() int { + return c.ID +} + +func (ContainerComponent) component() {} +func (ContainerComponent) layoutComponent() {} + +func (c ContainerComponent) WithID(id int) ContainerComponent { + c.ID = id + return c +} + +func (c ContainerComponent) WithAccentColor(accentColor int) ContainerComponent { + c.AccentColor = accentColor + return c +} + +func (c ContainerComponent) WithSpoiler(spoiler bool) ContainerComponent { + c.Spoiler = spoiler + return c +} + +func (c ContainerComponent) SubComponents() iter.Seq[Component] { + return func(yield func(Component) bool) { + for _, cc := range c.Components { + if !yield(cc) { + return + } + if ic, ok := cc.(ComponentIter); ok { + for cc := range ic.SubComponents() { + if !yield(cc) { + return + } + } + } + } + } +} + +func (c ContainerComponent) WithComponents(components ...ContainerSubComponent) ContainerComponent { + c.Components = components + return c +} + +func (c ContainerComponent) UpdateComponent(id int, component ContainerSubComponent) ContainerComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components[i] = component + return c + } + } + return c +} + +func (c ContainerComponent) AddComponents(components ...ContainerSubComponent) ContainerComponent { + c.Components = append(c.Components, components...) + return c +} + +func (c ContainerComponent) RemoveComponent(id int) ContainerComponent { + for i, cc := range c.Components { + if cc.GetID() == id { + c.Components = append(c.Components[:i], c.Components[i+1:]...) + return c + } + } + return c +} + +// NewUnknownComponent creates a new [UnknownComponent] with the provided type and data. +// This is useful for handling unknown components, but should be avoided if possible. +func NewUnknownComponent(componentType ComponentType, data json.RawMessage) UnknownComponent { + return UnknownComponent{ + ComponentType: componentType, + Data: data, + } +} + +var ( + _ Component = (*UnknownComponent)(nil) + _ InteractiveComponent = (*UnknownComponent)(nil) + _ LayoutComponent = (*UnknownComponent)(nil) + _ SelectMenuComponent = (*UnknownComponent)(nil) + _ SectionSubComponent = (*UnknownComponent)(nil) + _ SectionAccessoryComponent = (*UnknownComponent)(nil) + _ ContainerSubComponent = (*UnknownComponent)(nil) +) + +// UnknownComponent is a component that is not recognized by the library. +type UnknownComponent struct { + ComponentType ComponentType + ID int + Data json.RawMessage +} + +func (c UnknownComponent) MarshalJSON() ([]byte, error) { + data, err := json.Marshal(struct { + Type ComponentType `json:"type"` + ID int `json:"id,omitempty"` + }{ + Type: c.ComponentType, + ID: c.ID, + }) + if err != nil { + return nil, err + } + + return json.Merge(c.Data, data) +} + +func (c *UnknownComponent) UnmarshalJSON(data []byte) error { + var unknownComponent struct { + Type ComponentType `json:"type"` + ID int `json:"id,omitempty"` + } + if err := json.Unmarshal(data, &unknownComponent); err != nil { + return err + } + + c.ComponentType = unknownComponent.Type + c.ID = unknownComponent.ID + c.Data = data + return nil +} + +func (c UnknownComponent) Type() ComponentType { + return c.ComponentType +} + +func (c UnknownComponent) GetID() int { + return c.ID +} + +func (c UnknownComponent) GetCustomID() string { + var data struct { + CustomID string `json:"custom_id"` + } + if err := json.Unmarshal(c.Data, &data); err != nil { + return "" + } + + return data.CustomID +} + +func (UnknownComponent) component() {} +func (UnknownComponent) interactiveComponent() {} +func (UnknownComponent) layoutComponent() {} +func (UnknownComponent) selectMenuComponent() {} +func (UnknownComponent) containerSubComponent() {} +func (UnknownComponent) sectionSubComponent() {} +func (UnknownComponent) sectionAccessoryComponent() {} diff --git a/discord/component_iter.go b/discord/component_iter.go new file mode 100644 index 000000000..5e5b125ab --- /dev/null +++ b/discord/component_iter.go @@ -0,0 +1,24 @@ +package discord + +import "iter" + +// componentIter returns an iterator over the given components and their children. +func componentIter(components []LayoutComponent) iter.Seq[Component] { + return func(yield func(Component) bool) { + for _, c := range components { + if !yield(c) { + return + } + ic, ok := c.(ComponentIter) + if !ok { + continue + } + + for cc := range ic.SubComponents() { + if !yield(cc) { + return + } + } + } + } +} diff --git a/discord/component_iter_test.go b/discord/component_iter_test.go new file mode 100644 index 000000000..e9d821d13 --- /dev/null +++ b/discord/component_iter_test.go @@ -0,0 +1,39 @@ +package discord + +import "testing" + +func TestComponentIter(t *testing.T) { + components := []LayoutComponent{ + NewActionRow( + NewPrimaryButton("button1", "button1").WithID(2), + NewSecondaryButton("button2", "button2").WithID(3), + ).WithID(1), + NewSmallSeparator().WithID(4), + NewActionRow( + NewStringSelectMenu("select1", "select1", + StringSelectMenuOption{Label: "option1", Value: "option1"}, + StringSelectMenuOption{Label: "option2", Value: "option21"}, + ).WithID(6), + ).WithID(5), + NewSection( + NewTextDisplay("text1").WithID(8), + NewTextDisplay("text2").WithID(9), + ).WithID(7), + NewContainer( + NewActionRow( + NewPrimaryButton("button2", "button2").WithID(12), + ).WithID(11), + NewSection( + NewTextDisplay("text3").WithID(14), + ).WithID(13), + ).WithID(10), + } + + i := 1 + for c := range componentIter(components) { + if c.GetID() != i { + t.Errorf("expected component with ID %d, got %d", i, c.GetID()) + } + i++ + } +} diff --git a/discord/interaction_component.go b/discord/interaction_component.go index 8ab312237..803fcfe70 100644 --- a/discord/interaction_component.go +++ b/discord/interaction_component.go @@ -40,32 +40,32 @@ func (i *ComponentInteraction) UnmarshalJSON(data []byte) error { ) switch cType.Type { case ComponentTypeButton: - v := ButtonInteractionData{} + var v ButtonInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v case ComponentTypeStringSelectMenu: - v := StringSelectMenuInteractionData{} + var v StringSelectMenuInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v case ComponentTypeUserSelectMenu: - v := UserSelectMenuInteractionData{} + var v UserSelectMenuInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v case ComponentTypeRoleSelectMenu: - v := RoleSelectMenuInteractionData{} + var v RoleSelectMenuInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v case ComponentTypeMentionableSelectMenu: - v := MentionableSelectMenuInteractionData{} + var v MentionableSelectMenuInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v case ComponentTypeChannelSelectMenu: - v := ChannelSelectMenuInteractionData{} + var v ChannelSelectMenuInteractionData err = json.Unmarshal(interaction.Data, &v) interactionData = v diff --git a/discord/interaction_modal_submit.go b/discord/interaction_modal_submit.go index 204fdb458..78fc87cca 100644 --- a/discord/interaction_modal_submit.go +++ b/discord/interaction_modal_submit.go @@ -1,6 +1,10 @@ package discord -import "github.com/disgoorg/json" +import ( + "iter" + + "github.com/disgoorg/json" +) var ( _ Interaction = (*ModalSubmitInteraction)(nil) @@ -76,37 +80,41 @@ func (ModalSubmitInteraction) Type() InteractionType { func (ModalSubmitInteraction) interaction() {} type ModalSubmitInteractionData struct { - CustomID string `json:"custom_id"` - Components map[string]InteractiveComponent `json:"components"` + CustomID string `json:"custom_id"` + Components []LayoutComponent `json:"components"` } func (d *ModalSubmitInteractionData) UnmarshalJSON(data []byte) error { - type modalSubmitInteractionData ModalSubmitInteractionData var iData struct { + CustomID string `json:"custom_id"` Components []UnmarshalComponent `json:"components"` - modalSubmitInteractionData } if err := json.Unmarshal(data, &iData); err != nil { return err } - *d = ModalSubmitInteractionData(iData.modalSubmitInteractionData) + d.CustomID = iData.CustomID - if len(iData.Components) > 0 { - d.Components = make(map[string]InteractiveComponent, len(iData.Components)) - for _, containerComponent := range iData.Components { - for _, component := range containerComponent.Component.(ContainerComponent).Components() { - d.Components[component.ID()] = component - } - } + components := make([]LayoutComponent, 0, len(iData.Components)) + for _, containerComponent := range iData.Components { + components = append(components, containerComponent.Component.(LayoutComponent)) } + d.Components = components return nil } +func (d ModalSubmitInteractionData) AllComponents() iter.Seq[Component] { + return componentIter(d.Components) +} + func (d ModalSubmitInteractionData) Component(customID string) (InteractiveComponent, bool) { - component, ok := d.Components[customID] - return component, ok + for component := range d.AllComponents() { + if ic, ok := component.(InteractiveComponent); ok && ic.GetCustomID() == customID { + return ic, true + } + } + return nil, false } func (d ModalSubmitInteractionData) TextInputComponent(customID string) (TextInputComponent, bool) { diff --git a/discord/message.go b/discord/message.go index 9435d827e..eaa52f4dd 100644 --- a/discord/message.go +++ b/discord/message.go @@ -3,6 +3,7 @@ package discord import ( "bytes" "fmt" + "iter" "strconv" "time" @@ -101,7 +102,7 @@ type Message struct { Attachments []Attachment `json:"attachments"` TTS bool `json:"tts"` Embeds []Embed `json:"embeds,omitempty"` - Components []ContainerComponent `json:"components,omitempty"` + Components []LayoutComponent `json:"components,omitempty"` CreatedAt time.Time `json:"timestamp"` Mentions []User `json:"mentions"` MentionEveryone bool `json:"mention_everyone"` @@ -159,190 +160,12 @@ func (m *Message) UnmarshalJSON(data []byte) error { return nil } -// ActionRows returns all ActionRowComponent(s) from this Message -func (m Message) ActionRows() []ActionRowComponent { - var actionRows []ActionRowComponent - for i := range m.Components { - if actionRow, ok := m.Components[i].(ActionRowComponent); ok { - actionRows = append(actionRows, actionRow) - } - } - return actionRows -} - -// InteractiveComponents returns the InteractiveComponent(s) from this Message -func (m Message) InteractiveComponents() []InteractiveComponent { - var interactiveComponents []InteractiveComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - interactiveComponents = append(interactiveComponents, m.Components[i].Components()[ii]) - } - } - return interactiveComponents -} - -// ComponentByID returns the Component with the specific CustomID -func (m Message) ComponentByID(customID string) InteractiveComponent { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if m.Components[i].Components()[ii].ID() == customID { - return m.Components[i].Components()[ii] - } - } - } - return nil -} - -// Buttons returns all ButtonComponent(s) from this Message -func (m Message) Buttons() []ButtonComponent { - var buttons []ButtonComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if button, ok := m.Components[i].Components()[ii].(ButtonComponent); ok { - buttons = append(buttons, button) - } - } - } - return buttons -} - -// ButtonByID returns a ButtonComponent with the specific customID from this Message -func (m Message) ButtonByID(customID string) (ButtonComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if button, ok := m.Components[i].Components()[ii].(ButtonComponent); ok && button.ID() == customID { - return button, true - } - } - } - return ButtonComponent{}, false -} - -// SelectMenus returns all SelectMenuComponent(s) from this Message -func (m Message) SelectMenus() []SelectMenuComponent { - var selectMenus []SelectMenuComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if selectMenu, ok := m.Components[i].Components()[ii].(SelectMenuComponent); ok { - selectMenus = append(selectMenus, selectMenu) - } - } - } - return selectMenus -} - -// SelectMenuByID returns a SelectMenuComponent with the specific customID from this Message -func (m Message) SelectMenuByID(customID string) (SelectMenuComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if selectMenu, ok := m.Components[i].Components()[ii].(SelectMenuComponent); ok && selectMenu.ID() == customID { - return selectMenu, true - } - } - } - return nil, false -} - -// UserSelectMenus returns all UserSelectMenuComponent(s) from this Message -func (m Message) UserSelectMenus() []UserSelectMenuComponent { - var userSelectMenus []UserSelectMenuComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if userSelectMenu, ok := m.Components[i].Components()[ii].(UserSelectMenuComponent); ok { - userSelectMenus = append(userSelectMenus, userSelectMenu) - } - } - } - return userSelectMenus -} - -// UserSelectMenuByID returns a UserSelectMenuComponent with the specific customID from this Message -func (m Message) UserSelectMenuByID(customID string) (UserSelectMenuComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if userSelectMenu, ok := m.Components[i].Components()[ii].(UserSelectMenuComponent); ok && userSelectMenu.ID() == customID { - return userSelectMenu, true - } - } - } - return UserSelectMenuComponent{}, false -} - -// RoleSelectMenus returns all RoleSelectMenuComponent(s) from this Message -func (m Message) RoleSelectMenus() []RoleSelectMenuComponent { - var roleSelectMenus []RoleSelectMenuComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if roleSelectMenu, ok := m.Components[i].Components()[ii].(RoleSelectMenuComponent); ok { - roleSelectMenus = append(roleSelectMenus, roleSelectMenu) - } - } - } - return roleSelectMenus -} - -// RoleSelectMenuByID returns a RoleSelectMenuComponent with the specific customID from this Message -func (m Message) RoleSelectMenuByID(customID string) (RoleSelectMenuComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if roleSelectMenu, ok := m.Components[i].Components()[ii].(RoleSelectMenuComponent); ok && roleSelectMenu.ID() == customID { - return roleSelectMenu, true - } - } - } - return RoleSelectMenuComponent{}, false -} - -// MentionableSelectMenus returns all MentionableSelectMenuComponent(s) from this Message -func (m Message) MentionableSelectMenus() []MentionableSelectMenuComponent { - var mentionableSelectMenus []MentionableSelectMenuComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if mentionableSelectMenu, ok := m.Components[i].Components()[ii].(MentionableSelectMenuComponent); ok { - mentionableSelectMenus = append(mentionableSelectMenus, mentionableSelectMenu) - } - } - } - return mentionableSelectMenus -} - -// MentionableSelectMenuByID returns a MentionableSelectMenuComponent with the specific customID from this Message -func (m Message) MentionableSelectMenuByID(customID string) (MentionableSelectMenuComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if mentionableSelectMenu, ok := m.Components[i].Components()[ii].(MentionableSelectMenuComponent); ok && mentionableSelectMenu.ID() == customID { - return mentionableSelectMenu, true - } - } - } - return MentionableSelectMenuComponent{}, false -} - -// ChannelSelectMenus returns all ChannelSelectMenuComponent(s) from this Message -func (m Message) ChannelSelectMenus() []ChannelSelectMenuComponent { - var channelSelectMenus []ChannelSelectMenuComponent - for i := range m.Components { - for ii := range m.Components[i].Components() { - if channelSelectMenu, ok := m.Components[i].Components()[ii].(ChannelSelectMenuComponent); ok { - channelSelectMenus = append(channelSelectMenus, channelSelectMenu) - } - } - } - return channelSelectMenus -} - -// ChannelSelectMenuByID returns a ChannelSelectMenuComponent with the specific customID from this Message -func (m Message) ChannelSelectMenuByID(customID string) (ChannelSelectMenuComponent, bool) { - for i := range m.Components { - for ii := range m.Components[i].Components() { - if channelSelectMenu, ok := m.Components[i].Components()[ii].(ChannelSelectMenuComponent); ok && channelSelectMenu.ID() == customID { - return channelSelectMenu, true - } - } - } - return ChannelSelectMenuComponent{}, false +// AllComponents returns an [iter.Seq] of all components in the message. +func (m Message) AllComponents() iter.Seq[Component] { + return componentIter(m.Components) } +// JumpURL returns the URL which can be used to jump to the message in the discord client. func (m Message) JumpURL() string { guildID := "@me" if m.GuildID != nil { @@ -439,18 +262,18 @@ type MessageSnapshot struct { } type PartialMessage struct { - Type MessageType `json:"type"` - Content string `json:"content,omitempty"` - Embeds []Embed `json:"embeds,omitempty"` - Attachments []Attachment `json:"attachments"` - CreatedAt time.Time `json:"timestamp"` - EditedTimestamp *time.Time `json:"edited_timestamp"` - Flags MessageFlags `json:"flags"` - Mentions []User `json:"mentions"` - MentionRoles []snowflake.ID `json:"mention_roles"` - Stickers []Sticker `json:"stickers"` - StickerItems []MessageSticker `json:"sticker_items,omitempty"` - Components []ContainerComponent `json:"components,omitempty"` + Type MessageType `json:"type"` + Content string `json:"content,omitempty"` + Embeds []Embed `json:"embeds,omitempty"` + Attachments []Attachment `json:"attachments"` + CreatedAt time.Time `json:"timestamp"` + EditedTimestamp *time.Time `json:"edited_timestamp"` + Flags MessageFlags `json:"flags"` + Mentions []User `json:"mentions"` + MentionRoles []snowflake.ID `json:"mention_roles"` + Stickers []Sticker `json:"stickers"` + StickerItems []MessageSticker `json:"sticker_items,omitempty"` + Components []LayoutComponent `json:"components,omitempty"` } func (m *PartialMessage) UnmarshalJSON(data []byte) error { @@ -473,6 +296,10 @@ func (m *PartialMessage) UnmarshalJSON(data []byte) error { return nil } +func (m PartialMessage) AllComponents() iter.Seq[Component] { + return componentIter(m.Components) +} + // MessageInteraction is sent on the Message object when the message is a response to an interaction type MessageInteraction struct { ID snowflake.ID `json:"id"` @@ -505,6 +332,7 @@ const ( MessageFlagSuppressNotifications MessageFlagIsVoiceMessage MessageFlagHasSnapshot + MessageFlagIsComponentsV2 MessageFlagsNone MessageFlags = 0 ) @@ -560,12 +388,12 @@ type MessageCall struct { EndedTimestamp *time.Time `json:"ended_timestamp"` } -func unmarshalComponents(components []UnmarshalComponent) []ContainerComponent { - containerComponents := make([]ContainerComponent, len(components)) +func unmarshalComponents(components []UnmarshalComponent) []LayoutComponent { + c := make([]LayoutComponent, len(components)) for i := range components { - containerComponents[i] = components[i].Component.(ContainerComponent) + c[i] = components[i].Component.(LayoutComponent) } - return containerComponents + return c } // Nonce is a string or int used when sending a message to discord. diff --git a/discord/message_create.go b/discord/message_create.go index e21cc4cba..15f7e2b03 100644 --- a/discord/message_create.go +++ b/discord/message_create.go @@ -6,19 +6,19 @@ import ( // MessageCreate is the struct to create a new Message with type MessageCreate struct { - Nonce string `json:"nonce,omitempty"` - Content string `json:"content,omitempty"` - TTS bool `json:"tts,omitempty"` - Embeds []Embed `json:"embeds,omitempty"` - Components []ContainerComponent `json:"components,omitempty"` - StickerIDs []snowflake.ID `json:"sticker_ids,omitempty"` - Files []*File `json:"-"` - Attachments []AttachmentCreate `json:"attachments,omitempty"` - AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` - MessageReference *MessageReference `json:"message_reference,omitempty"` - Flags MessageFlags `json:"flags,omitempty"` - EnforceNonce bool `json:"enforce_nonce,omitempty"` - Poll *PollCreate `json:"poll,omitempty"` + Nonce string `json:"nonce,omitempty"` + Content string `json:"content,omitempty"` + TTS bool `json:"tts,omitempty"` + Embeds []Embed `json:"embeds,omitempty"` + Components []LayoutComponent `json:"components,omitempty"` + StickerIDs []snowflake.ID `json:"sticker_ids,omitempty"` + Files []*File `json:"-"` + Attachments []AttachmentCreate `json:"attachments,omitempty"` + AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` + MessageReference *MessageReference `json:"message_reference,omitempty"` + Flags MessageFlags `json:"flags,omitempty"` + EnforceNonce bool `json:"enforce_nonce,omitempty"` + Poll *PollCreate `json:"poll,omitempty"` } func (MessageCreate) interactionCallbackData() {} diff --git a/discord/message_create_builder.go b/discord/message_create_builder.go index cf1178c17..5303b0afa 100644 --- a/discord/message_create_builder.go +++ b/discord/message_create_builder.go @@ -84,14 +84,14 @@ func (b *MessageCreateBuilder) RemoveEmbed(i int) *MessageCreateBuilder { return b } -// SetContainerComponents sets the discord.ContainerComponent(s) of the Message -func (b *MessageCreateBuilder) SetContainerComponents(containerComponents ...ContainerComponent) *MessageCreateBuilder { - b.Components = containerComponents +// SetComponents sets the discord.LayoutComponent(s) of the Message +func (b *MessageCreateBuilder) SetComponents(LayoutComponents ...LayoutComponent) *MessageCreateBuilder { + b.Components = LayoutComponents return b } -// SetContainerComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) -func (b *MessageCreateBuilder) SetContainerComponent(i int, container ContainerComponent) *MessageCreateBuilder { +// SetComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) +func (b *MessageCreateBuilder) SetComponent(i int, container LayoutComponent) *MessageCreateBuilder { if len(b.Components) > i { b.Components[i] = container } @@ -100,27 +100,27 @@ func (b *MessageCreateBuilder) SetContainerComponent(i int, container ContainerC // AddActionRow adds a new discord.ActionRowComponent with the provided discord.InteractiveComponent(s) to the Message func (b *MessageCreateBuilder) AddActionRow(components ...InteractiveComponent) *MessageCreateBuilder { - b.Components = append(b.Components, ActionRowComponent(components)) + b.Components = append(b.Components, ActionRowComponent{Components: components}) return b } -// AddContainerComponents adds the discord.ContainerComponent(s) to the Message -func (b *MessageCreateBuilder) AddContainerComponents(containers ...ContainerComponent) *MessageCreateBuilder { +// AddComponents adds the discord.LayoutComponent(s) to the Message +func (b *MessageCreateBuilder) AddComponents(containers ...LayoutComponent) *MessageCreateBuilder { b.Components = append(b.Components, containers...) return b } -// RemoveContainerComponent removes a discord.ActionRowComponent from the Message -func (b *MessageCreateBuilder) RemoveContainerComponent(i int) *MessageCreateBuilder { +// RemoveComponent removes a discord.ActionRowComponent from the Message +func (b *MessageCreateBuilder) RemoveComponent(i int) *MessageCreateBuilder { 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 Message -func (b *MessageCreateBuilder) ClearContainerComponents() *MessageCreateBuilder { - b.Components = []ContainerComponent{} +// ClearComponents removes all the discord.LayoutComponent(s) of the Message +func (b *MessageCreateBuilder) ClearComponents() *MessageCreateBuilder { + b.Components = []LayoutComponent{} return b } @@ -241,6 +241,16 @@ func (b *MessageCreateBuilder) SetEphemeral(ephemeral bool) *MessageCreateBuilde return b } +// SetIsComponentsV2 adds/removes discord.MessageFlagIsComponentsV2 to the Message flags +func (b *MessageCreateBuilder) SetIsComponentsV2(isComponentV2 bool) *MessageCreateBuilder { + if isComponentV2 { + b.Flags = b.Flags.Add(MessageFlagIsComponentsV2) + } else { + b.Flags = b.Flags.Remove(MessageFlagIsComponentsV2) + } + return b +} + // SetSuppressEmbeds adds/removes discord.MessageFlagSuppressEmbeds to the Message flags func (b *MessageCreateBuilder) SetSuppressEmbeds(suppressEmbeds bool) *MessageCreateBuilder { if suppressEmbeds { diff --git a/discord/message_update.go b/discord/message_update.go index ddcd48a09..b7454c713 100644 --- a/discord/message_update.go +++ b/discord/message_update.go @@ -2,12 +2,12 @@ package discord // MessageUpdate is used to edit a Message type MessageUpdate struct { - Content *string `json:"content,omitempty"` - Embeds *[]Embed `json:"embeds,omitempty"` - Components *[]ContainerComponent `json:"components,omitempty"` - Attachments *[]AttachmentUpdate `json:"attachments,omitempty"` - Files []*File `json:"-"` - AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` + Content *string `json:"content,omitempty"` + Embeds *[]Embed `json:"embeds,omitempty"` + Components *[]LayoutComponent `json:"components,omitempty"` + Attachments *[]AttachmentUpdate `json:"attachments,omitempty"` + Files []*File `json:"-"` + AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` // Flags are the MessageFlags of the message. // Be careful not to override the current flags when editing messages from other users - this will result in a permission error. // Use MessageFlags.Add for flags like discord.MessageFlagSuppressEmbeds. diff --git a/discord/message_update_builder.go b/discord/message_update_builder.go index c81ca37c5..a3a0d305f 100644 --- a/discord/message_update_builder.go +++ b/discord/message_update_builder.go @@ -79,19 +79,19 @@ func (b *MessageUpdateBuilder) RemoveEmbed(i int) *MessageUpdateBuilder { return b } -// SetContainerComponents sets the discord.ContainerComponent(s) of the Message -func (b *MessageUpdateBuilder) SetContainerComponents(containerComponents ...ContainerComponent) *MessageUpdateBuilder { +// SetComponents sets the discord.LayoutComponent(s) of the Message +func (b *MessageUpdateBuilder) SetComponents(LayoutComponents ...LayoutComponent) *MessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } - *b.Components = containerComponents + *b.Components = LayoutComponents return b } -// SetContainerComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) -func (b *MessageUpdateBuilder) SetContainerComponent(i int, container ContainerComponent) *MessageUpdateBuilder { +// SetComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) +func (b *MessageUpdateBuilder) SetComponent(i int, container LayoutComponent) *MessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } if len(*b.Components) > i { (*b.Components)[i] = container @@ -102,23 +102,23 @@ func (b *MessageUpdateBuilder) SetContainerComponent(i int, container ContainerC // AddActionRow adds a new discord.ActionRowComponent with the provided discord.InteractiveComponent(s) to the Message func (b *MessageUpdateBuilder) AddActionRow(components ...InteractiveComponent) *MessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } - *b.Components = append(*b.Components, ActionRowComponent(components)) + *b.Components = append(*b.Components, ActionRowComponent{Components: components}) return b } -// AddContainerComponents adds the discord.ContainerComponent(s) to the Message -func (b *MessageUpdateBuilder) AddContainerComponents(containers ...ContainerComponent) *MessageUpdateBuilder { +// AddComponents adds the discord.LayoutComponent(s) to the Message +func (b *MessageUpdateBuilder) AddComponents(containers ...LayoutComponent) *MessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } *b.Components = append(*b.Components, containers...) return b } -// RemoveContainerComponent removes a discord.ContainerComponent from the Message -func (b *MessageUpdateBuilder) RemoveContainerComponent(i int) *MessageUpdateBuilder { +// RemoveComponent removes a discord.LayoutComponent from the Message +func (b *MessageUpdateBuilder) RemoveComponent(i int) *MessageUpdateBuilder { if b.Components == nil { return b } @@ -128,9 +128,9 @@ func (b *MessageUpdateBuilder) RemoveContainerComponent(i int) *MessageUpdateBui return b } -// ClearContainerComponents removes all the discord.ContainerComponent(s) of the Message -func (b *MessageUpdateBuilder) ClearContainerComponents() *MessageUpdateBuilder { - b.Components = &[]ContainerComponent{} +// ClearComponents removes all the discord.LayoutComponent(s) of the Message +func (b *MessageUpdateBuilder) ClearComponents() *MessageUpdateBuilder { + b.Components = &[]LayoutComponent{} return b } diff --git a/discord/modal_create.go b/discord/modal_create.go index 21291e362..42e481806 100644 --- a/discord/modal_create.go +++ b/discord/modal_create.go @@ -3,9 +3,9 @@ package discord var _ InteractionResponseData = (*ModalCreate)(nil) type ModalCreate struct { - CustomID string `json:"custom_id"` - Title string `json:"title"` - Components []ContainerComponent `json:"components"` + CustomID string `json:"custom_id"` + Title string `json:"title"` + Components []LayoutComponent `json:"components"` } func (ModalCreate) interactionCallbackData() {} @@ -31,14 +31,14 @@ func (b *ModalCreateBuilder) SetTitle(title string) *ModalCreateBuilder { return b } -// SetContainerComponents sets the discord.ContainerComponent(s) of the ModalCreate -func (b *ModalCreateBuilder) SetContainerComponents(containerComponents ...ContainerComponent) *ModalCreateBuilder { - b.Components = containerComponents +// SetComponents sets the discord.LayoutComponent(s) of the ModalCreate +func (b *ModalCreateBuilder) SetComponents(LayoutComponents ...LayoutComponent) *ModalCreateBuilder { + b.Components = LayoutComponents return b } -// SetContainerComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) -func (b *ModalCreateBuilder) SetContainerComponent(i int, container ContainerComponent) *ModalCreateBuilder { +// SetComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) +func (b *ModalCreateBuilder) SetComponent(i int, container LayoutComponent) *ModalCreateBuilder { if len(b.Components) > i { b.Components[i] = container } @@ -47,27 +47,27 @@ func (b *ModalCreateBuilder) SetContainerComponent(i int, container ContainerCom // 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)) + b.Components = append(b.Components, ActionRowComponent{Components: components}) return b } -// AddContainerComponents adds the discord.ContainerComponent(s) to the ModalCreate -func (b *ModalCreateBuilder) AddContainerComponents(containers ...ContainerComponent) *ModalCreateBuilder { +// AddComponents adds the discord.LayoutComponent(s) to the ModalCreate +func (b *ModalCreateBuilder) AddComponents(containers ...LayoutComponent) *ModalCreateBuilder { b.Components = append(b.Components, containers...) return b } -// RemoveContainerComponent removes a discord.ActionRowComponent from the ModalCreate -func (b *ModalCreateBuilder) RemoveContainerComponent(i int) *ModalCreateBuilder { +// RemoveComponent removes a discord.ActionRowComponent from the ModalCreate +func (b *ModalCreateBuilder) RemoveComponent(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{} +// ClearComponents removes all the discord.LayoutComponent(s) of the ModalCreate +func (b *ModalCreateBuilder) ClearComponents() *ModalCreateBuilder { + b.Components = []LayoutComponent{} return b } diff --git a/discord/select_menu.go b/discord/select_menu.go index 6f7105dee..f3238990a 100644 --- a/discord/select_menu.go +++ b/discord/select_menu.go @@ -5,9 +5,16 @@ import ( "github.com/disgoorg/snowflake/v2" ) +// SelectMenuComponent is an interface for all components that are select menus. +// [StringSelectMenuComponent] +// [UserSelectMenuComponent] +// [RoleSelectMenuComponent] +// [MentionableSelectMenuComponent] +// [ChannelSelectMenuComponent] +// [UnknownComponent] type SelectMenuComponent interface { InteractiveComponent - selectMenu() + selectMenuComponent() } var ( @@ -26,6 +33,7 @@ func NewStringSelectMenu(customID string, placeholder string, options ...StringS } type StringSelectMenuComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Placeholder string `json:"placeholder,omitempty"` MinValues *int `json:"min_values,omitempty"` @@ -49,13 +57,17 @@ func (StringSelectMenuComponent) Type() ComponentType { return ComponentTypeStringSelectMenu } -func (c StringSelectMenuComponent) ID() string { +func (c StringSelectMenuComponent) GetID() int { + return c.ID +} + +func (c StringSelectMenuComponent) GetCustomID() string { return c.CustomID } func (StringSelectMenuComponent) component() {} func (StringSelectMenuComponent) interactiveComponent() {} -func (StringSelectMenuComponent) selectMenu() {} +func (StringSelectMenuComponent) selectMenuComponent() {} // WithCustomID returns a new StringSelectMenuComponent with the provided customID func (c StringSelectMenuComponent) WithCustomID(customID string) StringSelectMenuComponent { @@ -130,6 +142,11 @@ func (c StringSelectMenuComponent) RemoveOption(index int) StringSelectMenuCompo return c } +func (c StringSelectMenuComponent) WithID(i int) InteractiveComponent { + c.ID = i + return c +} + // NewStringSelectMenuOption builds a new StringSelectMenuOption func NewStringSelectMenuOption(label string, value string) StringSelectMenuOption { return StringSelectMenuOption{ @@ -140,6 +157,7 @@ func NewStringSelectMenuOption(label string, value string) StringSelectMenuOptio // StringSelectMenuOption represents an option in a StringSelectMenuComponent type StringSelectMenuOption struct { + ID int `json:"id,omitempty"` Label string `json:"label"` Value string `json:"value"` Description string `json:"description,omitempty"` @@ -192,6 +210,7 @@ func NewUserSelectMenu(customID string, placeholder string) UserSelectMenuCompon } type UserSelectMenuComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Placeholder string `json:"placeholder,omitempty"` DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` @@ -215,13 +234,17 @@ func (UserSelectMenuComponent) Type() ComponentType { return ComponentTypeUserSelectMenu } -func (c UserSelectMenuComponent) ID() string { +func (c UserSelectMenuComponent) GetID() int { + return c.ID +} + +func (c UserSelectMenuComponent) GetCustomID() string { return c.CustomID } func (UserSelectMenuComponent) component() {} func (UserSelectMenuComponent) interactiveComponent() {} -func (UserSelectMenuComponent) selectMenu() {} +func (UserSelectMenuComponent) selectMenuComponent() {} // WithCustomID returns a new UserSelectMenuComponent with the provided customID func (c UserSelectMenuComponent) WithCustomID(customID string) UserSelectMenuComponent { @@ -304,6 +327,7 @@ func NewRoleSelectMenu(customID string, placeholder string) RoleSelectMenuCompon } type RoleSelectMenuComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Placeholder string `json:"placeholder,omitempty"` DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` @@ -327,13 +351,17 @@ func (RoleSelectMenuComponent) Type() ComponentType { return ComponentTypeRoleSelectMenu } -func (c RoleSelectMenuComponent) ID() string { +func (c RoleSelectMenuComponent) GetID() int { + return c.ID +} + +func (c RoleSelectMenuComponent) GetCustomID() string { return c.CustomID } func (RoleSelectMenuComponent) component() {} func (RoleSelectMenuComponent) interactiveComponent() {} -func (RoleSelectMenuComponent) selectMenu() {} +func (RoleSelectMenuComponent) selectMenuComponent() {} // WithCustomID returns a new RoleSelectMenuComponent with the provided customID func (c RoleSelectMenuComponent) WithCustomID(customID string) RoleSelectMenuComponent { @@ -416,6 +444,7 @@ func NewMentionableSelectMenu(customID string, placeholder string) MentionableSe } type MentionableSelectMenuComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Placeholder string `json:"placeholder,omitempty"` DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` @@ -439,13 +468,17 @@ func (MentionableSelectMenuComponent) Type() ComponentType { return ComponentTypeMentionableSelectMenu } -func (c MentionableSelectMenuComponent) ID() string { +func (c MentionableSelectMenuComponent) GetID() int { + return c.ID +} + +func (c MentionableSelectMenuComponent) GetCustomID() string { return c.CustomID } func (MentionableSelectMenuComponent) component() {} func (MentionableSelectMenuComponent) interactiveComponent() {} -func (MentionableSelectMenuComponent) selectMenu() {} +func (MentionableSelectMenuComponent) selectMenuComponent() {} // WithCustomID returns a new MentionableSelectMenuComponent with the provided customID func (c MentionableSelectMenuComponent) WithCustomID(customID string) MentionableSelectMenuComponent { @@ -525,6 +558,7 @@ func NewChannelSelectMenu(customID string, placeholder string) ChannelSelectMenu } type ChannelSelectMenuComponent struct { + ID int `json:"id,omitempty"` CustomID string `json:"custom_id"` Placeholder string `json:"placeholder,omitempty"` DefaultValues []SelectMenuDefaultValue `json:"default_values,omitempty"` @@ -549,13 +583,17 @@ func (ChannelSelectMenuComponent) Type() ComponentType { return ComponentTypeChannelSelectMenu } -func (c ChannelSelectMenuComponent) ID() string { +func (c ChannelSelectMenuComponent) GetID() int { + return c.ID +} + +func (c ChannelSelectMenuComponent) GetCustomID() string { return c.CustomID } func (ChannelSelectMenuComponent) component() {} func (ChannelSelectMenuComponent) interactiveComponent() {} -func (ChannelSelectMenuComponent) selectMenu() {} +func (ChannelSelectMenuComponent) selectMenuComponent() {} // WithCustomID returns a new ChannelSelectMenuComponent with the provided customID func (c ChannelSelectMenuComponent) WithCustomID(customID string) ChannelSelectMenuComponent { diff --git a/discord/webhook_message_create.go b/discord/webhook_message_create.go index f84e4ea8d..7f7ac78ce 100644 --- a/discord/webhook_message_create.go +++ b/discord/webhook_message_create.go @@ -3,19 +3,19 @@ package discord import "github.com/disgoorg/snowflake/v2" type WebhookMessageCreate struct { - Content string `json:"content,omitempty"` - Username string `json:"username,omitempty"` - AvatarURL string `json:"avatar_url,omitempty"` - TTS bool `json:"tts,omitempty"` - Embeds []Embed `json:"embeds,omitempty"` - Components []ContainerComponent `json:"components,omitempty"` - Attachments []AttachmentCreate `json:"attachments,omitempty"` - Files []*File `json:"-"` - AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` - Flags MessageFlags `json:"flags,omitempty"` - ThreadName string `json:"thread_name,omitempty"` - AppliedTags []snowflake.ID `json:"applied_tags,omitempty"` - Poll *PollCreate `json:"poll,omitempty"` + Content string `json:"content,omitempty"` + Username string `json:"username,omitempty"` + AvatarURL string `json:"avatar_url,omitempty"` + TTS bool `json:"tts,omitempty"` + Embeds []Embed `json:"embeds,omitempty"` + Components []LayoutComponent `json:"components,omitempty"` + Attachments []AttachmentCreate `json:"attachments,omitempty"` + Files []*File `json:"-"` + AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` + Flags MessageFlags `json:"flags,omitempty"` + ThreadName string `json:"thread_name,omitempty"` + AppliedTags []snowflake.ID `json:"applied_tags,omitempty"` + Poll *PollCreate `json:"poll,omitempty"` } // ToBody returns the MessageCreate ready for body diff --git a/discord/webhook_message_create_builder.go b/discord/webhook_message_create_builder.go index 4c8666959..ab1588b5c 100644 --- a/discord/webhook_message_create_builder.go +++ b/discord/webhook_message_create_builder.go @@ -80,14 +80,14 @@ func (b *WebhookMessageCreateBuilder) RemoveEmbed(i int) *WebhookMessageCreateBu return b } -// SetContainerComponents sets the discord.ContainerComponent(s) of the Message -func (b *WebhookMessageCreateBuilder) SetContainerComponents(containerComponents ...ContainerComponent) *WebhookMessageCreateBuilder { - b.Components = containerComponents +// SetComponents sets the discord.LayoutComponent(s) of the Message +func (b *WebhookMessageCreateBuilder) SetComponents(LayoutComponents ...LayoutComponent) *WebhookMessageCreateBuilder { + b.Components = LayoutComponents return b } -// SetContainerComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) -func (b *WebhookMessageCreateBuilder) SetContainerComponent(i int, container ContainerComponent) *WebhookMessageCreateBuilder { +// SetComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) +func (b *WebhookMessageCreateBuilder) SetComponent(i int, container LayoutComponent) *WebhookMessageCreateBuilder { if len(b.Components) > i { b.Components[i] = container } @@ -96,27 +96,27 @@ func (b *WebhookMessageCreateBuilder) SetContainerComponent(i int, container Con // AddActionRow adds a new discord.ActionRowComponent with the provided discord.InteractiveComponent(s) to the Message func (b *WebhookMessageCreateBuilder) AddActionRow(components ...InteractiveComponent) *WebhookMessageCreateBuilder { - b.Components = append(b.Components, ActionRowComponent(components)) + b.Components = append(b.Components, ActionRowComponent{Components: components}) return b } -// AddContainerComponents adds the discord.ContainerComponent(s) to the Message -func (b *WebhookMessageCreateBuilder) AddContainerComponents(containers ...ContainerComponent) *WebhookMessageCreateBuilder { +// AddComponents adds the discord.LayoutComponent(s) to the Message +func (b *WebhookMessageCreateBuilder) AddComponents(containers ...LayoutComponent) *WebhookMessageCreateBuilder { b.Components = append(b.Components, containers...) return b } -// RemoveContainerComponent removes a discord.ActionRowComponent from the Message -func (b *WebhookMessageCreateBuilder) RemoveContainerComponent(i int) *WebhookMessageCreateBuilder { +// RemoveComponent removes a discord.ActionRowComponent from the Message +func (b *WebhookMessageCreateBuilder) RemoveComponent(i int) *WebhookMessageCreateBuilder { 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 Message -func (b *WebhookMessageCreateBuilder) ClearContainerComponents() *WebhookMessageCreateBuilder { - b.Components = []ContainerComponent{} +// ClearComponents removes all the discord.LayoutComponent(s) of the Message +func (b *WebhookMessageCreateBuilder) ClearComponents() *WebhookMessageCreateBuilder { + b.Components = []LayoutComponent{} return b } @@ -194,6 +194,16 @@ func (b *WebhookMessageCreateBuilder) ClearFlags() *WebhookMessageCreateBuilder return b.SetFlags(MessageFlagsNone) } +// SetIsComponentsV2 adds/removes discord.MessageFlagIsComponentsV2 to the Message flags +func (b *WebhookMessageCreateBuilder) SetIsComponentsV2(isComponentV2 bool) *WebhookMessageCreateBuilder { + if isComponentV2 { + b.Flags = b.Flags.Add(MessageFlagIsComponentsV2) + } else { + b.Flags = b.Flags.Remove(MessageFlagIsComponentsV2) + } + return b +} + // SetSuppressEmbeds adds/removes discord.MessageFlagSuppressEmbeds to the Message flags func (b *WebhookMessageCreateBuilder) SetSuppressEmbeds(suppressEmbeds bool) *WebhookMessageCreateBuilder { if suppressEmbeds { diff --git a/discord/webhook_message_update.go b/discord/webhook_message_update.go index c756d1d39..5e92758e2 100644 --- a/discord/webhook_message_update.go +++ b/discord/webhook_message_update.go @@ -2,13 +2,13 @@ package discord // WebhookMessageUpdate is used to edit a Message type WebhookMessageUpdate struct { - Content *string `json:"content,omitempty"` - Embeds *[]Embed `json:"embeds,omitempty"` - Components *[]ContainerComponent `json:"components,omitempty"` - Attachments *[]AttachmentUpdate `json:"attachments,omitempty"` - Files []*File `json:"-"` - AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` - Poll *PollCreate `json:"poll,omitempty"` + Content *string `json:"content,omitempty"` + Embeds *[]Embed `json:"embeds,omitempty"` + Components *[]LayoutComponent `json:"components,omitempty"` + Attachments *[]AttachmentUpdate `json:"attachments,omitempty"` + Files []*File `json:"-"` + AllowedMentions *AllowedMentions `json:"allowed_mentions,omitempty"` + Poll *PollCreate `json:"poll,omitempty"` } // ToBody returns the WebhookMessageUpdate ready for body diff --git a/discord/webhook_message_update_builder.go b/discord/webhook_message_update_builder.go index b031fa642..abee0e852 100644 --- a/discord/webhook_message_update_builder.go +++ b/discord/webhook_message_update_builder.go @@ -83,19 +83,19 @@ func (b *WebhookMessageUpdateBuilder) RemoveEmbed(i int) *WebhookMessageUpdateBu return b } -// SetContainerComponents sets the discord.ContainerComponent(s) of the Message -func (b *WebhookMessageUpdateBuilder) SetContainerComponents(containerComponents ...ContainerComponent) *WebhookMessageUpdateBuilder { +// SetComponents sets the discord.LayoutComponent(s) of the Message +func (b *WebhookMessageUpdateBuilder) SetComponents(LayoutComponents ...LayoutComponent) *WebhookMessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } - *b.Components = containerComponents + *b.Components = LayoutComponents return b } -// SetContainerComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) -func (b *WebhookMessageUpdateBuilder) SetContainerComponent(i int, container ContainerComponent) *WebhookMessageUpdateBuilder { +// SetComponent sets the provided discord.InteractiveComponent at the index of discord.InteractiveComponent(s) +func (b *WebhookMessageUpdateBuilder) SetComponent(i int, container LayoutComponent) *WebhookMessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } if len(*b.Components) > i { (*b.Components)[i] = container @@ -106,23 +106,23 @@ func (b *WebhookMessageUpdateBuilder) SetContainerComponent(i int, container Con // AddActionRow adds a new discord.ActionRowComponent with the provided discord.InteractiveComponent(s) to the Message func (b *WebhookMessageUpdateBuilder) AddActionRow(components ...InteractiveComponent) *WebhookMessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } - *b.Components = append(*b.Components, ActionRowComponent(components)) + *b.Components = append(*b.Components, ActionRowComponent{Components: components}) return b } -// AddContainerComponents adds the discord.ContainerComponent(s) to the Message -func (b *WebhookMessageUpdateBuilder) AddContainerComponents(containers ...ContainerComponent) *WebhookMessageUpdateBuilder { +// AddComponents adds the discord.LayoutComponent(s) to the Message +func (b *WebhookMessageUpdateBuilder) AddComponents(containers ...LayoutComponent) *WebhookMessageUpdateBuilder { if b.Components == nil { - b.Components = new([]ContainerComponent) + b.Components = new([]LayoutComponent) } *b.Components = append(*b.Components, containers...) return b } -// RemoveContainerComponent removes a discord.ContainerComponent from the Message -func (b *WebhookMessageUpdateBuilder) RemoveContainerComponent(i int) *WebhookMessageUpdateBuilder { +// RemoveComponent removes a discord.LayoutComponent from the Message +func (b *WebhookMessageUpdateBuilder) RemoveComponent(i int) *WebhookMessageUpdateBuilder { if b.Components == nil { return b } @@ -132,9 +132,9 @@ func (b *WebhookMessageUpdateBuilder) RemoveContainerComponent(i int) *WebhookMe return b } -// ClearContainerComponents removes all the discord.ContainerComponent(s) of the Message -func (b *WebhookMessageUpdateBuilder) ClearContainerComponents() *WebhookMessageUpdateBuilder { - b.Components = &[]ContainerComponent{} +// ClearComponents removes all the discord.LayoutComponent(s) of the Message +func (b *WebhookMessageUpdateBuilder) ClearComponents() *WebhookMessageUpdateBuilder { + b.Components = &[]LayoutComponent{} return b }