glyde/model/component
Message components: action rows, buttons and select menus.
Two positions, two types. A Component is a top-level entry on a message:
an action row, or a Components V2 element. A RowChild is what sits inside
a row: a button, a select menu. Discord answers 400 to a row nested in a
row and to a button at top level, and neither is a value that can be built
here.
Types 1, 2, 3, 5, 6, 7 and 8 are modelled. Everything else keeps its payload intact, so another bot’s Components V2 message does not sink your MESSAGE_CREATE.
Types
A link button with a custom_id is a 400, so each kind carries only the
fields its style allows. Styles 5 and 6 are the link and premium buttons.
pub type ButtonKind {
ActionButton(
custom_id: String,
style: ButtonStyle,
label: ButtonLabel,
)
LinkButton(url: String, label: ButtonLabel)
PremiumButton(sku_id: id.Id(id.Sku))
}
Constructors
-
ActionButton( custom_id: String, style: ButtonStyle, label: ButtonLabel, ) -
LinkButton(url: String, label: ButtonLabel) -
Discord supplies the label from the SKU, so this one takes none.
Discord needs a button to say something, so “no label and no emoji” is not a state to have. Text is max 80 characters.
pub type ButtonLabel {
Text(String)
Icon(emoji.Emoji)
TextAndIcon(String, emoji.Emoji)
}
Constructors
-
Text(String) -
Icon(emoji.Emoji) -
TextAndIcon(String, emoji.Emoji)
pub type ButtonStyle {
PrimaryButton
SecondaryButton
SuccessButton
DangerButton
UnknownButtonStyle(Int)
}
Constructors
-
PrimaryButton -
SecondaryButton -
SuccessButton -
DangerButton -
UnknownButtonStyle(Int)
A top-level entry in a message’s components.
pub type Component {
ActionRow(id: option.Option(Int), components: List(RowChild))
UnknownComponent(type_: Int, raw: RawPayload)
}
Constructors
-
ActionRow(id: option.Option(Int), components: List(RowChild))Type 1. Holds up to five buttons, or exactly one select menu.
Arguments
- id
-
Discord’s per-component sequence number, not a snowflake.
-
UnknownComponent(type_: Int, raw: RawPayload)Types 9 to 23, the Components V2 elements, and anything else that arrived where a row belongs. The raw payload is kept, so it can be sent back unchanged.
Discord’s type number, the one field both positions carry. Types 9 to 23
are Components V2, unmodelled, and arrive as UnknownComponentType.
pub type ComponentType {
ActionRowType
ButtonType
StringSelectType
TextInputType
UserSelectType
RoleSelectType
MentionableSelectType
ChannelSelectType
UnknownComponentType(Int)
}
Constructors
-
ActionRowType -
ButtonType -
StringSelectType -
TextInputTypeA modal’s text input, one per action row.
-
UserSelectType -
RoleSelectType -
MentionableSelectType -
ChannelSelectType -
UnknownComponentType(Int)
A pre-selected entry on an entity select, tagged so id and kind agree.
pub type DefaultValue {
DefaultUser(id: id.Id(id.User))
DefaultRole(id: id.Id(id.Role))
DefaultChannel(id: id.Id(id.Channel))
}
Constructors
-
-
-
DefaultChannel(id: id.Id(id.Channel))
A payload this build does not model, read once into keys and values. The read happens at decode time, where a malformed payload is expected, so nothing downstream has to handle a failure by dropping the lot.
pub opaque type RawPayload
What an action row holds. Never a row: Discord answers 400 to a nested one, and never a Components V2 element, which is top level.
pub type RowChild {
Button(
id: option.Option(Int),
kind: ButtonKind,
disabled: Bool,
)
SelectMenu(
id: option.Option(Int),
custom_id: String,
kind: SelectKind,
placeholder: option.Option(String),
min_values: option.Option(Int),
max_values: option.Option(Int),
disabled: Bool,
)
UnknownChild(type_: Int, raw: RawPayload)
}
Constructors
-
Button(id: option.Option(Int), kind: ButtonKind, disabled: Bool)Type 2. What it says is in
kind: a premium button takes its label from the SKU, and the other two need one of their own. -
SelectMenu( id: option.Option(Int), custom_id: String, kind: SelectKind, placeholder: option.Option(String), min_values: option.Option(Int), max_values: option.Option(Int), disabled: Bool, )Types 3, 5, 6, 7 and 8.
kindis the discriminator.Arguments
- custom_id
-
1 to 100 characters, chosen by you.
-
UnknownChild(type_: Int, raw: RawPayload)Type 4, the text input a modal puts in a row, and a type 2 with no kind to degrade to: a button Discord would reject. The raw payload is kept, so it can be sent back unchanged.
pub type SelectKind {
StringSelect(options: List(SelectOption))
UserSelect(default_values: List(DefaultValue))
RoleSelect(default_values: List(DefaultValue))
MentionableSelect(default_values: List(DefaultValue))
ChannelSelect(
channel_types: List(channel.ChannelType),
default_values: List(DefaultValue),
)
}
Constructors
-
StringSelect(options: List(SelectOption))Type 3. Carries 1 to 25 options.
-
UserSelect(default_values: List(DefaultValue))Type 5.
-
RoleSelect(default_values: List(DefaultValue))Type 6.
-
MentionableSelect(default_values: List(DefaultValue))Type 7. Resolves to users and roles together.
-
ChannelSelect( channel_types: List(channel.ChannelType), default_values: List(DefaultValue), )Type 8.
Arguments
- channel_types
-
Empty means every channel type is offered.
pub type SelectOption {
SelectOption(
label: String,
value: String,
description: option.Option(String),
emoji: option.Option(emoji.Emoji),
default: Bool,
)
}
Constructors
-
SelectOption( label: String, value: String, description: option.Option(String), emoji: option.Option(emoji.Emoji), default: Bool, )Arguments
- label
-
Max 100 characters, shown to the user.
- value
-
Max 100 characters, sent back to you on click.
Values
pub fn button(
custom_id custom_id: String,
label label: String,
) -> RowChild
PrimaryButton is our default, not Discord’s: the API requires a style and
has none of its own. Change it with a record update.
pub fn button_style_decoder() -> decode.Decoder(ButtonStyle)
pub fn button_style_from_int(value: Int) -> ButtonStyle
pub fn button_style_to_int(value: ButtonStyle) -> Int
pub fn button_style_to_json(value: ButtonStyle) -> json.Json
pub fn component_type(component: Component) -> ComponentType
pub fn component_type_from_int(value: Int) -> ComponentType
pub fn component_type_to_int(value: ComponentType) -> Int
pub fn decoder() -> decode.Decoder(Component)
pub fn default_value_decoder() -> decode.Decoder(DefaultValue)
An unrecognised type fails the whole select menu on purpose: a default
dropped silently would pre-select the wrong thing.
pub fn default_value_to_json(value: DefaultValue) -> json.Json
pub fn empty_raw_payload() -> RawPayload
pub fn link_button(
url url: String,
label label: String,
) -> RowChild
Opens a url, and sends no interaction.
pub fn option(
label label: String,
value value: String,
) -> SelectOption
pub fn premium_button(sku_id: id.Id(id.Sku)) -> RowChild
A premium upsell. Discord supplies the label from the SKU.
pub fn raw_payload(
value: dynamic.Dynamic,
) -> Result(RawPayload, Nil)
Error(Nil) when the value is not a JSON object. Decoders fall back to
empty_raw_payload; nothing else needs to.
pub fn raw_payload_entries(
payload: RawPayload,
) -> List(#(String, json.Json))
The keys as they will be written, sorted: a Dict has no order of its own
and these become request bytes.
pub fn row_child_decoder() -> decode.Decoder(RowChild)
pub fn row_child_type(child: RowChild) -> ComponentType
pub fn rows(children: List(RowChild)) -> List(Component)
Pack loose children into action rows: five buttons to a row, a whole row per select menu, order preserved. A row and a Components V2 element are top level, so they are not inputs here: append them to what this returns.
pub fn select(
custom_id custom_id: String,
kind kind: SelectKind,
) -> RowChild
pub fn select_option_decoder() -> decode.Decoder(SelectOption)
pub fn select_option_to_json(item: SelectOption) -> json.Json