///|
/// A Discord message component dispatched by its numeric wire discriminator.
#derive(Debug)
#non_exhaustive
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)
}
///|
/// 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 : Int32) -> Json {
match payload {
Object(fields) => {
fields["type"] = Json::number(typ.to_double())
Json::object(fields)
}
value => value
}
}
///|
/// Encodes the component with its `type` discriminator.
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
}
}
///|
/// Decodes a component by its `type` discriminator.
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.
#derive(ToJson, FromJson, Debug)
pub(all) struct ActionRow {
id : Int32?
components : Array[Component]
}
///|
/// A clickable button component.
#derive(ToJson, FromJson, Debug)
pub(all) struct Button {
id : Int32?
style : ButtonStyle
label : String?
emoji : Emoji?
custom_id : String?
sku_id : SkuId?
url : String?
disabled : Bool?
}
///|
/// The visual and behavioral style of a button.
#derive(Eq, Debug)
#non_exhaustive
pub(all) enum ButtonStyle {
Primary
Secondary
Success
Danger
Link
Premium
Unknown(Int32)
}
///|
/// The Discord integer value of this `ButtonStyle`.
pub fn ButtonStyle::to_int(self : ButtonStyle) -> Int32 {
match self {
Primary => 1
Secondary => 2
Success => 3
Danger => 4
Link => 5
Premium => 6
Unknown(value) => value
}
}
///|
/// The `ButtonStyle` for its Discord integer value; unknown values become `Unknown(n)`.
pub fn ButtonStyle::from_int(value : Int32) -> ButtonStyle {
match value {
1 => Primary
2 => Secondary
3 => Success
4 => Danger
5 => Link
6 => Premium
value => Unknown(value)
}
}
///|
/// Encodes the `ButtonStyle` as its Discord integer value.
pub impl ToJson for ButtonStyle with fn to_json(self) {
Json::number(self.to_int().to_double())
}
///|
/// Decodes the `ButtonStyle` from a JSON number; unknown values become `Unknown(n)`.
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.
#derive(ToJson, FromJson, Debug)
pub(all) struct SelectMenu {
id : Int32?
custom_id : String
placeholder : String?
min_values : Int32?
max_values : Int32?
disabled : Bool?
required : Bool?
options : Array[SelectOption]?
channel_types : Array[ChannelType]?
default_values : Array[SelectDefaultValue]?
values : Array[String]?
}
///|
/// A selectable option in a string select menu.
#derive(ToJson, FromJson, Debug)
pub(all) struct SelectOption {
label : String
value : String
description : String?
emoji : Emoji?
default : Bool?
}
///|
/// A preselected entity in an auto-populated select menu.
#derive(ToJson(fields(typ(rename="type"))), FromJson(fields(typ(rename="type"))), Debug)
pub(all) struct SelectDefaultValue {
id : GenericId
typ : String
}
///|
/// A section containing text components and a button or thumbnail accessory.
#derive(ToJson, FromJson, Debug)
pub(all) struct Section {
id : Int32?
components : Array[Component]
accessory : Component
}
///|
/// A markdown text block displayed in a Components v2 layout.
#derive(ToJson, FromJson, Debug)
pub(all) struct TextDisplay {
id : Int32?
content : String
}
///|
/// A thumbnail accessory rendered from an unfurled media item.
#derive(ToJson, FromJson, Debug)
pub(all) struct Thumbnail {
id : Int32?
media : UnfurledMediaItem
description : Nullable[String]?
spoiler : Bool?
}
///|
/// A gallery containing one to ten media items.
#derive(ToJson, FromJson, Debug)
pub(all) struct MediaGallery {
id : Int32?
items : Array[MediaGalleryItem]
}
///|
/// One item in a media gallery.
#derive(ToJson, FromJson, Debug)
pub(all) struct MediaGalleryItem {
media : UnfurledMediaItem
description : Nullable[String]?
spoiler : Bool?
}
///|
/// A file attachment rendered as a Components v2 file component.
#derive(ToJson, FromJson, Debug)
pub(all) struct FileComponent {
id : Int32?
file : UnfurledMediaItem
spoiler : Bool?
name : String?
size : Int32?
}
///|
/// A visual separator between Components v2 content blocks.
#derive(ToJson, FromJson, Debug)
pub(all) struct Separator {
id : Int32?
divider : Bool?
spacing : Int32?
}
///|
/// A Components v2 container with an optional accent color.
#derive(ToJson, FromJson, Debug)
pub(all) struct Container {
id : Int32?
components : Array[Component]
accent_color : Nullable[Int32]?
spoiler : Bool?
}
///|
/// A modal label wrapping one interactive component.
#derive(ToJson, FromJson, Debug)
pub(all) struct Label {
id : Int32?
// Required when sending a modal; omitted in modal-submit payloads.
label : String?
description : String?
component : Component
}
///|
/// A modal file upload component.
#derive(ToJson, FromJson, Debug)
pub(all) struct FileUpload {
id : Int32?
custom_id : String
min_values : Int32?
max_values : Int32?
required : Bool?
values : Array[String]?
file_types : Array[FileTypeFilter]?
}
///|
/// A modal radio group accepting one option.
#derive(ToJson, FromJson, Debug)
pub(all) struct RadioGroup {
id : Int32?
custom_id : String
options : Array[RadioOption]
required : Bool?
value : String?
}
///|
/// A selectable option shared by radio and checkbox groups.
#derive(ToJson, FromJson, Debug)
pub(all) struct RadioOption {
value : String
label : String
description : String?
default : Bool?
}
///|
/// A modal checkbox group accepting zero or more options.
#derive(ToJson, FromJson, Debug)
pub(all) struct CheckboxGroup {
id : Int32?
custom_id : String
options : Array[RadioOption]
min_values : Int32?
max_values : Int32?
required : Bool?
values : Array[String]?
}
///|
/// A single modal checkbox.
#derive(ToJson, FromJson, Debug)
pub(all) struct Checkbox {
id : Int32?
custom_id : String
default : Bool?
value : Bool?
}
///|
/// Media metadata used by thumbnail, gallery, and file components.
#derive(ToJson, FromJson, Debug)
pub(all) struct UnfurledMediaItem {
url : String
proxy_url : String?
height : Nullable[Int32]?
width : Nullable[Int32]?
placeholder : String?
placeholder_version : Int32?
content_type : String?
flags : Int32?
/// Discord's content scanning result, once the media has been scanned. The
/// scan can finish after the message is sent, so it may first appear in a
/// Message Update.
content_scan_metadata : ContentScanMetadata?
attachment_id : String?
}
///|
/// The result of Discord's content scanning for an unfurled media item.
#derive(ToJson, FromJson, Debug)
pub(all) struct ContentScanMetadata {
flags : ContentScanFlags
}
///|
/// Bit flags recording what Discord's content scanning found in a media item.
#derive(Eq)
pub(all) struct ContentScanFlags(UInt64)
///|
/// No flags set.
pub fn ContentScanFlags::none() -> ContentScanFlags {
ContentScanFlags(0)
}
///|
/// Flags from a raw bit field; unknown bits are kept.
pub fn ContentScanFlags::from_bits(bits : UInt64) -> ContentScanFlags {
ContentScanFlags(bits)
}
///|
/// The raw bit field.
pub fn ContentScanFlags::bits(self : ContentScanFlags) -> UInt64 {
self.0
}
///|
/// Whether every flag in `other` is set.
pub fn ContentScanFlags::contains(
self : ContentScanFlags,
other : ContentScanFlags,
) -> Bool {
(self.0 & other.0) == other.0
}
///|
/// Combines flags: every flag set in either operand is set.
pub impl BitOr for ContentScanFlags with fn lor(a, b) {
ContentScanFlags(a.0 | b.0)
}
///|
/// Keeps the flags set in both operands.
pub impl BitAnd for ContentScanFlags with fn land(a, b) {
ContentScanFlags(a.0 & b.0)
}
///|
/// Debug output is the raw bit field.
pub impl Debug for ContentScanFlags with fn to_repr(self) {
Repr(self.0)
}
///|
/// Encodes the flags as a JSON number.
pub impl ToJson for ContentScanFlags with fn to_json(self) {
Json::number(self.0.to_double(), repr=self.0.to_string())
}
///|
/// Decodes the flags from a JSON number; unknown bits are kept.
pub impl @json.FromJson for ContentScanFlags with fn from_json(json, path) {
match json {
Number(_, repr=Some(repr)) =>
ContentScanFlags(
@string.parse_uint64(repr.to_string()) catch {
_ =>
raise JsonDecodeError(
(path, "expected content scan flags (number), got \{repr}"),
)
},
)
Number(value, repr=None) => ContentScanFlags(value.to_uint64())
_ => raise JsonDecodeError((path, "expected content scan flags (number)"))
}
}
///|
/// The media was flagged as explicit content.
pub fn ContentScanFlags::is_explicit_content() -> ContentScanFlags {
ContentScanFlags(1UL << 0)
}
///|
/// The media was flagged as gore.
pub fn ContentScanFlags::is_gore_content() -> ContentScanFlags {
ContentScanFlags(1UL << 1)
}
///|
/// The media was flagged as self-harm content.
pub fn ContentScanFlags::is_self_harm_content() -> ContentScanFlags {
ContentScanFlags(1UL << 2)
}
///|
/// A free-form text input component used in a modal.
#derive(ToJson, FromJson, Debug)
pub(all) struct TextInput {
id : Int32?
custom_id : String
// Required when sending a modal; omitted in modal-submit payloads.
style : TextInputStyle?
label : String?
min_length : Int32?
max_length : Int32?
required : Bool?
value : String?
placeholder : String?
}
///|
/// The single-line or multi-line style of a text input.
#derive(Eq, Debug)
#non_exhaustive
pub(all) enum TextInputStyle {
Short
Paragraph
Unknown(Int32)
}
///|
/// The Discord integer value of this `TextInputStyle`.
pub fn TextInputStyle::to_int(self : TextInputStyle) -> Int32 {
match self {
Short => 1
Paragraph => 2
Unknown(value) => value
}
}
///|
/// The `TextInputStyle` for its Discord integer value; unknown values become `Unknown(n)`.
pub fn TextInputStyle::from_int(value : Int32) -> TextInputStyle {
match value {
1 => Short
2 => Paragraph
value => Unknown(value)
}
}
///|
/// Encodes the `TextInputStyle` as its Discord integer value.
pub impl ToJson for TextInputStyle with fn to_json(self) {
Json::number(self.to_int().to_double())
}
///|
/// Decodes the `TextInputStyle` from a JSON number; unknown values become `Unknown(n)`.
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)"))
}
}