From c6a3661ee46787712ec7b85b5e2b32bb4e90d446 Mon Sep 17 00:00:00 2001 From: Ben Hall Date: Thu, 8 Oct 2026 19:23:35 +0100 Subject: [PATCH 1/3] feat~: Components v2 Builder Signed-off-by: Ben Hall --- objects/interaction/component/componentbutton.go | 9 ++++++++- objects/interaction/component/selectmenu.go | 8 ++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/objects/interaction/component/componentbutton.go b/objects/interaction/component/componentbutton.go index 014e832..1e60b5a 100644 --- a/objects/interaction/component/componentbutton.go +++ b/objects/interaction/component/componentbutton.go @@ -13,7 +13,14 @@ type Button struct { Emoji *emoji.Emoji `json:"emoji,omitempty"` SkuId *uint64 `json:"sku_id,string,omitempty"` Url *string `json:"url,omitempty"` - Disabled bool `json:"disabled"` + Disabled bool `json:"disabled,omitempty"` + + // PanelId marks this button as a placeholder for a real panel's "Open Ticket" button + // within a guild-authored Components V2 tree, rather than a literal button to send to + // Discord. It is never sent to Discord as-is: a resolve pass always replaces a button + // carrying PanelId with a fresh button built from the referenced panel's real + // CustomId/Label/Emoji before the message goes out, clearing PanelId on the output. + PanelId *int `json:"panel_id,omitempty"` } func (b Button) Type() ComponentType { diff --git a/objects/interaction/component/selectmenu.go b/objects/interaction/component/selectmenu.go index ee17a61..0a5215a 100644 --- a/objects/interaction/component/selectmenu.go +++ b/objects/interaction/component/selectmenu.go @@ -14,6 +14,14 @@ type SelectMenu struct { MaxValues *int `json:"max_values,omitempty"` Disabled bool `json:"disabled"` Required *bool `json:"required,omitempty"` + + // IsPanelSelect marks this select menu as a placeholder for the multi-panel's + // dropdown within a guild-authored Components V2 tree, rather than a literal select + // menu to send to Discord. It is never sent to Discord as-is: a resolve pass always + // replaces a select menu carrying IsPanelSelect with a fresh select menu built from + // the multi-panel's unplaced panels before the message goes out, clearing + // IsPanelSelect on the output. + IsPanelSelect bool `json:"is_panel_select,omitempty"` } type SelectOption struct { From 81f424418e2400b030b70ebacb22a76ebe29c066 Mon Sep 17 00:00:00 2001 From: Ben Hall Date: Wed, 14 Oct 2026 17:09:18 +0100 Subject: [PATCH 2/3] fixes based on feedback Signed-off-by: Ben Hall --- .../interaction/component/component_checkbox_group.go | 10 +++++----- objects/interaction/component/componentactionrow.go | 2 +- objects/interaction/component/componentbutton.go | 9 +-------- objects/interaction/component/selectmenu.go | 8 -------- rest/channel.go | 4 ++++ 5 files changed, 11 insertions(+), 22 deletions(-) diff --git a/objects/interaction/component/component_checkbox_group.go b/objects/interaction/component/component_checkbox_group.go index 156a11f..eaf0842 100644 --- a/objects/interaction/component/component_checkbox_group.go +++ b/objects/interaction/component/component_checkbox_group.go @@ -3,11 +3,11 @@ package component import "encoding/json" type CheckboxGroup struct { - CustomId string `json:"custom_id"` - Options []CheckboxGroupOption `json:"options"` - MinValues *int `json:"min_values,omitempty"` - MaxValues *int `json:"max_values,omitempty"` - Required *bool `json:"required,omitempty"` + CustomId string `json:"custom_id"` + Options []CheckboxGroupOption `json:"options"` + MinValues *int `json:"min_values,omitempty"` + MaxValues *int `json:"max_values,omitempty"` + Required *bool `json:"required,omitempty"` } type CheckboxGroupOption struct { diff --git a/objects/interaction/component/componentactionrow.go b/objects/interaction/component/componentactionrow.go index 2b1ef5f..49747d0 100644 --- a/objects/interaction/component/componentactionrow.go +++ b/objects/interaction/component/componentactionrow.go @@ -24,7 +24,7 @@ func (a ActionRow) MarshalJSON() ([]byte, error) { func BuildActionRow(components ...Component) Component { return Component{ - Type: ComponentActionRow, + Type: ComponentActionRow, ComponentData: ActionRow{ Components: components, }, diff --git a/objects/interaction/component/componentbutton.go b/objects/interaction/component/componentbutton.go index 1e60b5a..014e832 100644 --- a/objects/interaction/component/componentbutton.go +++ b/objects/interaction/component/componentbutton.go @@ -13,14 +13,7 @@ type Button struct { Emoji *emoji.Emoji `json:"emoji,omitempty"` SkuId *uint64 `json:"sku_id,string,omitempty"` Url *string `json:"url,omitempty"` - Disabled bool `json:"disabled,omitempty"` - - // PanelId marks this button as a placeholder for a real panel's "Open Ticket" button - // within a guild-authored Components V2 tree, rather than a literal button to send to - // Discord. It is never sent to Discord as-is: a resolve pass always replaces a button - // carrying PanelId with a fresh button built from the referenced panel's real - // CustomId/Label/Emoji before the message goes out, clearing PanelId on the output. - PanelId *int `json:"panel_id,omitempty"` + Disabled bool `json:"disabled"` } func (b Button) Type() ComponentType { diff --git a/objects/interaction/component/selectmenu.go b/objects/interaction/component/selectmenu.go index 0a5215a..ee17a61 100644 --- a/objects/interaction/component/selectmenu.go +++ b/objects/interaction/component/selectmenu.go @@ -14,14 +14,6 @@ type SelectMenu struct { MaxValues *int `json:"max_values,omitempty"` Disabled bool `json:"disabled"` Required *bool `json:"required,omitempty"` - - // IsPanelSelect marks this select menu as a placeholder for the multi-panel's - // dropdown within a guild-authored Components V2 tree, rather than a literal select - // menu to send to Discord. It is never sent to Discord as-is: a resolve pass always - // replaces a select menu carrying IsPanelSelect with a fresh select menu built from - // the multi-panel's unplaced panels before the message goes out, clearing - // IsPanelSelect on the output. - IsPanelSelect bool `json:"is_panel_select,omitempty"` } type SelectOption struct { diff --git a/rest/channel.go b/rest/channel.go index b0eb44f..c774e07 100644 --- a/rest/channel.go +++ b/rest/channel.go @@ -327,6 +327,10 @@ type EditMessageData struct { Embeds []*embed.Embed `json:"embeds"` Flags uint `json:"flags"` // https://discord.com/developers/docs/resources/channel#message-object-message-flags TODO: Helper function Components []component.Component `json:"components"` + + // AllowedMentions is omitted when nil. Discord re-parses mentions with default + // allowances on an edit that does not send it. + AllowedMentions *message.AllowedMention `json:"allowed_mentions,omitempty"` } func EditMessage(ctx context.Context, token string, rateLimiter *ratelimit.Ratelimiter, channelId, messageId uint64, data EditMessageData) (message.Message, error) { From 58d70adbedc9e51b4ca1f0414112d9591b3549e7 Mon Sep 17 00:00:00 2001 From: biast12 <53872542+biast12@users.noreply.github.com> Date: Wed, 28 Oct 2026 10:59:25 +0100 Subject: [PATCH 3/3] Clarify message edit mention comment Remove the inaccurate nil-omission statement from the AllowedMentions documentation while retaining the Discord re-parsing behavior note. --- rest/channel.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/rest/channel.go b/rest/channel.go index c774e07..efb14cb 100644 --- a/rest/channel.go +++ b/rest/channel.go @@ -328,8 +328,7 @@ type EditMessageData struct { Flags uint `json:"flags"` // https://discord.com/developers/docs/resources/channel#message-object-message-flags TODO: Helper function Components []component.Component `json:"components"` - // AllowedMentions is omitted when nil. Discord re-parses mentions with default - // allowances on an edit that does not send it. + // Discord re-parses mentions with default allowances on an edit that does not send it. AllowedMentions *message.AllowedMention `json:"allowed_mentions,omitempty"` }