///|
/// A Discord message component dispatched by its numeric wire discriminator.
pub(all) enum Component {
ActionRow(ActionRow)
Button(Button)
StringSelect(SelectMenu)
TextInput(TextInput)
UserSelect(SelectMenu)
RoleSelect(SelectMenu)
MentionableSelect(SelectMenu)
ChannelSelect(SelectMenu)
Section(Section)
TextDisplay(TextDisplay)
Thumbnail(Thumbnail)
MediaGallery(MediaGallery)
File(FileComponent)
Separator(Separator)
Container(Container)
Label(Label)
FileUpload(FileUpload)
RadioGroup(RadioGroup)
CheckboxGroup(CheckboxGroup)
Checkbox(Checkbox)
Unknown(Json)
} derive(Debug)
///|
/// Whether any top-level component requires Discord's Components V2 message
/// flag.
///
/// ```mbt check
/// test "detect top-level Components V2 layouts" {
/// let row : @model.Component = ActionRow({ id: None, components: [], })
/// let container : @model.Component = Container({
/// id: None,
/// components: [],
/// accent_color: None,
/// spoiler: None,
/// })
/// assert_false(@model.requires_components_v2([row]))
/// assert_true(@model.requires_components_v2([row, container]))
/// }
/// ```
pub fn requires_components_v2(components : Array[Component]) -> Bool {
components.any(component => {
match component {
Section(_)
| TextDisplay(_)
| MediaGallery(_)
| File(_)
| Separator(_)
| Container(_) => true
_ => false
}
})
}
///|
fn component_payload(payload : Json, typ : Int) -> Json {
match payload {
Object(fields) => {
fields["type"] = Json::number(typ.to_double())
Json::object(fields)
}
value => value
}
}
///|
pub impl ToJson for Component with fn to_json(self) {
match self {
ActionRow(value) => component_payload(ToJson::to_json(value), 1)
Button(value) => component_payload(ToJson::to_json(value), 2)
StringSelect(value) => component_payload(ToJson::to_json(value), 3)
TextInput(value) => component_payload(ToJson::to_json(value), 4)
UserSelect(value) => component_payload(ToJson::to_json(value), 5)
RoleSelect(value) => component_payload(ToJson::to_json(value), 6)
MentionableSelect(value) => component_payload(ToJson::to_json(value), 7)
ChannelSelect(value) => component_payload(ToJson::to_json(value), 8)
Section(value) => component_payload(ToJson::to_json(value), 9)
TextDisplay(value) => component_payload(ToJson::to_json(value), 10)
Thumbnail(value) => component_payload(ToJson::to_json(value), 11)
MediaGallery(value) => component_payload(ToJson::to_json(value), 12)
File(value) => component_payload(ToJson::to_json(value), 13)
Separator(value) => component_payload(ToJson::to_json(value), 14)
Container(value) => component_payload(ToJson::to_json(value), 17)
Label(value) => component_payload(ToJson::to_json(value), 18)
FileUpload(value) => component_payload(ToJson::to_json(value), 19)
RadioGroup(value) => component_payload(ToJson::to_json(value), 21)
CheckboxGroup(value) => component_payload(ToJson::to_json(value), 22)
Checkbox(value) => component_payload(ToJson::to_json(value), 23)
Unknown(value) => value
}
}
///|
pub impl @json.FromJson for Component with fn from_json(json, path) {
guard json is Object(fields) else {
raise JsonDecodeError((path, "expected a component (object)"))
}
match fields.get("type") {
Some(Number(value, ..)) =>
match value.to_int() {
1 => ActionRow(@json.from_json(json, path~))
2 => Button(@json.from_json(json, path~))
3 => StringSelect(@json.from_json(json, path~))
4 => TextInput(@json.from_json(json, path~))
5 => UserSelect(@json.from_json(json, path~))
6 => RoleSelect(@json.from_json(json, path~))
7 => MentionableSelect(@json.from_json(json, path~))
8 => ChannelSelect(@json.from_json(json, path~))
9 => Section(@json.from_json(json, path~))
10 => TextDisplay(@json.from_json(json, path~))
11 => Thumbnail(@json.from_json(json, path~))
12 => MediaGallery(@json.from_json(json, path~))
13 => File(@json.from_json(json, path~))
14 => Separator(@json.from_json(json, path~))
17 => Container(@json.from_json(json, path~))
18 => Label(@json.from_json(json, path~))
19 => FileUpload(@json.from_json(json, path~))
21 => RadioGroup(@json.from_json(json, path~))
22 => CheckboxGroup(@json.from_json(json, path~))
23 => Checkbox(@json.from_json(json, path~))
_ => Unknown(json)
}
_ => Unknown(json)
}
}
///|
/// A layout row containing nested interactive components.
pub(all) struct ActionRow {
id : Int?
components : Array[Component]
} derive(ToJson, FromJson, Debug)
///|
/// A clickable button component.
pub(all) struct Button {
id : Int?
style : ButtonStyle
label : String?
emoji : Emoji?
custom_id : String?
sku_id : SkuId?
url : String?
disabled : Bool?
} derive(ToJson, FromJson, Debug)
///|
/// The visual and behavioral style of a button.
pub(all) enum ButtonStyle {
Primary
Secondary
Success
Danger
Link
Premium
Unknown(Int)
} derive(Eq, Debug)
///|
pub fn ButtonStyle::to_int(self : ButtonStyle) -> Int {
match self {
Primary => 1
Secondary => 2
Success => 3
Danger => 4
Link => 5
Premium => 6
Unknown(value) => value
}
}
///|
pub fn ButtonStyle::from_int(value : Int) -> ButtonStyle {
match value {
1 => Primary
2 => Secondary
3 => Success
4 => Danger
5 => Link
6 => Premium
value => Unknown(value)
}
}
///|
pub impl ToJson for ButtonStyle with fn to_json(self) {
Json::number(self.to_int().to_double())
}
///|
pub impl @json.FromJson for ButtonStyle with fn from_json(json, path) {
match json {
Number(value, ..) => ButtonStyle::from_int(value.to_int())
_ => raise JsonDecodeError((path, "expected a button style (number)"))
}
}
///|
/// A text or auto-populated select menu component.
pub(all) struct SelectMenu {
id : Int?
custom_id : String
placeholder : String?
min_values : Int?
max_values : Int?
disabled : Bool?
options : Array[SelectOption]?
channel_types : Array[ChannelType]?
default_values : Array[SelectDefaultValue]?
values : Array[String]?
} derive(ToJson, FromJson, Debug)
///|
/// A selectable option in a string select menu.
pub(all) struct SelectOption {
label : String
value : String
description : String?
emoji : Emoji?
default : Bool?
} derive(ToJson, FromJson, Debug)
///|
/// A preselected entity in an auto-populated select menu.
pub(all) struct SelectDefaultValue {
id : GenericId
typ : String
} derive (
ToJson(fields(typ(rename="type"))),
FromJson(fields(typ(rename="type"))),
Debug,
)
///|
/// A section containing text components and a button or thumbnail accessory.
pub(all) struct Section {
id : Int?
components : Array[Component]
accessory : Component
} derive(ToJson, FromJson, Debug)
///|
/// A markdown text block displayed in a Components v2 layout.
pub(all) struct TextDisplay {
id : Int?
content : String
} derive(ToJson, FromJson, Debug)
///|
/// A thumbnail accessory rendered from an unfurled media item.
pub(all) struct Thumbnail {
id : Int?
media : UnfurledMediaItem
description : String?
spoiler : Bool?
} derive(ToJson, FromJson, Debug)
///|
/// A gallery containing one to ten media items.
pub(all) struct MediaGallery {
id : Int?
items : Array[MediaGalleryItem]
} derive(ToJson, FromJson, Debug)
///|
/// One item in a media gallery.
pub(all) struct MediaGalleryItem {
media : UnfurledMediaItem
description : String?
spoiler : Bool?
} derive(ToJson, FromJson, Debug)
///|
/// A file attachment rendered as a Components v2 file component.
pub(all) struct FileComponent {
id : Int?
file : UnfurledMediaItem
spoiler : Bool?
name : String?
size : Int?
} derive(ToJson, FromJson, Debug)
///|
/// A visual separator between Components v2 content blocks.
pub(all) struct Separator {
id : Int?
divider : Bool?
spacing : Int?
} derive(ToJson, FromJson, Debug)
///|
/// A Components v2 container with an optional accent color.
pub(all) struct Container {
id : Int?
components : Array[Component]
accent_color : Nullable[Int]?
spoiler : Bool?
} derive(ToJson, FromJson, Debug)
///|
/// A modal label wrapping one interactive component.
pub(all) struct Label {
id : Int?
// Required when sending a modal; omitted in modal-submit payloads.
label : String?
description : String?
component : Component
} derive(ToJson, FromJson, Debug)
///|
/// A modal file upload component.
pub(all) struct FileUpload {
id : Int?
custom_id : String
min_values : Int?
max_values : Int?
required : Bool?
values : Array[String]?
} derive(ToJson, FromJson, Debug)
///|
/// A modal radio group accepting one option.
pub(all) struct RadioGroup {
id : Int?
custom_id : String
options : Array[RadioOption]
required : Bool?
value : String?
} derive(ToJson, FromJson, Debug)
///|
/// A selectable option shared by radio and checkbox groups.
pub(all) struct RadioOption {
value : String
label : String
description : String?
default : Bool?
} derive(ToJson, FromJson, Debug)
///|
/// A modal checkbox group accepting zero or more options.
pub(all) struct CheckboxGroup {
id : Int?
custom_id : String
options : Array[RadioOption]
min_values : Int?
max_values : Int?
required : Bool?
values : Array[String]?
} derive(ToJson, FromJson, Debug)
///|
/// A single modal checkbox.
pub(all) struct Checkbox {
id : Int?
custom_id : String
default : Bool?
value : Bool?
} derive(ToJson, FromJson, Debug)
///|
/// Media metadata used by thumbnail, gallery, and file components.
pub(all) struct UnfurledMediaItem {
url : String
proxy_url : String?
height : Int?
width : Int?
placeholder : String?
placeholder_version : Int?
content_type : String?
flags : Int?
attachment_id : String?
} derive(ToJson, FromJson, Debug)
///|
/// A free-form text input component used in a modal.
pub(all) struct TextInput {
id : Int?
custom_id : String
// Required when sending a modal; omitted in modal-submit payloads.
style : TextInputStyle?
label : String?
min_length : Int?
max_length : Int?
required : Bool?
value : String?
placeholder : String?
} derive(ToJson, FromJson, Debug)
///|
/// The single-line or multi-line style of a text input.
pub(all) enum TextInputStyle {
Short
Paragraph
Unknown(Int)
} derive(Eq, Debug)
///|
pub fn TextInputStyle::to_int(self : TextInputStyle) -> Int {
match self {
Short => 1
Paragraph => 2
Unknown(value) => value
}
}
///|
pub fn TextInputStyle::from_int(value : Int) -> TextInputStyle {
match value {
1 => Short
2 => Paragraph
value => Unknown(value)
}
}
///|
pub impl ToJson for TextInputStyle with fn to_json(self) {
Json::number(self.to_int().to_double())
}
///|
pub impl @json.FromJson for TextInputStyle with fn from_json(json, path) {
match json {
Number(value, ..) => TextInputStyle::from_int(value.to_int())
_ => raise JsonDecodeError((path, "expected a text input style (number)"))
}
}