From 0d8ecc354a9b16741fbefc3312ebd35bb3fec7b3 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 21 Feb 2025 01:12:14 +0000 Subject: [PATCH 01/21] Partial (scuffed) components V2 support --- src/model/application/component.rs | 172 +++++++++++++++++++++++++++++ src/model/channel/message.rs | 3 + 2 files changed, 175 insertions(+) diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 4cd52bf27d1..ff4fd31f9b8 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -19,10 +19,182 @@ enum_number! { RoleSelect = 6, MentionableSelect = 7, ChannelSelect = 8, + Section = 9, + TextDisplay = 10, + Thumbnail = 11, + MediaGallery = 12, + File = 13, + Separator = 14, + Container = 17, _ => Unknown(u8), } } +// TODO: doc everything new :sob: + +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Serialize)] +#[non_exhaustive] +pub enum Component { + ActionRow(ActionRow), + Button(Button), + SelectMenu(SelectMenu), + Section(Section), + TextDisplay(TextDisplay), + MediaGallery(MediaGallery), + Separator(Separator), + File(FileComponent), + Container(Container), +} + +// TODO: add something like this to every variant. +// The component type, it will always be [`ComponentType::Thing`]. +// #[serde(rename = "type")] +// pub kind: ComponentType, + +// TODO: use fixedstring is places i missed when i find suitable lengths + +impl<'de> Deserialize<'de> for Component { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct ComponentRaw { + #[serde(rename = "type")] + kind: ComponentType, + } + + let value = <&RawValue>::deserialize(deserializer)?; + let raw = ComponentRaw::deserialize(value).map_err(DeError::custom)?; + + match raw.kind { + ComponentType::ActionRow => Deserialize::deserialize(value).map(Component::ActionRow), + ComponentType::Button => Deserialize::deserialize(value).map(Component::Button), + ComponentType::StringSelect + | ComponentType::UserSelect + | ComponentType::RoleSelect + | ComponentType::MentionableSelect + | ComponentType::ChannelSelect => { + Deserialize::deserialize(value).map(Component::SelectMenu) + }, + ComponentType::Section => Deserialize::deserialize(value).map(Component::Section), + ComponentType::TextDisplay => { + Deserialize::deserialize(value).map(Component::TextDisplay) + }, + ComponentType::MediaGallery => { + Deserialize::deserialize(value).map(Component::MediaGallery) + }, + ComponentType::Separator => Deserialize::deserialize(value).map(Component::Separator), + ComponentType::File => Deserialize::deserialize(value).map(Component::File), + ComponentType::Container => Deserialize::deserialize(value).map(Component::Container), + // TODO: maybe just not include it so the deserialization doesn't explode. + // With all new component types right now, the ENTIRE message won't deserialize. + // I need to do other stuff in other places too so that its as resilent as possible, it + // should not die when discord adds new stuff. + _ => Err(DeError::custom("Unknown component type")), + } + .map_err(DeError::custom) + } +} + +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct Section { + components: FixedArray, + accessory: SectionAccessory, +} + +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct Thumbnail { + media: UnfurledMediaItem, + description: Option, + spoiler: Option, +} + +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct UnfurledMediaItem { + url: String, +} + +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub enum SectionAccessory { + Thumbnail(Thumbnail), + Button(Button), +} + +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub enum SectionComponent { + TextDisplay(TextDisplay), + // TODO: check others because i'm rushing this. +} + +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct TextDisplay { + content: String, +} + +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct MediaGallery { + items: FixedArray, +} + +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct MediaGalleryItem { + media: UnfurledMediaItem, + description: Option, + spoiler: Option, +} + +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct Separator { + divider: Option, + spacing: Option, +} + +enum_number! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] + #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] + #[non_exhaustive] + pub enum SeparatorSpacingSize { + Small = 1, + Large = 2, + _ => Unknown(u8), + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct FileComponent { + file: UnfurledMediaItem, + spoiler: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct Container { + accent_color: Option, + spoiler: Option, + components: FixedArray, +} + /// An action row. /// /// [Discord docs](https://discord.com/developers/docs/interactions/message-components#action-rows). diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 9e7abb33be1..4751abc4c61 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -947,6 +947,9 @@ bitflags! { /// As of 2023-04-20, bots are currently not able to send voice messages /// ([source](https://github.com/discord/discord-api-docs/pull/6082)). const IS_VOICE_MESSAGE = 1 << 13; + /// TODO: document + /// for me: when enabled, content and embeds can't be used. + const IS_COMPONENTS_V2 = 1 << 15; } } From 17f4a057adaebf70cf5173729eb5aacf797d8dae Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 21 Feb 2025 19:21:44 +0000 Subject: [PATCH 02/21] fix deserilization --- examples/e01_basic_ping_bot/Cargo.toml | 1 + src/model/application/component.rs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/e01_basic_ping_bot/Cargo.toml b/examples/e01_basic_ping_bot/Cargo.toml index b030477b41e..e9a52a94cb4 100644 --- a/examples/e01_basic_ping_bot/Cargo.toml +++ b/examples/e01_basic_ping_bot/Cargo.toml @@ -7,3 +7,4 @@ edition.workspace = true [dependencies] serenity = { path = "../../", default-features = false, features = ["gateway", "model", "rustls_backend"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } +tracing-subscriber = "0.3.19" diff --git a/src/model/application/component.rs b/src/model/application/component.rs index ff4fd31f9b8..e74fcb51c42 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -41,10 +41,12 @@ pub enum Component { SelectMenu(SelectMenu), Section(Section), TextDisplay(TextDisplay), + Thumbnail(Thumbnail), MediaGallery(MediaGallery), Separator(Separator), File(FileComponent), Container(Container), + Unknown, // always update the macro below. } // TODO: add something like this to every variant. @@ -55,7 +57,7 @@ pub enum Component { // TODO: use fixedstring is places i missed when i find suitable lengths impl<'de> Deserialize<'de> for Component { - fn deserialize(deserializer: D) -> Result + fn deserialize(deserializer: D) -> std::result::Result where D: Deserializer<'de>, { From ac58152b0deb624baa0f5a72c916efa0d02f7b06 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 21 Feb 2025 19:53:16 +0000 Subject: [PATCH 03/21] partially handle unknown variants --- src/model/application/component.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/model/application/component.rs b/src/model/application/component.rs index e74fcb51c42..2e47611ae22 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -130,6 +130,7 @@ pub struct UnfurledMediaItem { pub enum SectionAccessory { Thumbnail(Thumbnail), Button(Button), + Unknown, } #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] @@ -137,6 +138,7 @@ pub enum SectionAccessory { #[non_exhaustive] pub enum SectionComponent { TextDisplay(TextDisplay), + Unknown, // TODO: check others because i'm rushing this. } From 24ef92ee5836db551409c8fe01c8a990f60aec85 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Sat, 22 Feb 2025 20:29:19 +0000 Subject: [PATCH 04/21] Start working on builders So... yeah, not under `unstable` yet but things are just starting to work! Need to refactor some old component stuff, and when i put this under the unstable feature I've got some code duplication to handle. Going well so far, sections work great and are really extensive --- examples/e01_basic_ping_bot/Cargo.toml | 1 + src/builder/create_components.rs | 134 ++++++++++++++++++ src/builder/create_interaction_response.rs | 10 +- .../create_interaction_response_followup.rs | 6 +- src/builder/create_message.rs | 6 +- src/builder/edit_interaction_response.rs | 4 +- src/builder/edit_message.rs | 6 +- src/builder/edit_webhook_message.rs | 6 +- src/builder/execute_webhook.rs | 6 +- src/builder/mod.rs | 6 +- src/collector/quick_modal.rs | 12 +- src/model/application/component.rs | 3 +- 12 files changed, 172 insertions(+), 28 deletions(-) diff --git a/examples/e01_basic_ping_bot/Cargo.toml b/examples/e01_basic_ping_bot/Cargo.toml index e9a52a94cb4..91cf7bca685 100644 --- a/examples/e01_basic_ping_bot/Cargo.toml +++ b/examples/e01_basic_ping_bot/Cargo.toml @@ -5,6 +5,7 @@ authors = ["my name "] edition.workspace = true [dependencies] +serde_json = "1.0.139" serenity = { path = "../../", default-features = false, features = ["gateway", "model", "rustls_backend"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } tracing-subscriber = "0.3.19" diff --git a/src/builder/create_components.rs b/src/builder/create_components.rs index 4729c6dfa2a..13a170decb5 100644 --- a/src/builder/create_components.rs +++ b/src/builder/create_components.rs @@ -4,6 +4,15 @@ use serde::Serialize; use crate::model::prelude::*; +#[derive(Clone, Debug)] +struct StaticU8; + +impl Serialize for StaticU8 { + fn serialize(&self, ser: S) -> Result { + ser.serialize_u8(VAL) + } +} + /// A builder for creating a components action row in a message. /// /// [Discord docs](https://discord.com/developers/docs/interactions/message-components#component-object). @@ -47,6 +56,131 @@ impl serde::Serialize for CreateActionRow<'_> { } } +/// TODO: doc +#[derive(Clone, Debug, Serialize)] +#[must_use] +#[serde(untagged)] +pub enum CreateComponent<'a> { + /// A regular action row, a V1 component. + ActionRow(CreateActionRow<'a>), + /// A section, V2 component. + Section(CreateSection<'a>), +} + +#[derive(Clone, Debug, Serialize)] +#[must_use] +pub struct CreateSection<'a> { + #[serde(rename = "type")] + kind: StaticU8<9>, + // so i gotta include a type to all of these so discord can serialize properly. + #[serde(skip_serializing_if = "<[_]>::is_empty")] + components: Cow<'a, [CreateSectionComponent<'a>]>, + accessory: CreateSectionAccessory<'a>, +} + +impl<'a> CreateSection<'a> { + // TODO: change type + pub fn new( + components: impl Into]>>, + accessory: CreateSectionAccessory<'a>, + ) -> Self { + CreateSection { + kind: StaticU8::<9>, + components: components.into(), + accessory, + } + } +} + +#[derive(Clone, Debug, Serialize)] +#[must_use] +#[serde(untagged)] +pub enum CreateSectionComponent<'a> { + TextDisplay(CreateTextDisplay<'a>), +} + +#[derive(Clone, Debug, Serialize)] +#[must_use] +pub struct CreateTextDisplay<'a> { + #[serde(rename = "type")] + kind: StaticU8<10>, + content: Cow<'a, str>, +} + +impl<'a> CreateTextDisplay<'a> { + pub fn new(content: impl Into>) -> Self { + CreateTextDisplay { + kind: StaticU8::<10>, + content: content.into(), + } + } + + pub fn content(mut self, content: impl Into>) -> Self { + self.content = content.into(); + self + } +} + +#[derive(Clone, Debug, Serialize)] +#[must_use] +#[serde(untagged)] +pub enum CreateSectionAccessory<'a> { + // Thumbnail(CreateThumbnail<'a>), + // TODO: check if it actually still is unsupported here, docs say it will be supported. + Button(CreateButton<'a>), +} + +#[derive(Clone, Debug, Serialize)] +#[must_use] +pub struct CreateThumbnail<'a> { + #[serde(rename = "type")] + kind: StaticU8<11>, + media: CreateUnfurledMediaItem<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + description: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + spoiler: Option, +} + +impl<'a> CreateThumbnail<'a> { + pub fn new(media: CreateUnfurledMediaItem<'a>) -> Self { + CreateThumbnail { + kind: StaticU8::<11>, + media, + description: None, + spoiler: None, + } + } + + pub fn media(mut self, media: CreateUnfurledMediaItem<'a>) -> Self { + self.media = media; + self + } + + pub fn description(mut self, description: impl Into>) -> Self { + self.description = Some(description.into()); + self + } + + pub fn spoiler(mut self, spoiler: bool) -> Self { + self.spoiler = Some(spoiler); + self + } +} + +#[derive(Clone, Debug, Serialize, Default)] +#[must_use] +pub struct CreateUnfurledMediaItem<'a> { + url: Cow<'a, str>, +} + +impl<'a> CreateUnfurledMediaItem<'a> { + pub fn url(mut self, url: impl Into>) -> Self { + self.url = url.into(); + self + } +} + /// A builder for creating a button component in a message #[derive(Clone, Debug, Serialize)] #[must_use] diff --git a/src/builder/create_interaction_response.rs b/src/builder/create_interaction_response.rs index d42c7b4d7c4..fb1059f657c 100644 --- a/src/builder/create_interaction_response.rs +++ b/src/builder/create_interaction_response.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; use super::create_poll::Ready; use super::{ - CreateActionRow, CreateAllowedMentions, CreateAttachment, + CreateComponent, CreateEmbed, CreatePoll, EditAttachments, @@ -160,7 +160,7 @@ pub struct CreateInteractionResponseMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] flags: Option, #[serde(skip_serializing_if = "Option::is_none")] - components: Option]>>, + components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] poll: Option>, attachments: EditAttachments<'a>, @@ -273,7 +273,7 @@ impl<'a> CreateInteractionResponseMessage<'a> { } /// Sets the components of this message. - pub fn components(mut self, components: impl Into]>>) -> Self { + pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } @@ -423,7 +423,7 @@ impl<'a> CreateAutocompleteResponse<'a> { #[derive(Clone, Debug, Default, Serialize)] #[must_use] pub struct CreateModal<'a> { - components: Cow<'a, [CreateActionRow<'a>]>, + components: Cow<'a, [CreateComponent<'a>]>, custom_id: Cow<'a, str>, title: Cow<'a, str>, } @@ -441,7 +441,7 @@ impl<'a> CreateModal<'a> { /// Sets the components of this message. /// /// Overwrites existing components. - pub fn components(mut self, components: impl Into]>>) -> Self { + pub fn components(mut self, components: impl Into]>>) -> Self { self.components = components.into(); self } diff --git a/src/builder/create_interaction_response_followup.rs b/src/builder/create_interaction_response_followup.rs index dab3681dc6d..dae8059eab1 100644 --- a/src/builder/create_interaction_response_followup.rs +++ b/src/builder/create_interaction_response_followup.rs @@ -2,9 +2,9 @@ use std::borrow::Cow; use super::create_poll::Ready; use super::{ - CreateActionRow, CreateAllowedMentions, CreateAttachment, + CreateComponent, CreateEmbed, CreatePoll, EditAttachments, @@ -29,7 +29,7 @@ pub struct CreateInteractionResponseFollowup<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] - components: Option]>>, + components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] flags: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -153,7 +153,7 @@ impl<'a> CreateInteractionResponseFollowup<'a> { } /// Sets the components of this message. - pub fn components(mut self, components: impl Into]>>) -> Self { + pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } diff --git a/src/builder/create_message.rs b/src/builder/create_message.rs index 3f4bdfdb502..c01e3f0f9ed 100644 --- a/src/builder/create_message.rs +++ b/src/builder/create_message.rs @@ -2,9 +2,9 @@ use std::borrow::Cow; use super::create_poll::Ready; use super::{ - CreateActionRow, CreateAllowedMentions, CreateAttachment, + CreateComponent, CreateEmbed, CreatePoll, EditAttachments, @@ -63,7 +63,7 @@ pub struct CreateMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] message_reference: Option, #[serde(skip_serializing_if = "Option::is_none")] - components: Option]>>, + components: Option]>>, sticker_ids: Cow<'a, [StickerId]>, #[serde(skip_serializing_if = "Option::is_none")] flags: Option, @@ -184,7 +184,7 @@ impl<'a> CreateMessage<'a> { } /// Sets the components of this message. - pub fn components(mut self, components: impl Into]>>) -> Self { + pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } diff --git a/src/builder/edit_interaction_response.rs b/src/builder/edit_interaction_response.rs index e28dcdc1b5e..4b344e31a1a 100644 --- a/src/builder/edit_interaction_response.rs +++ b/src/builder/edit_interaction_response.rs @@ -1,9 +1,9 @@ use std::borrow::Cow; use super::{ - CreateActionRow, CreateAllowedMentions, CreateAttachment, + CreateComponent, CreateEmbed, EditAttachments, EditWebhookMessage, @@ -70,7 +70,7 @@ impl<'a> EditInteractionResponse<'a> { } /// Sets the components of this message. - pub fn components(self, components: impl Into]>>) -> Self { + pub fn components(self, components: impl Into]>>) -> Self { Self(self.0.components(components)) } super::button_and_select_menu_convenience_methods!(self.0.components); diff --git a/src/builder/edit_message.rs b/src/builder/edit_message.rs index 6ca6a0d05d8..c20c9f441d9 100644 --- a/src/builder/edit_message.rs +++ b/src/builder/edit_message.rs @@ -1,9 +1,9 @@ use std::borrow::Cow; use super::{ - CreateActionRow, CreateAllowedMentions, CreateAttachment, + CreateComponent, CreateEmbed, EditAttachments, }; @@ -45,7 +45,7 @@ pub struct EditMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] - components: Option]>>, + components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] attachments: Option>, } @@ -136,7 +136,7 @@ impl<'a> EditMessage<'a> { } /// Sets the components of this message. - pub fn components(mut self, components: impl Into]>>) -> Self { + pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } diff --git a/src/builder/edit_webhook_message.rs b/src/builder/edit_webhook_message.rs index e794dcfab4e..a1a985fc36c 100644 --- a/src/builder/edit_webhook_message.rs +++ b/src/builder/edit_webhook_message.rs @@ -1,9 +1,9 @@ use std::borrow::Cow; use super::{ - CreateActionRow, CreateAllowedMentions, CreateAttachment, + CreateComponent, CreateEmbed, EditAttachments, }; @@ -26,7 +26,7 @@ pub struct EditWebhookMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub(crate) components: Option]>>, + pub(crate) components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] pub(crate) attachments: Option>, @@ -107,7 +107,7 @@ impl<'a> EditWebhookMessage<'a> { /// /// [`WebhookType::Application`]: crate::model::webhook::WebhookType /// [`WebhookType::Incoming`]: crate::model::webhook::WebhookType - pub fn components(mut self, components: impl Into]>>) -> Self { + pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } diff --git a/src/builder/execute_webhook.rs b/src/builder/execute_webhook.rs index 2eb9c81d5e3..2a7c084d155 100644 --- a/src/builder/execute_webhook.rs +++ b/src/builder/execute_webhook.rs @@ -1,9 +1,9 @@ use std::borrow::Cow; use super::{ - CreateActionRow, CreateAllowedMentions, CreateAttachment, + CreateComponent, CreateEmbed, EditAttachments, }; @@ -68,7 +68,7 @@ pub struct ExecuteWebhook<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] - components: Option]>>, + components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] flags: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -216,7 +216,7 @@ impl<'a> ExecuteWebhook<'a> { /// /// [`WebhookType::Application`]: crate::model::webhook::WebhookType /// [`WebhookType::Incoming`]: crate::model::webhook::WebhookType - pub fn components(mut self, components: impl Into]>>) -> Self { + pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } diff --git a/src/builder/mod.rs b/src/builder/mod.rs index d923cbcae63..f08ab8faad5 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -130,12 +130,12 @@ macro_rules! button_and_select_menu_convenience_methods { pub fn button(mut $self, button: super::CreateButton<'a>) -> Self { let rows = $self$(.$components_path)+.get_or_insert_with(Cow::default).to_mut(); let row_with_space_left = rows.last_mut().and_then(|row| match row { - super::CreateActionRow::Buttons(buttons) if buttons.len() < 5 => Some(buttons.to_mut()), + super::CreateComponent::ActionRow(super::CreateActionRow::Buttons(buttons)) if buttons.len() < 5 => Some(buttons.to_mut()), _ => None, }); match row_with_space_left { Some(row) => row.push(button), - None => rows.push(super::CreateActionRow::buttons(vec![button])), + None => rows.push(super::CreateComponent::ActionRow(super::CreateActionRow::buttons(vec![button]))), } $self } @@ -147,7 +147,7 @@ macro_rules! button_and_select_menu_convenience_methods { $self$(.$components_path)+ .get_or_insert_with(Cow::default) .to_mut() - .push(super::CreateActionRow::SelectMenu(select_menu)); + .push(super::CreateComponent::ActionRow(super::CreateActionRow::SelectMenu(select_menu))); $self } }; diff --git a/src/collector/quick_modal.rs b/src/collector/quick_modal.rs index d4d0c40d74f..7b84013208e 100644 --- a/src/collector/quick_modal.rs +++ b/src/collector/quick_modal.rs @@ -1,6 +1,12 @@ use std::borrow::Cow; -use crate::builder::{CreateActionRow, CreateInputText, CreateInteractionResponse, CreateModal}; +use crate::builder::{ + CreateActionRow, + CreateComponent, + CreateInputText, + CreateInteractionResponse, + CreateModal, +}; use crate::collector::ModalInteractionCollector; use crate::gateway::client::Context; use crate::internal::prelude::*; @@ -91,7 +97,9 @@ impl<'a> CreateQuickModal<'a> { .into_iter() .enumerate() .map(|(i, input_text)| { - CreateActionRow::InputText(input_text.custom_id(i.to_string())) + CreateComponent::ActionRow(CreateActionRow::InputText( + input_text.custom_id(i.to_string()), + )) }) .collect::>(), ), diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 2e47611ae22..b40fe652ce1 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -46,7 +46,8 @@ pub enum Component { Separator(Separator), File(FileComponent), Container(Container), - Unknown, // always update the macro below. + Unknown, + // always update the macro below. } // TODO: add something like this to every variant. From 07b42cc6f56308b074a5ac812d22b6418f02b547 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Sun, 23 Feb 2025 01:14:40 +0000 Subject: [PATCH 05/21] Hopefully last builder related change I do need to look at resolved media stuff, as right now i don't handle that. --- src/builder/create_components.rs | 147 ++++++++++++++++++++++++++++- src/model/application/component.rs | 2 +- 2 files changed, 145 insertions(+), 4 deletions(-) diff --git a/src/builder/create_components.rs b/src/builder/create_components.rs index 13a170decb5..c6bb5fbc877 100644 --- a/src/builder/create_components.rs +++ b/src/builder/create_components.rs @@ -72,7 +72,6 @@ pub enum CreateComponent<'a> { pub struct CreateSection<'a> { #[serde(rename = "type")] kind: StaticU8<9>, - // so i gotta include a type to all of these so discord can serialize properly. #[serde(skip_serializing_if = "<[_]>::is_empty")] components: Cow<'a, [CreateSectionComponent<'a>]>, accessory: CreateSectionAccessory<'a>, @@ -125,8 +124,7 @@ impl<'a> CreateTextDisplay<'a> { #[must_use] #[serde(untagged)] pub enum CreateSectionAccessory<'a> { - // Thumbnail(CreateThumbnail<'a>), - // TODO: check if it actually still is unsupported here, docs say it will be supported. + Thumbnail(CreateThumbnail<'a>), Button(CreateButton<'a>), } @@ -175,12 +173,155 @@ pub struct CreateUnfurledMediaItem<'a> { } impl<'a> CreateUnfurledMediaItem<'a> { + pub fn new(url: impl Into>) -> Self { + CreateUnfurledMediaItem { + url: url.into(), + } + } + pub fn url(mut self, url: impl Into>) -> Self { self.url = url.into(); self } } +#[derive(Clone, Debug, Serialize)] +#[must_use] +pub struct CreateMediaGallery<'a> { + #[serde(rename = "type")] + kind: StaticU8<12>, + items: Cow<'a, [CreateSectionComponent<'a>]>, +} + +impl<'a> CreateMediaGallery<'a> { + pub fn new(items: impl Into]>>) -> Self { + CreateMediaGallery { + kind: StaticU8::<12>, + items: items.into(), + } + } + + pub fn items(mut self, items: impl Into]>>) -> Self { + self.items = items.into(); + self + } +} + +#[derive(Clone, Debug, Serialize, Default)] +#[must_use] +pub struct CreateMediaGalleryItem<'a> { + media: CreateUnfurledMediaItem<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + description: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + spoiler: Option, +} + +#[derive(Clone, Debug, Serialize)] +#[must_use] +pub struct CreateFile<'a> { + #[serde(rename = "type")] + kind: StaticU8<13>, + file: CreateUnfurledMediaItem<'a>, + #[serde(skip_serializing_if = "Option::is_none")] + spoiler: Option, +} + +impl<'a> CreateFile<'a> { + pub fn new(file: impl Into>) -> Self { + CreateFile { + kind: StaticU8::<13>, + file: file.into(), + spoiler: None, + } + } + + // Only supports attachment:// format. + pub fn file(mut self, file: impl Into>) -> Self { + self.file = file.into(); + self + } + + pub fn spoiler(mut self, spoiler: bool) -> Self { + self.spoiler = Some(spoiler); + self + } +} + +#[derive(Clone, Debug, Serialize)] +#[must_use] +pub struct CreateSeparator { + #[serde(rename = "type")] + kind: StaticU8<14>, + divider: bool, + #[serde(skip_serializing_if = "Option::is_none")] + spacing: Option, +} + +impl CreateSeparator { + pub fn new(divider: bool) -> Self { + CreateSeparator { + kind: StaticU8::<14>, + divider, + spacing: None, + } + } + + pub fn spacing(mut self, spacing: Spacing) -> Self { + self.spacing = Some(spacing); + self + } +} + +#[derive(Clone, Debug, Serialize)] +#[must_use] +pub struct CreateContainer<'a> { + #[serde(rename = "type")] + kind: StaticU8<17>, + #[serde(skip_serializing_if = "Option::is_none")] + accent_color: Option, + #[serde(skip_serializing_if = "Option::is_none")] + spoiler: Option, + components: Cow<'a, [CreateComponent<'a>]>, +} + +impl<'a> CreateContainer<'a> { + pub fn new(components: impl Into]>>) -> Self { + CreateContainer { + kind: StaticU8::<17>, + accent_color: None, + spoiler: None, + components: components.into(), + } + } + + pub fn accent_color(mut self, accent_color: Colour) -> Self { + self.accent_color = Some(accent_color); + self + } + + pub fn spoiler(mut self, spoiler: bool) -> Self { + self.spoiler = Some(spoiler); + self + } + + pub fn components(mut self, components: impl Into]>>) -> Self { + self.components = components.into(); + self + } +} + +enum_number! { + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] + #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] + #[non_exhaustive] + pub enum Spacing { + Small = 1, + Large = 2, + _ => Unknown(u8), + } +} + /// A builder for creating a button component in a message #[derive(Clone, Debug, Serialize)] #[must_use] diff --git a/src/model/application/component.rs b/src/model/application/component.rs index b40fe652ce1..61b4b0273dc 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -195,7 +195,7 @@ pub struct FileComponent { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct Container { - accent_color: Option, + accent_color: Option, spoiler: Option, components: FixedArray, } From 8879990b7e4968de1882d366dfdefe662edd701a Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Sun, 23 Feb 2025 01:22:22 +0000 Subject: [PATCH 06/21] typesize --- src/model/application/component.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 61b4b0273dc..7703501dd33 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -186,6 +186,7 @@ enum_number! { } #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] pub struct FileComponent { file: UnfurledMediaItem, @@ -193,6 +194,7 @@ pub struct FileComponent { } #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] pub struct Container { accent_color: Option, From 9091a5af8e8954b46ef03b1546d7c92b1e4bf2ad Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Thu, 27 Feb 2025 01:17:07 +0000 Subject: [PATCH 07/21] fix builders, test, fixedstring, type resolved variant of media items --- examples/e01_basic_ping_bot/Cargo.toml | 4 +-- src/builder/create_components.rs | 38 +++++++++++++++++--- src/model/application/component.rs | 48 +++++++++++++++++++++----- 3 files changed, 75 insertions(+), 15 deletions(-) diff --git a/examples/e01_basic_ping_bot/Cargo.toml b/examples/e01_basic_ping_bot/Cargo.toml index 91cf7bca685..eefccc62a28 100644 --- a/examples/e01_basic_ping_bot/Cargo.toml +++ b/examples/e01_basic_ping_bot/Cargo.toml @@ -5,7 +5,5 @@ authors = ["my name "] edition.workspace = true [dependencies] -serde_json = "1.0.139" serenity = { path = "../../", default-features = false, features = ["gateway", "model", "rustls_backend"] } -tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } -tracing-subscriber = "0.3.19" +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } \ No newline at end of file diff --git a/src/builder/create_components.rs b/src/builder/create_components.rs index c6bb5fbc877..2ff59569556 100644 --- a/src/builder/create_components.rs +++ b/src/builder/create_components.rs @@ -65,6 +65,12 @@ pub enum CreateComponent<'a> { ActionRow(CreateActionRow<'a>), /// A section, V2 component. Section(CreateSection<'a>), + TextDisplay(CreateTextDisplay<'a>), + Thumbnail(CreateThumbnail<'a>), + MediaGallery(CreateMediaGallery<'a>), + File(CreateFile<'a>), + Separator(CreateSeparator), + Container(CreateContainer<'a>), } #[derive(Clone, Debug, Serialize)] @@ -78,7 +84,6 @@ pub struct CreateSection<'a> { } impl<'a> CreateSection<'a> { - // TODO: change type pub fn new( components: impl Into]>>, accessory: CreateSectionAccessory<'a>, @@ -190,18 +195,18 @@ impl<'a> CreateUnfurledMediaItem<'a> { pub struct CreateMediaGallery<'a> { #[serde(rename = "type")] kind: StaticU8<12>, - items: Cow<'a, [CreateSectionComponent<'a>]>, + items: Cow<'a, [CreateMediaGalleryItem<'a>]>, } impl<'a> CreateMediaGallery<'a> { - pub fn new(items: impl Into]>>) -> Self { + pub fn new(items: impl Into]>>) -> Self { CreateMediaGallery { kind: StaticU8::<12>, items: items.into(), } } - pub fn items(mut self, items: impl Into]>>) -> Self { + pub fn items(mut self, items: impl Into]>>) -> Self { self.items = items.into(); self } @@ -217,6 +222,31 @@ pub struct CreateMediaGalleryItem<'a> { spoiler: Option, } +impl<'a> CreateMediaGalleryItem<'a> { + pub fn new(media: CreateUnfurledMediaItem<'a>) -> Self { + CreateMediaGalleryItem { + media, + description: None, + spoiler: None, + } + } + + pub fn media(mut self, media: CreateUnfurledMediaItem<'a>) -> Self { + self.media = media; + self + } + + pub fn description(mut self, description: impl Into>) -> Self { + self.description = Some(description.into()); + self + } + + pub fn spoiler(mut self, spoiler: bool) -> Self { + self.spoiler = Some(spoiler); + self + } +} + #[derive(Clone, Debug, Serialize)] #[must_use] pub struct CreateFile<'a> { diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 7703501dd33..63489d26beb 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -1,3 +1,4 @@ +use nonmax::NonMaxU32; use serde::de::Error as DeError; use serde::ser::{Serialize, Serializer}; use serde_json::value::RawValue; @@ -52,10 +53,6 @@ pub enum Component { // TODO: add something like this to every variant. // The component type, it will always be [`ComponentType::Thing`]. -// #[serde(rename = "type")] -// pub kind: ComponentType, - -// TODO: use fixedstring is places i missed when i find suitable lengths impl<'de> Deserialize<'de> for Component { fn deserialize(deserializer: D) -> std::result::Result @@ -114,15 +111,50 @@ pub struct Section { #[non_exhaustive] pub struct Thumbnail { media: UnfurledMediaItem, - description: Option, + description: Option>, spoiler: Option, } +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +#[serde(untagged)] +pub enum MediaItem { + Resolved(ResolvedUnfurledMediaItem), + Unresolved(UnfurledMediaItem), +} + #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct UnfurledMediaItem { - url: String, + url: FixedString, +} + +#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[non_exhaustive] +pub struct ResolvedUnfurledMediaItem { + url: FixedString, + proxy_url: FixedString, + width: NonMaxU32, + height: NonMaxU32, + content_type: FixedString, + loading_state: UnfurledMediaItemLoadingState, +} + +enum_number! { + /// The loading state of the media item. + #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] + #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] + #[non_exhaustive] + pub enum UnfurledMediaItemLoadingState { + DiscordUnknown = 0, + Loading = 1, + LoadingSuccess = 2, + LoadingNotFound = 3, + _ => Unknown(u8), + } } #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] @@ -147,7 +179,7 @@ pub enum SectionComponent { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct TextDisplay { - content: String, + content: FixedString, } #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] @@ -162,7 +194,7 @@ pub struct MediaGallery { #[non_exhaustive] pub struct MediaGalleryItem { media: UnfurledMediaItem, - description: Option, + description: Option>, spoiler: Option, } From 036e59e27c69b3b33cd52a78c45cbd58f0ada696 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Thu, 27 Feb 2025 01:20:11 +0000 Subject: [PATCH 08/21] actually revert changes --- examples/e01_basic_ping_bot/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/e01_basic_ping_bot/Cargo.toml b/examples/e01_basic_ping_bot/Cargo.toml index eefccc62a28..b030477b41e 100644 --- a/examples/e01_basic_ping_bot/Cargo.toml +++ b/examples/e01_basic_ping_bot/Cargo.toml @@ -6,4 +6,4 @@ edition.workspace = true [dependencies] serenity = { path = "../../", default-features = false, features = ["gateway", "model", "rustls_backend"] } -tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } \ No newline at end of file +tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } From 4622262b76a10455d07c1bd045c89f3490d31568 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 28 Feb 2025 00:23:55 +0000 Subject: [PATCH 09/21] partial, incomplete docs (no builders) --- src/model/application/component.rs | 72 ++++++++++++++++++++++++++++-- src/model/channel/message.rs | 16 ++++++- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 63489d26beb..1087e4fbb47 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -31,8 +31,14 @@ enum_number! { } } -// TODO: doc everything new :sob: - +/// Represents Discord components, a part of messages that are usually interactable. +/// +/// # Component Versioning +/// +/// - When `IS_COMPONENTS_V2` is **not** set, the **only** valid top-level component is +/// [`ActionRow`]. +/// - When `IS_COMPONENTS_V2` **is** set, other component types may be used at the top level, but +/// other message limitations are applied. #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Serialize)] #[non_exhaustive] @@ -98,23 +104,38 @@ impl<'de> Deserialize<'de> for Component { } } +/// A component that is a container for up to 3 text display components and an accessory. +/// +/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct Section { - components: FixedArray, + /// Always [`ComponentType::Section`] + #[serde(rename = "type")] + pub kind: ComponentType, + components: FixedArray, accessory: SectionAccessory, } +/// A section component's thumbnail. +/// +/// See [`Section`] for how this fits within a section. +/// +/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct Thumbnail { + /// Always [`ComponentType::Thumbnail`] + #[serde(rename = "type")] + pub kind: ComponentType, media: UnfurledMediaItem, description: Option>, spoiler: Option, } +/// An abstraction over a resolved and unresolved unfurled media item. #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -124,6 +145,9 @@ pub enum MediaItem { Unresolved(UnfurledMediaItem), } +/// An unfurled media item, stores the url to the item. +/// +/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -131,6 +155,9 @@ pub struct UnfurledMediaItem { url: FixedString, } +/// A resolved unfurled media item, with extra metadata added by Discord. +/// +/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -157,6 +184,11 @@ enum_number! { } } +/// A list of valid components for an accessory of a section. +/// +/// See [`Section`] for how this works. +/// +/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -172,9 +204,11 @@ pub enum SectionAccessory { pub enum SectionComponent { TextDisplay(TextDisplay), Unknown, - // TODO: check others because i'm rushing this. } +/// A text display component. +/// +/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -182,13 +216,24 @@ pub struct TextDisplay { content: FixedString, } +/// A media gallery component. +/// +/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct MediaGallery { + /// Always [`ComponentType::MediaGallery`] + #[serde(rename = "type")] + pub kind: ComponentType, items: FixedArray, } +/// An individual media gallery item. +/// +/// Belongs to [`MediaGallery`]. +/// +/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -198,15 +243,22 @@ pub struct MediaGalleryItem { spoiler: Option, } +/// A separator component +/// +/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct Separator { + /// Always [`ComponentType::Separator`] + #[serde(rename = "type")] + pub kind: ComponentType, divider: Option, spacing: Option, } enum_number! { + /// The size of a separator component. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] @@ -217,18 +269,30 @@ enum_number! { } } +/// A file component, will not render a text preview to the user. +/// +/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] pub struct FileComponent { + /// Always [`ComponentType::File`] + #[serde(rename = "type")] + pub kind: ComponentType, file: UnfurledMediaItem, spoiler: Option, } +/// A container component, similar to an embed but without all the functionality. +/// +/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] pub struct Container { + /// Always [`ComponentType::Container`] + #[serde(rename = "type")] + pub kind: ComponentType, accent_color: Option, spoiler: Option, components: FixedArray, diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 4751abc4c61..6a2d979ce38 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -947,9 +947,21 @@ bitflags! { /// As of 2023-04-20, bots are currently not able to send voice messages /// ([source](https://github.com/discord/discord-api-docs/pull/6082)). const IS_VOICE_MESSAGE = 1 << 13; - /// TODO: document - /// for me: when enabled, content and embeds can't be used. + /// Enables support for sending Components V2. + /// + /// Setting this flag is required to use V2 components. + /// Attempting to send V2 components without enabling this flag will result in an error. + /// + /// # Limitations + /// When this flag is enabled, certain restrictions apply: + /// - The `content` and `embeds` fields cannot be set. + /// - Audio file attachments are not supported. + /// - Files will not have a simple text preview. + /// - URLs will not generate embeds. + /// + /// For more details, refer to the Discord documentation: [https://github.com/Lulalaby/discord-api-docs/pull/30] const IS_COMPONENTS_V2 = 1 << 15; + } } From df6604a5036a171f8fabce4514ec70c29be760e0 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 28 Feb 2025 14:30:27 +0000 Subject: [PATCH 10/21] doc model fields, make public and don't restrict recieved types --- src/model/application/component.rs | 77 +++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 1087e4fbb47..043ef71deef 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -114,8 +114,15 @@ pub struct Section { /// Always [`ComponentType::Section`] #[serde(rename = "type")] pub kind: ComponentType, - components: FixedArray, - accessory: SectionAccessory, + /// The components inside of the section. + /// + /// As of 2025-02-28, this is limited to just [`ComponentType::TextDisplay`] with up to 3 max. + pub components: FixedArray, + /// The accessory to the side of the section. + /// + /// As of 2025-02-28, this is limited to [`ComponentType::Button`] or + /// [`ComponentType::Thumbnail`] + pub accessory: Box, } /// A section component's thumbnail. @@ -130,8 +137,11 @@ pub struct Thumbnail { /// Always [`ComponentType::Thumbnail`] #[serde(rename = "type")] pub kind: ComponentType, - media: UnfurledMediaItem, + /// The internal media item this contains. + pub media: UnfurledMediaItem, + /// The description of the thumbnail. description: Option>, + /// Whether or not this component is spoilered. spoiler: Option, } @@ -152,7 +162,8 @@ pub enum MediaItem { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct UnfurledMediaItem { - url: FixedString, + /// The url of this item. + pub url: FixedString, } /// A resolved unfurled media item, with extra metadata added by Discord. @@ -162,12 +173,18 @@ pub struct UnfurledMediaItem { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct ResolvedUnfurledMediaItem { - url: FixedString, - proxy_url: FixedString, - width: NonMaxU32, - height: NonMaxU32, - content_type: FixedString, - loading_state: UnfurledMediaItemLoadingState, + /// The url of this item. + pub url: FixedString, + /// The proxied discord url. + pub proxy_url: FixedString, + /// The width of the media item. + pub width: NonMaxU32, + /// The height of the media item. + pub height: NonMaxU32, + /// The content type of the media item. + pub content_type: FixedString, + /// The loading state of the item, declaring if it has fully loaded yet. + pub loading_state: UnfurledMediaItemLoadingState, } enum_number! { @@ -213,7 +230,8 @@ pub enum SectionComponent { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct TextDisplay { - content: FixedString, + /// The content of this text display component. + pub content: FixedString, } /// A media gallery component. @@ -226,7 +244,8 @@ pub struct MediaGallery { /// Always [`ComponentType::MediaGallery`] #[serde(rename = "type")] pub kind: ComponentType, - items: FixedArray, + /// Array of images this media gallery can contain, max of 10. + pub items: FixedArray, } /// An individual media gallery item. @@ -238,9 +257,12 @@ pub struct MediaGallery { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] pub struct MediaGalleryItem { - media: UnfurledMediaItem, - description: Option>, - spoiler: Option, + /// The internal media piece that this item contains. + pub media: UnfurledMediaItem, + /// The description of the media item. + pub description: Option>, + /// Whether or not this component is spoilered. + pub spoiler: Option, } /// A separator component @@ -253,8 +275,10 @@ pub struct Separator { /// Always [`ComponentType::Separator`] #[serde(rename = "type")] pub kind: ComponentType, - divider: Option, - spacing: Option, + /// Whether or not this contains a separating divider. + pub divider: Option, + /// The spacing of the separator. + pub spacing: Option, } enum_number! { @@ -279,8 +303,10 @@ pub struct FileComponent { /// Always [`ComponentType::File`] #[serde(rename = "type")] pub kind: ComponentType, - file: UnfurledMediaItem, - spoiler: Option, + /// The file this component internally contains. + pub file: UnfurledMediaItem, + /// Whether or not this component is spoilered. + pub spoiler: Option, } /// A container component, similar to an embed but without all the functionality. @@ -293,9 +319,16 @@ pub struct Container { /// Always [`ComponentType::Container`] #[serde(rename = "type")] pub kind: ComponentType, - accent_color: Option, - spoiler: Option, - components: FixedArray, + /// The accent colour, similar to an embeds accent. + pub accent_color: Option, + /// Whether or not this component is spoilered. + pub spoiler: Option, + /// The components within this container. + /// + /// As of 2025-02-28, this can be [`ComponentType::ActionRow`], [`ComponentType::Section`], + /// [`ComponentType::TextDisplay`], [`ComponentType::MediaGallery`], [`ComponentType::File`] or + /// [`ComponentType::Separator`] + pub components: FixedArray, } /// An action row. From cea1ef87c7003f5a4545283d96dd4ee8dcacac28 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 28 Feb 2025 15:40:50 +0000 Subject: [PATCH 11/21] doc rest --- src/builder/create_components.rs | 175 +++++++++++++++++++++++++++++-- 1 file changed, 168 insertions(+), 7 deletions(-) diff --git a/src/builder/create_components.rs b/src/builder/create_components.rs index 2ff59569556..05ae217c2da 100644 --- a/src/builder/create_components.rs +++ b/src/builder/create_components.rs @@ -56,23 +56,60 @@ impl serde::Serialize for CreateActionRow<'_> { } } -/// TODO: doc +/// A builder for creating components in a structured way. +/// +/// This enum supports both V1 and V2 components, with the exception of `ActionRow`, which is a V1 +/// component. +/// +/// ## V2 Components and Message Flags +/// To send V2 components, you must set [`MessageFlags::IS_COMPONENTS_V2`]. +/// +/// ### Limitations +/// - You can include a maximum of **10 top-level components** per message. +/// - The total number of **nested components** is limited to **30**. +/// - The maximum character count for text within components is **4000**. +/// - The ability to set the `content` and `embeds` field will be disabled +/// - No support for audio files +/// - No simple text preview for files +/// - No embeds for urls #[derive(Clone, Debug, Serialize)] #[must_use] #[serde(untagged)] pub enum CreateComponent<'a> { - /// A regular action row, a V1 component. + /// Represents an action row component (V1). + /// + /// An action row is a container for other interactive components, such as buttons and select + /// menus. ActionRow(CreateActionRow<'a>), - /// A section, V2 component. + /// Represents a section component (V2). + /// + /// A section is used to structure and group other components with an accessory. Section(CreateSection<'a>), + /// Represents a text display component (V2). + /// + /// This component is used for displaying text within a message, separate from interactive + /// elements. TextDisplay(CreateTextDisplay<'a>), - Thumbnail(CreateThumbnail<'a>), + /// Represents a media gallery component (V2). + /// + /// A media gallery allows embedding images, videos, or other media assets within a message. MediaGallery(CreateMediaGallery<'a>), + /// Represents a file component (V2). + /// + /// This component is used for attaching and displaying files within a message. File(CreateFile<'a>), + /// Represents a separator component (V2). + /// + /// A separator is used to visually divide sections within a message for better readability. Separator(CreateSeparator), + /// Represents a container component (V2). + /// + /// A container is a flexible component that can hold multiple nested components. Container(CreateContainer<'a>), } +/// A builder to create a section component, supports up to a max of **3** components with an +/// accessory. #[derive(Clone, Debug, Serialize)] #[must_use] pub struct CreateSection<'a> { @@ -84,6 +121,9 @@ pub struct CreateSection<'a> { } impl<'a> CreateSection<'a> { + /// Creates a new builder with the specified components and accessory. + /// + /// Note: You may specify no more than **3** components or this will error on send. pub fn new( components: impl Into]>>, accessory: CreateSectionAccessory<'a>, @@ -94,8 +134,35 @@ impl<'a> CreateSection<'a> { accessory, } } + + /// Sets the components for the section. Replaces the current value as set in [`Self::new`]. + /// + /// **Note**: This will replace all existing components. Use [`Self::add_component()`] to add + /// additional components. + pub fn components( + mut self, + components: impl Into]>>, + ) -> Self { + self.components = components.into(); + self + } + + /// Adds an additional component to this section. + /// + /// **Note**: This will add additional components. Use [`Self::components()`] to replace them. + pub fn add_component(mut self, component: CreateSectionComponent<'a>) -> Self { + self.components.to_mut().push(component); + self + } + + /// Sets the accessory for this section. Replaces the current value as set in [`Self::new`]. + pub fn accessory(mut self, accessory: CreateSectionAccessory<'a>) -> Self { + self.accessory = accessory; + self + } } +/// An enum of all valid section components. #[derive(Clone, Debug, Serialize)] #[must_use] #[serde(untagged)] @@ -103,6 +170,7 @@ pub enum CreateSectionComponent<'a> { TextDisplay(CreateTextDisplay<'a>), } +/// A builder to create a text display component. #[derive(Clone, Debug, Serialize)] #[must_use] pub struct CreateTextDisplay<'a> { @@ -112,6 +180,9 @@ pub struct CreateTextDisplay<'a> { } impl<'a> CreateTextDisplay<'a> { + /// Creates a new text display component. + /// + /// Note: All components on a message shares the same **4000** character limit. pub fn new(content: impl Into>) -> Self { CreateTextDisplay { kind: StaticU8::<10>, @@ -119,12 +190,17 @@ impl<'a> CreateTextDisplay<'a> { } } + /// Sets the content of this text display component. Replaces the current value as set in + /// [`Self::new`]. + /// + /// Note: All components on a message shares the same **4000** character limit. pub fn content(mut self, content: impl Into>) -> Self { self.content = content.into(); self } } +/// An enum of all valid section accessories. #[derive(Clone, Debug, Serialize)] #[must_use] #[serde(untagged)] @@ -133,6 +209,7 @@ pub enum CreateSectionAccessory<'a> { Button(CreateButton<'a>), } +/// A builder to create a thumbnail for a section. #[derive(Clone, Debug, Serialize)] #[must_use] pub struct CreateThumbnail<'a> { @@ -146,6 +223,7 @@ pub struct CreateThumbnail<'a> { } impl<'a> CreateThumbnail<'a> { + /// Creates a new thumbnail with a media item. pub fn new(media: CreateUnfurledMediaItem<'a>) -> Self { CreateThumbnail { kind: StaticU8::<11>, @@ -155,22 +233,26 @@ impl<'a> CreateThumbnail<'a> { } } + /// Sets the media item. Replaces the current value as set in [`Self::new`]. pub fn media(mut self, media: CreateUnfurledMediaItem<'a>) -> Self { self.media = media; self } + /// Sets the description for this thumbnail. pub fn description(mut self, description: impl Into>) -> Self { self.description = Some(description.into()); self } + /// Sets if this thumbnail is spoilered. pub fn spoiler(mut self, spoiler: bool) -> Self { self.spoiler = Some(spoiler); self } } +/// A builder to create a media item. #[derive(Clone, Debug, Serialize, Default)] #[must_use] pub struct CreateUnfurledMediaItem<'a> { @@ -178,18 +260,23 @@ pub struct CreateUnfurledMediaItem<'a> { } impl<'a> CreateUnfurledMediaItem<'a> { + /// Creates a new media item. pub fn new(url: impl Into>) -> Self { CreateUnfurledMediaItem { url: url.into(), } } + /// Sets the url to this media item. Replaces the current value as set in [`Self::new`]. pub fn url(mut self, url: impl Into>) -> Self { self.url = url.into(); self } } +/// A builder to create a media gallery, a component that can contain multiple pieces of media. +/// +/// Note: May contain up to **10** items. #[derive(Clone, Debug, Serialize)] #[must_use] pub struct CreateMediaGallery<'a> { @@ -199,6 +286,7 @@ pub struct CreateMediaGallery<'a> { } impl<'a> CreateMediaGallery<'a> { + /// Creates a new media gallery with up to **10** items. pub fn new(items: impl Into]>>) -> Self { CreateMediaGallery { kind: StaticU8::<12>, @@ -206,12 +294,25 @@ impl<'a> CreateMediaGallery<'a> { } } + /// Sets the items of the gallery. Replaces the current value as set in [`Self::new`]. + /// + /// **Note**: This will replace all existing items. Use [`Self::add_item()`] to add additional + /// items pub fn items(mut self, items: impl Into]>>) -> Self { self.items = items.into(); self } + + /// Adds a singular item to the gallery. + /// + /// **Note**: This will add a singular item. Use [`Self::items()`] to replace all items. + pub fn add_item(mut self, item: CreateMediaGalleryItem<'a>) -> Self { + self.items.to_mut().push(item); + self + } } +/// Builder to create individual media gallery items. #[derive(Clone, Debug, Serialize, Default)] #[must_use] pub struct CreateMediaGalleryItem<'a> { @@ -223,6 +324,7 @@ pub struct CreateMediaGalleryItem<'a> { } impl<'a> CreateMediaGalleryItem<'a> { + /// Create a new media gallery item. pub fn new(media: CreateUnfurledMediaItem<'a>) -> Self { CreateMediaGalleryItem { media, @@ -231,22 +333,43 @@ impl<'a> CreateMediaGalleryItem<'a> { } } + /// Sets the internal media item. Replaces the current value as set in [`Self::new`]. pub fn media(mut self, media: CreateUnfurledMediaItem<'a>) -> Self { self.media = media; self } + /// Sets the description of this item in the gallery. pub fn description(mut self, description: impl Into>) -> Self { self.description = Some(description.into()); self } + /// Specifies if this piece of media should be spoilered. pub fn spoiler(mut self, spoiler: bool) -> Self { self.spoiler = Some(spoiler); self } } +/// A builder for specifying a file to be uploaded. +/// +/// This builder **only** supports the `attachment://filename.extension` format. +/// This means that you must first upload the file as an attachment in the message +/// and then reference it using this format. +/// +/// # Usage +/// +/// 1. Upload the file as an attachment. +/// 2. Use the `attachment://` scheme to reference the uploaded file. +/// +/// ## Example +/// +/// If you upload an attachment to the message that is called "example.txt", you set the url of the +/// item to "attachment://example.txt". +/// +/// For more details on naming and rules for attachments, +/// refer to the [Discord Documentation](https://discord.com/developers/docs/reference#uploading-files). #[derive(Clone, Debug, Serialize)] #[must_use] pub struct CreateFile<'a> { @@ -258,6 +381,8 @@ pub struct CreateFile<'a> { } impl<'a> CreateFile<'a> { + /// Create a new builder for the file component. Refer to this builders documentation for + /// limits. pub fn new(file: impl Into>) -> Self { CreateFile { kind: StaticU8::<13>, @@ -266,18 +391,21 @@ impl<'a> CreateFile<'a> { } } - // Only supports attachment:// format. + // Only supports `attachment://filename.extension` format, refer to this builders documentation + // for more details. Replaces the current value as set in [`Self::new`]. pub fn file(mut self, file: impl Into>) -> Self { self.file = file.into(); self } + /// Sets if this file should be spoilered or not. pub fn spoiler(mut self, spoiler: bool) -> Self { self.spoiler = Some(spoiler); self } } +/// A builder for creating a separator. #[derive(Clone, Debug, Serialize)] #[must_use] pub struct CreateSeparator { @@ -289,6 +417,7 @@ pub struct CreateSeparator { } impl CreateSeparator { + /// Creates a new separator, with or without a divider. pub fn new(divider: bool) -> Self { CreateSeparator { kind: StaticU8::<14>, @@ -297,12 +426,21 @@ impl CreateSeparator { } } + /// Sets if this separator should have a divider or not. Replaces the current value as set in + /// [`Self::new`]. + pub fn divider(mut self, divider: bool) -> Self { + self.divider = divider; + self + } + + /// Sets the spacing of this separator. pub fn spacing(mut self, spacing: Spacing) -> Self { self.spacing = Some(spacing); self } } +/// A builder to create a container, which acts similarly to embeds. #[derive(Clone, Debug, Serialize)] #[must_use] pub struct CreateContainer<'a> { @@ -316,6 +454,8 @@ pub struct CreateContainer<'a> { } impl<'a> CreateContainer<'a> { + /// Create a new container, with an array of components inside. This component may contain any + /// other component except another container! pub fn new(components: impl Into]>>) -> Self { CreateContainer { kind: StaticU8::<17>, @@ -325,20 +465,41 @@ impl<'a> CreateContainer<'a> { } } - pub fn accent_color(mut self, accent_color: Colour) -> Self { - self.accent_color = Some(accent_color); + // Set the colour of the left-hand side of the container. + pub fn accent_colour>(mut self, colour: C) -> Selff { + self.accent_color = Some(colour.into()); self } + /// Set the colour of the left-hand side of the container. + /// + /// This is an alias of [`Self::accent_colour`]. + pub fn accent_color>(self, colour: C) -> Self { + self.accent_colour(colour) + } + + /// Sets if this container is spoilered or not. pub fn spoiler(mut self, spoiler: bool) -> Self { self.spoiler = Some(spoiler); self } + /// Sets the components of this container. Replaces the current value as set in [`Self::new`]. + /// + /// **Note**: This will replace all existing components. Use [`Self::add_component()`] to add + /// additional components. pub fn components(mut self, components: impl Into]>>) -> Self { self.components = components.into(); self } + + /// Adds an additional component to this container. + /// + /// **Note**: This will add additional components. Use [`Self::components()`] to replace them. + pub fn add_component(mut self, component: CreateComponent<'a>) -> Self { + self.components.to_mut().push(component); + self + } } enum_number! { From 42e54a72b13e3becbd5e96bf7ec1be21d84a72c2 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 28 Feb 2025 15:41:13 +0000 Subject: [PATCH 12/21] thats silly! --- src/builder/create_components.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builder/create_components.rs b/src/builder/create_components.rs index 05ae217c2da..b3f94f1fd5f 100644 --- a/src/builder/create_components.rs +++ b/src/builder/create_components.rs @@ -466,7 +466,7 @@ impl<'a> CreateContainer<'a> { } // Set the colour of the left-hand side of the container. - pub fn accent_colour>(mut self, colour: C) -> Selff { + pub fn accent_colour>(mut self, colour: C) -> Self { self.accent_color = Some(colour.into()); self } From 5753d54ca657f3ba24ecf043d179dd800762dbe8 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 28 Feb 2025 16:07:32 +0000 Subject: [PATCH 13/21] unstable --- src/builder/create_components.rs | 23 +++++++++ src/builder/create_interaction_response.rs | 38 +++++++++++--- .../create_interaction_response_followup.rs | 25 +++++++--- src/builder/create_message.rs | 26 +++++++--- src/builder/edit_interaction_response.rs | 13 ++++- src/builder/edit_message.rs | 25 +++++++--- src/builder/edit_webhook_message.rs | 30 +++++++++--- src/builder/execute_webhook.rs | 30 +++++++++--- src/builder/mod.rs | 15 ++++++ src/collector/quick_modal.rs | 24 +++++---- src/model/application/component.rs | 49 ++++++++++--------- src/model/channel/message.rs | 22 ++++++--- 12 files changed, 234 insertions(+), 86 deletions(-) diff --git a/src/builder/create_components.rs b/src/builder/create_components.rs index b3f94f1fd5f..6c18227dc6d 100644 --- a/src/builder/create_components.rs +++ b/src/builder/create_components.rs @@ -5,8 +5,10 @@ use serde::Serialize; use crate::model::prelude::*; #[derive(Clone, Debug)] +#[cfg(feature = "unstable")] struct StaticU8; +#[cfg(feature = "unstable")] impl Serialize for StaticU8 { fn serialize(&self, ser: S) -> Result { ser.serialize_u8(VAL) @@ -75,6 +77,7 @@ impl serde::Serialize for CreateActionRow<'_> { #[derive(Clone, Debug, Serialize)] #[must_use] #[serde(untagged)] +#[cfg(feature = "unstable")] pub enum CreateComponent<'a> { /// Represents an action row component (V1). /// @@ -112,6 +115,7 @@ pub enum CreateComponent<'a> { /// accessory. #[derive(Clone, Debug, Serialize)] #[must_use] +#[cfg(feature = "unstable")] pub struct CreateSection<'a> { #[serde(rename = "type")] kind: StaticU8<9>, @@ -120,6 +124,7 @@ pub struct CreateSection<'a> { accessory: CreateSectionAccessory<'a>, } +#[cfg(feature = "unstable")] impl<'a> CreateSection<'a> { /// Creates a new builder with the specified components and accessory. /// @@ -166,6 +171,7 @@ impl<'a> CreateSection<'a> { #[derive(Clone, Debug, Serialize)] #[must_use] #[serde(untagged)] +#[cfg(feature = "unstable")] pub enum CreateSectionComponent<'a> { TextDisplay(CreateTextDisplay<'a>), } @@ -173,12 +179,14 @@ pub enum CreateSectionComponent<'a> { /// A builder to create a text display component. #[derive(Clone, Debug, Serialize)] #[must_use] +#[cfg(feature = "unstable")] pub struct CreateTextDisplay<'a> { #[serde(rename = "type")] kind: StaticU8<10>, content: Cow<'a, str>, } +#[cfg(feature = "unstable")] impl<'a> CreateTextDisplay<'a> { /// Creates a new text display component. /// @@ -204,6 +212,7 @@ impl<'a> CreateTextDisplay<'a> { #[derive(Clone, Debug, Serialize)] #[must_use] #[serde(untagged)] +#[cfg(feature = "unstable")] pub enum CreateSectionAccessory<'a> { Thumbnail(CreateThumbnail<'a>), Button(CreateButton<'a>), @@ -212,6 +221,7 @@ pub enum CreateSectionAccessory<'a> { /// A builder to create a thumbnail for a section. #[derive(Clone, Debug, Serialize)] #[must_use] +#[cfg(feature = "unstable")] pub struct CreateThumbnail<'a> { #[serde(rename = "type")] kind: StaticU8<11>, @@ -222,6 +232,7 @@ pub struct CreateThumbnail<'a> { spoiler: Option, } +#[cfg(feature = "unstable")] impl<'a> CreateThumbnail<'a> { /// Creates a new thumbnail with a media item. pub fn new(media: CreateUnfurledMediaItem<'a>) -> Self { @@ -255,10 +266,12 @@ impl<'a> CreateThumbnail<'a> { /// A builder to create a media item. #[derive(Clone, Debug, Serialize, Default)] #[must_use] +#[cfg(feature = "unstable")] pub struct CreateUnfurledMediaItem<'a> { url: Cow<'a, str>, } +#[cfg(feature = "unstable")] impl<'a> CreateUnfurledMediaItem<'a> { /// Creates a new media item. pub fn new(url: impl Into>) -> Self { @@ -279,12 +292,14 @@ impl<'a> CreateUnfurledMediaItem<'a> { /// Note: May contain up to **10** items. #[derive(Clone, Debug, Serialize)] #[must_use] +#[cfg(feature = "unstable")] pub struct CreateMediaGallery<'a> { #[serde(rename = "type")] kind: StaticU8<12>, items: Cow<'a, [CreateMediaGalleryItem<'a>]>, } +#[cfg(feature = "unstable")] impl<'a> CreateMediaGallery<'a> { /// Creates a new media gallery with up to **10** items. pub fn new(items: impl Into]>>) -> Self { @@ -315,6 +330,7 @@ impl<'a> CreateMediaGallery<'a> { /// Builder to create individual media gallery items. #[derive(Clone, Debug, Serialize, Default)] #[must_use] +#[cfg(feature = "unstable")] pub struct CreateMediaGalleryItem<'a> { media: CreateUnfurledMediaItem<'a>, #[serde(skip_serializing_if = "Option::is_none")] @@ -323,6 +339,7 @@ pub struct CreateMediaGalleryItem<'a> { spoiler: Option, } +#[cfg(feature = "unstable")] impl<'a> CreateMediaGalleryItem<'a> { /// Create a new media gallery item. pub fn new(media: CreateUnfurledMediaItem<'a>) -> Self { @@ -372,6 +389,7 @@ impl<'a> CreateMediaGalleryItem<'a> { /// refer to the [Discord Documentation](https://discord.com/developers/docs/reference#uploading-files). #[derive(Clone, Debug, Serialize)] #[must_use] +#[cfg(feature = "unstable")] pub struct CreateFile<'a> { #[serde(rename = "type")] kind: StaticU8<13>, @@ -380,6 +398,7 @@ pub struct CreateFile<'a> { spoiler: Option, } +#[cfg(feature = "unstable")] impl<'a> CreateFile<'a> { /// Create a new builder for the file component. Refer to this builders documentation for /// limits. @@ -408,6 +427,7 @@ impl<'a> CreateFile<'a> { /// A builder for creating a separator. #[derive(Clone, Debug, Serialize)] #[must_use] +#[cfg(feature = "unstable")] pub struct CreateSeparator { #[serde(rename = "type")] kind: StaticU8<14>, @@ -416,6 +436,7 @@ pub struct CreateSeparator { spacing: Option, } +#[cfg(feature = "unstable")] impl CreateSeparator { /// Creates a new separator, with or without a divider. pub fn new(divider: bool) -> Self { @@ -443,6 +464,7 @@ impl CreateSeparator { /// A builder to create a container, which acts similarly to embeds. #[derive(Clone, Debug, Serialize)] #[must_use] +#[cfg(feature = "unstable")] pub struct CreateContainer<'a> { #[serde(rename = "type")] kind: StaticU8<17>, @@ -453,6 +475,7 @@ pub struct CreateContainer<'a> { components: Cow<'a, [CreateComponent<'a>]>, } +#[cfg(feature = "unstable")] impl<'a> CreateContainer<'a> { /// Create a new container, with an array of components inside. This component may contain any /// other component except another container! diff --git a/src/builder/create_interaction_response.rs b/src/builder/create_interaction_response.rs index fb1059f657c..5d15ed71fa2 100644 --- a/src/builder/create_interaction_response.rs +++ b/src/builder/create_interaction_response.rs @@ -1,15 +1,12 @@ use std::borrow::Cow; use std::collections::HashMap; +#[cfg(not(feature = "unstable"))] +use super::CreateActionRow; +#[cfg(feature = "unstable")] +use super::CreateComponent; use super::create_poll::Ready; -use super::{ - CreateAllowedMentions, - CreateAttachment, - CreateComponent, - CreateEmbed, - CreatePoll, - EditAttachments, -}; +use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, CreatePoll, EditAttachments}; #[cfg(feature = "http")] use crate::http::Http; use crate::internal::prelude::*; @@ -160,8 +157,12 @@ pub struct CreateInteractionResponseMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] flags: Option, #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(feature = "unstable")] components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(not(feature = "unstable"))] + components: Option]>>, + #[serde(skip_serializing_if = "Option::is_none")] poll: Option>, attachments: EditAttachments<'a>, } @@ -273,11 +274,19 @@ impl<'a> CreateInteractionResponseMessage<'a> { } /// Sets the components of this message. + #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } + /// Sets the components of this message. + #[cfg(not(feature = "unstable"))] + pub fn components(mut self, components: impl Into]>>) -> Self { + self.components = Some(components.into()); + self + } + /// Adds a poll to the message. Only one poll can be added per message. /// /// See [`CreatePoll`] for more information on creating and configuring a poll. @@ -423,6 +432,9 @@ impl<'a> CreateAutocompleteResponse<'a> { #[derive(Clone, Debug, Default, Serialize)] #[must_use] pub struct CreateModal<'a> { + #[cfg(not(feature = "unstable"))] + components: Cow<'a, [CreateActionRow<'a>]>, + #[cfg(feature = "unstable")] components: Cow<'a, [CreateComponent<'a>]>, custom_id: Cow<'a, str>, title: Cow<'a, str>, @@ -441,8 +453,18 @@ impl<'a> CreateModal<'a> { /// Sets the components of this message. /// /// Overwrites existing components. + #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = components.into(); self } + + /// Sets the components of this message. + /// + /// Overwrites existing components. + #[cfg(not(feature = "unstable"))] + pub fn components(mut self, components: impl Into]>>) -> Self { + self.components = components.into(); + self + } } diff --git a/src/builder/create_interaction_response_followup.rs b/src/builder/create_interaction_response_followup.rs index dae8059eab1..abc55ea6f02 100644 --- a/src/builder/create_interaction_response_followup.rs +++ b/src/builder/create_interaction_response_followup.rs @@ -1,14 +1,11 @@ use std::borrow::Cow; +#[cfg(not(feature = "unstable"))] +use super::CreateActionRow; +#[cfg(feature = "unstable")] +use super::CreateComponent; use super::create_poll::Ready; -use super::{ - CreateAllowedMentions, - CreateAttachment, - CreateComponent, - CreateEmbed, - CreatePoll, - EditAttachments, -}; +use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, CreatePoll, EditAttachments}; #[cfg(feature = "http")] use crate::http::Http; #[cfg(feature = "http")] @@ -29,8 +26,12 @@ pub struct CreateInteractionResponseFollowup<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(feature = "unstable")] components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(not(feature = "unstable"))] + components: Option]>>, + #[serde(skip_serializing_if = "Option::is_none")] flags: Option, #[serde(skip_serializing_if = "Option::is_none")] poll: Option>, @@ -153,10 +154,18 @@ impl<'a> CreateInteractionResponseFollowup<'a> { } /// Sets the components of this message. + #[cfg(not(feature = "unstable"))] + pub fn components(mut self, components: impl Into]>>) -> Self { + self.components = Some(components.into()); + self + } + + #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } + super::button_and_select_menu_convenience_methods!(self.components); /// Creates or edits a followup response to the response sent. If a [`MessageId`] is provided, diff --git a/src/builder/create_message.rs b/src/builder/create_message.rs index c01e3f0f9ed..e221cf1926e 100644 --- a/src/builder/create_message.rs +++ b/src/builder/create_message.rs @@ -1,14 +1,11 @@ use std::borrow::Cow; +#[cfg(not(feature = "unstable"))] +use super::CreateActionRow; +#[cfg(feature = "unstable")] +use super::CreateComponent; use super::create_poll::Ready; -use super::{ - CreateAllowedMentions, - CreateAttachment, - CreateComponent, - CreateEmbed, - CreatePoll, - EditAttachments, -}; +use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, CreatePoll, EditAttachments}; #[cfg(feature = "http")] use crate::http::Http; #[cfg(feature = "http")] @@ -63,7 +60,11 @@ pub struct CreateMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] message_reference: Option, #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(feature = "unstable")] components: Option]>>, + #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(not(feature = "unstable"))] + components: Option]>>, sticker_ids: Cow<'a, [StickerId]>, #[serde(skip_serializing_if = "Option::is_none")] flags: Option, @@ -184,10 +185,19 @@ impl<'a> CreateMessage<'a> { } /// Sets the components of this message. + #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } + + /// Sets the components of this message. + #[cfg(not(feature = "unstable"))] + pub fn components(mut self, components: impl Into]>>) -> Self { + self.components = Some(components.into()); + self + } + super::button_and_select_menu_convenience_methods!(self.components); /// Sets the flags for the message. diff --git a/src/builder/edit_interaction_response.rs b/src/builder/edit_interaction_response.rs index 4b344e31a1a..a128d9e19f6 100644 --- a/src/builder/edit_interaction_response.rs +++ b/src/builder/edit_interaction_response.rs @@ -1,9 +1,12 @@ use std::borrow::Cow; +#[cfg(not(feature = "unstable"))] +use super::CreateActionRow; +#[cfg(feature = "unstable")] +use super::CreateComponent; use super::{ CreateAllowedMentions, CreateAttachment, - CreateComponent, CreateEmbed, EditAttachments, EditWebhookMessage, @@ -70,9 +73,17 @@ impl<'a> EditInteractionResponse<'a> { } /// Sets the components of this message. + #[cfg(feature = "unstable")] pub fn components(self, components: impl Into]>>) -> Self { Self(self.0.components(components)) } + + /// Sets the components of this message. + #[cfg(not(feature = "unstable"))] + pub fn components(self, components: impl Into]>>) -> Self { + Self(self.0.components(components)) + } + super::button_and_select_menu_convenience_methods!(self.0.components); /// Sets attachments, see [`EditAttachments`] for more details. diff --git a/src/builder/edit_message.rs b/src/builder/edit_message.rs index c20c9f441d9..ebcac16a6a9 100644 --- a/src/builder/edit_message.rs +++ b/src/builder/edit_message.rs @@ -1,12 +1,10 @@ use std::borrow::Cow; -use super::{ - CreateAllowedMentions, - CreateAttachment, - CreateComponent, - CreateEmbed, - EditAttachments, -}; +#[cfg(not(feature = "unstable"))] +use super::CreateActionRow; +#[cfg(feature = "unstable")] +use super::CreateComponent; +use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, EditAttachments}; #[cfg(feature = "http")] use crate::http::CacheHttp; #[cfg(feature = "http")] @@ -45,8 +43,12 @@ pub struct EditMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(feature = "unstable")] components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(not(feature = "unstable"))] + components: Option]>>, + #[serde(skip_serializing_if = "Option::is_none")] attachments: Option>, } @@ -136,10 +138,19 @@ impl<'a> EditMessage<'a> { } /// Sets the components of this message. + #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } + + /// Sets the components of this message. + #[cfg(not(feature = "unstable"))] + pub fn components(mut self, components: impl Into]>>) -> Self { + self.components = Some(components.into()); + self + } + super::button_and_select_menu_convenience_methods!(self.components); /// Sets the flags for the message. diff --git a/src/builder/edit_webhook_message.rs b/src/builder/edit_webhook_message.rs index a1a985fc36c..9522de2f7ba 100644 --- a/src/builder/edit_webhook_message.rs +++ b/src/builder/edit_webhook_message.rs @@ -1,12 +1,10 @@ use std::borrow::Cow; -use super::{ - CreateAllowedMentions, - CreateAttachment, - CreateComponent, - CreateEmbed, - EditAttachments, -}; +#[cfg(not(feature = "unstable"))] +use super::CreateActionRow; +#[cfg(feature = "unstable")] +use super::CreateComponent; +use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, EditAttachments}; #[cfg(feature = "http")] use crate::http::Http; #[cfg(feature = "http")] @@ -26,8 +24,12 @@ pub struct EditWebhookMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(feature = "unstable")] pub(crate) components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(not(feature = "unstable"))] + pub(crate) components: Option]>>, + #[serde(skip_serializing_if = "Option::is_none")] pub(crate) attachments: Option>, #[serde(skip)] @@ -107,10 +109,24 @@ impl<'a> EditWebhookMessage<'a> { /// /// [`WebhookType::Application`]: crate::model::webhook::WebhookType /// [`WebhookType::Incoming`]: crate::model::webhook::WebhookType + #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } + + /// Sets the components for this message. Requires an application-owned webhook, meaning either + /// the webhook's `kind` field is set to [`WebhookType::Application`], or it was created by an + /// application (and has kind [`WebhookType::Incoming`]). + /// + /// [`WebhookType::Application`]: crate::model::webhook::WebhookType + /// [`WebhookType::Incoming`]: crate::model::webhook::WebhookType + #[cfg(not(feature = "unstable"))] + pub fn components(mut self, components: impl Into]>>) -> Self { + self.components = Some(components.into()); + self + } + super::button_and_select_menu_convenience_methods!(self.components); /// Sets attachments, see [`EditAttachments`] for more details. diff --git a/src/builder/execute_webhook.rs b/src/builder/execute_webhook.rs index 2a7c084d155..6e5e3ab95b3 100644 --- a/src/builder/execute_webhook.rs +++ b/src/builder/execute_webhook.rs @@ -1,12 +1,10 @@ use std::borrow::Cow; -use super::{ - CreateAllowedMentions, - CreateAttachment, - CreateComponent, - CreateEmbed, - EditAttachments, -}; +#[cfg(not(feature = "unstable"))] +use super::CreateActionRow; +#[cfg(feature = "unstable")] +use super::CreateComponent; +use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, EditAttachments}; #[cfg(feature = "http")] use crate::http::Http; #[cfg(feature = "http")] @@ -68,8 +66,12 @@ pub struct ExecuteWebhook<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(feature = "unstable")] components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] + #[cfg(not(feature = "unstable"))] + components: Option]>>, + #[serde(skip_serializing_if = "Option::is_none")] flags: Option, #[serde(skip_serializing_if = "Option::is_none")] thread_name: Option>, @@ -216,10 +218,24 @@ impl<'a> ExecuteWebhook<'a> { /// /// [`WebhookType::Application`]: crate::model::webhook::WebhookType /// [`WebhookType::Incoming`]: crate::model::webhook::WebhookType + #[cfg(not(feature = "unstable"))] + pub fn components(mut self, components: impl Into]>>) -> Self { + self.components = Some(components.into()); + self + } + + /// Sets the components for this message. Requires an application-owned webhook, meaning either + /// the webhook's `kind` field is set to [`WebhookType::Application`], or it was created by an + /// application (and has kind [`WebhookType::Incoming`]). + /// + /// [`WebhookType::Application`]: crate::model::webhook::WebhookType + /// [`WebhookType::Incoming`]: crate::model::webhook::WebhookType + #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } + super::button_and_select_menu_convenience_methods!(self.components); /// Set an embed for the message. diff --git a/src/builder/mod.rs b/src/builder/mod.rs index f08ab8faad5..a6b61e0e4a8 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -130,11 +130,18 @@ macro_rules! button_and_select_menu_convenience_methods { pub fn button(mut $self, button: super::CreateButton<'a>) -> Self { let rows = $self$(.$components_path)+.get_or_insert_with(Cow::default).to_mut(); let row_with_space_left = rows.last_mut().and_then(|row| match row { + #[cfg(not(feature = "unstable"))] + super::CreateActionRow::Buttons(buttons) if buttons.len() < 5 => Some(buttons.to_mut()), + + #[cfg(feature = "unstable")] super::CreateComponent::ActionRow(super::CreateActionRow::Buttons(buttons)) if buttons.len() < 5 => Some(buttons.to_mut()), _ => None, }); match row_with_space_left { Some(row) => row.push(button), + #[cfg(not(feature = "unstable"))] + None => rows.push(super::CreateActionRow::buttons(vec![button])), + #[cfg(feature = "unstable")] None => rows.push(super::CreateComponent::ActionRow(super::CreateActionRow::buttons(vec![button]))), } $self @@ -144,6 +151,14 @@ macro_rules! button_and_select_menu_convenience_methods { /// /// Convenience method that wraps [`Self::components`]. pub fn select_menu(mut $self, select_menu: super::CreateSelectMenu<'a>) -> Self { + #[cfg(not(feature = "unstable"))] + $self$(.$components_path)+ + .get_or_insert_with(Cow::default) + .to_mut() + .push(super::CreateActionRow::SelectMenu(select_menu)); + + + #[cfg(feature = "unstable")] $self$(.$components_path)+ .get_or_insert_with(Cow::default) .to_mut() diff --git a/src/collector/quick_modal.rs b/src/collector/quick_modal.rs index 7b84013208e..811aeceb97f 100644 --- a/src/collector/quick_modal.rs +++ b/src/collector/quick_modal.rs @@ -1,12 +1,8 @@ use std::borrow::Cow; -use crate::builder::{ - CreateActionRow, - CreateComponent, - CreateInputText, - CreateInteractionResponse, - CreateModal, -}; +#[cfg(feature = "unstable")] +use crate::builder::CreateComponent; +use crate::builder::{CreateActionRow, CreateInputText, CreateInteractionResponse, CreateModal}; use crate::collector::ModalInteractionCollector; use crate::gateway::client::Context; use crate::internal::prelude::*; @@ -97,9 +93,17 @@ impl<'a> CreateQuickModal<'a> { .into_iter() .enumerate() .map(|(i, input_text)| { - CreateComponent::ActionRow(CreateActionRow::InputText( - input_text.custom_id(i.to_string()), - )) + #[cfg(not(feature = "unstable"))] + { + CreateActionRow::InputText(input_text.custom_id(i.to_string())) + } + + #[cfg(feature = "unstable")] + { + CreateComponent::ActionRow(CreateActionRow::InputText( + input_text.custom_id(i.to_string()), + )) + } }) .collect::>(), ), diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 043ef71deef..74c4681999a 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "unstable")] use nonmax::NonMaxU32; use serde::de::Error as DeError; use serde::ser::{Serialize, Serializer}; @@ -20,12 +21,19 @@ enum_number! { RoleSelect = 6, MentionableSelect = 7, ChannelSelect = 8, + #[cfg(feature = "unstable")] Section = 9, + #[cfg(feature = "unstable")] TextDisplay = 10, + #[cfg(feature = "unstable")] Thumbnail = 11, + #[cfg(feature = "unstable")] MediaGallery = 12, + #[cfg(feature = "unstable")] File = 13, + #[cfg(feature = "unstable")] Separator = 14, + #[cfg(feature = "unstable")] Container = 17, _ => Unknown(u8), } @@ -42,6 +50,7 @@ enum_number! { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Serialize)] #[non_exhaustive] +#[cfg(feature = "unstable")] pub enum Component { ActionRow(ActionRow), Button(Button), @@ -60,6 +69,7 @@ pub enum Component { // TODO: add something like this to every variant. // The component type, it will always be [`ComponentType::Thing`]. +#[cfg(feature = "unstable")] impl<'de> Deserialize<'de> for Component { fn deserialize(deserializer: D) -> std::result::Result where @@ -110,6 +120,7 @@ impl<'de> Deserialize<'de> for Component { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] +#[cfg(feature = "unstable")] pub struct Section { /// Always [`ComponentType::Section`] #[serde(rename = "type")] @@ -133,6 +144,7 @@ pub struct Section { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] +#[cfg(feature = "unstable")] pub struct Thumbnail { /// Always [`ComponentType::Thumbnail`] #[serde(rename = "type")] @@ -140,9 +152,9 @@ pub struct Thumbnail { /// The internal media item this contains. pub media: UnfurledMediaItem, /// The description of the thumbnail. - description: Option>, + pub description: Option>, /// Whether or not this component is spoilered. - spoiler: Option, + pub spoiler: Option, } /// An abstraction over a resolved and unresolved unfurled media item. @@ -150,6 +162,7 @@ pub struct Thumbnail { #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] #[serde(untagged)] +#[cfg(feature = "unstable")] pub enum MediaItem { Resolved(ResolvedUnfurledMediaItem), Unresolved(UnfurledMediaItem), @@ -161,6 +174,7 @@ pub enum MediaItem { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] +#[cfg(feature = "unstable")] pub struct UnfurledMediaItem { /// The url of this item. pub url: FixedString, @@ -172,6 +186,7 @@ pub struct UnfurledMediaItem { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] +#[cfg(feature = "unstable")] pub struct ResolvedUnfurledMediaItem { /// The url of this item. pub url: FixedString, @@ -187,6 +202,7 @@ pub struct ResolvedUnfurledMediaItem { pub loading_state: UnfurledMediaItemLoadingState, } +#[cfg(feature = "unstable")] enum_number! { /// The loading state of the media item. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] @@ -201,34 +217,13 @@ enum_number! { } } -/// A list of valid components for an accessory of a section. -/// -/// See [`Section`] for how this works. -/// -/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) -#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Deserialize, Serialize)] -#[non_exhaustive] -pub enum SectionAccessory { - Thumbnail(Thumbnail), - Button(Button), - Unknown, -} - -#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Deserialize, Serialize)] -#[non_exhaustive] -pub enum SectionComponent { - TextDisplay(TextDisplay), - Unknown, -} - /// A text display component. /// /// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] +#[cfg(feature = "unstable")] pub struct TextDisplay { /// The content of this text display component. pub content: FixedString, @@ -240,6 +235,7 @@ pub struct TextDisplay { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] +#[cfg(feature = "unstable")] pub struct MediaGallery { /// Always [`ComponentType::MediaGallery`] #[serde(rename = "type")] @@ -256,6 +252,7 @@ pub struct MediaGallery { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] +#[cfg(feature = "unstable")] pub struct MediaGalleryItem { /// The internal media piece that this item contains. pub media: UnfurledMediaItem, @@ -271,6 +268,7 @@ pub struct MediaGalleryItem { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] +#[cfg(feature = "unstable")] pub struct Separator { /// Always [`ComponentType::Separator`] #[serde(rename = "type")] @@ -281,6 +279,7 @@ pub struct Separator { pub spacing: Option, } +#[cfg(feature = "unstable")] enum_number! { /// The size of a separator component. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] @@ -299,6 +298,7 @@ enum_number! { #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] +#[cfg(feature = "unstable")] pub struct FileComponent { /// Always [`ComponentType::File`] #[serde(rename = "type")] @@ -315,6 +315,7 @@ pub struct FileComponent { #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] +#[cfg(feature = "unstable")] pub struct Container { /// Always [`ComponentType::Container`] #[serde(rename = "type")] diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 6a2d979ce38..4234bc68992 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -109,6 +109,12 @@ pub struct Message { /// The thread that was started from this message, includes thread member object. pub thread: Option>, /// The components of this message + #[serde(default)] + #[cfg(feature = "unstable")] + pub components: FixedArray, + + /// The components of this message + #[cfg(not(feature = "unstable"))] #[serde(default, deserialize_with = "deserialize_components")] pub components: FixedArray, /// Array of message sticker item objects. @@ -873,21 +879,25 @@ pub struct ChannelMention { #[derive(Clone, Debug, Serialize, Deserialize)] #[non_exhaustive] pub struct MessageSnapshot { - pub content: String, + pub content: FixedString, pub timestamp: Timestamp, pub edited_timestamp: Option, - pub mentions: Vec, + pub mentions: FixedArray, #[serde(default)] - pub mention_roles: Vec, - pub attachments: Vec, - pub embeds: Vec, + pub mention_roles: FixedArray, + pub attachments: FixedArray, + pub embeds: FixedArray, #[serde(rename = "type")] pub kind: MessageType, pub flags: Option, + #[serde(default)] + #[cfg(feature = "unstable")] + pub components: FixedArray, #[serde(default, deserialize_with = "deserialize_components")] + #[cfg(not(feature = "unstable"))] pub components: FixedArray, #[serde(default)] - pub sticker_items: Vec, + pub sticker_items: FixedArray, } /// Custom deserialization function to handle the nested "message" field From 54da46496c7c3aaedf38eda22f1c681cf294bf2b Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Sun, 9 Mar 2025 00:10:49 +0000 Subject: [PATCH 14/21] make ResolvedUnfurledMediaItem fields optional --- src/model/application/component.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 74c4681999a..9460ee1d6b1 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -191,15 +191,15 @@ pub struct ResolvedUnfurledMediaItem { /// The url of this item. pub url: FixedString, /// The proxied discord url. - pub proxy_url: FixedString, + pub proxy_url: Option>, /// The width of the media item. - pub width: NonMaxU32, + pub width: Option, /// The height of the media item. - pub height: NonMaxU32, + pub height: Option, /// The content type of the media item. - pub content_type: FixedString, + pub content_type: Option, /// The loading state of the item, declaring if it has fully loaded yet. - pub loading_state: UnfurledMediaItemLoadingState, + pub loading_state: Option, } #[cfg(feature = "unstable")] From 3dc164427fa021731e359d8e3396c13735216c8b Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 18 Apr 2025 21:12:16 +0100 Subject: [PATCH 15/21] Remove InteractionResponseFlags This is just a duplicate of MessageFlags and having this duplicate impl for the same flags just adds extra complexity for poise, we don't usually wrap like this in other places like serenity, so I replaced it with MessageFlags --- examples/testing/src/model_type_sizes.rs | 1 - src/builder/create_interaction_response.rs | 10 +++++----- src/model/application/interaction.rs | 17 ----------------- 3 files changed, 5 insertions(+), 23 deletions(-) diff --git a/examples/testing/src/model_type_sizes.rs b/examples/testing/src/model_type_sizes.rs index eb23e783ab4..ef5c77c5a1f 100644 --- a/examples/testing/src/model_type_sizes.rs +++ b/examples/testing/src/model_type_sizes.rs @@ -104,7 +104,6 @@ pub fn print_ranking() { ("IntegrationDeleteEvent", std::mem::size_of::()), ("IntegrationId", std::mem::size_of::()), ("IntegrationUpdateEvent", std::mem::size_of::()), - ("InteractionResponseFlags", std::mem::size_of::()), ("InteractionCreateEvent", std::mem::size_of::()), ("InteractionId", std::mem::size_of::()), ("Invite", std::mem::size_of::()), diff --git a/src/builder/create_interaction_response.rs b/src/builder/create_interaction_response.rs index 5d15ed71fa2..552978ec6d2 100644 --- a/src/builder/create_interaction_response.rs +++ b/src/builder/create_interaction_response.rs @@ -155,7 +155,7 @@ pub struct CreateInteractionResponseMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] - flags: Option, + flags: Option, #[serde(skip_serializing_if = "Option::is_none")] #[cfg(feature = "unstable")] components: Option]>>, @@ -254,19 +254,19 @@ impl<'a> CreateInteractionResponseMessage<'a> { } /// Sets the flags for the message. - pub fn flags(mut self, flags: InteractionResponseFlags) -> Self { + pub fn flags(mut self, flags: MessageFlags) -> Self { self.flags = Some(flags); self } /// Adds or removes the ephemeral flag. pub fn ephemeral(mut self, ephemeral: bool) -> Self { - let mut flags = self.flags.unwrap_or_else(InteractionResponseFlags::empty); + let mut flags = self.flags.unwrap_or_else(MessageFlags::empty); if ephemeral { - flags |= InteractionResponseFlags::EPHEMERAL; + flags |= MessageFlags::EPHEMERAL; } else { - flags &= !InteractionResponseFlags::EPHEMERAL; + flags &= !MessageFlags::EPHEMERAL; } self.flags = Some(flags); diff --git a/src/model/application/interaction.rs b/src/model/application/interaction.rs index 44826af5bb9..4070501fa36 100644 --- a/src/model/application/interaction.rs +++ b/src/model/application/interaction.rs @@ -299,23 +299,6 @@ enum_number! { } } -bitflags! { - /// The flags for an interaction response message. - /// - /// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object-message-flags) - /// ([only some are valid in this context](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-messages)) - #[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)] - pub struct InteractionResponseFlags: u64 { - /// Do not include any embeds when serializing this message. - const SUPPRESS_EMBEDS = 1 << 2; - /// Interaction message will only be visible to sender and will - /// be quickly deleted. - const EPHEMERAL = 1 << 6; - /// Does not trigger push notifications or desktop notifications. - const SUPPRESS_NOTIFICATIONS = 1 << 12; - } -} - /// A cleaned up enum for determining the authorizing owner for an [`Interaction`]. /// /// [Discord Docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-authorizing-integration-owners-object) From 338a24dbd1e7cc3c4d8cbb55059c6824e027fc44 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 18 Apr 2025 22:04:48 +0100 Subject: [PATCH 16/21] fix deserialization for Thumbnails, remove variant for media item --- src/model/application/component.rs | 36 +++++------------------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 9460ee1d6b1..1281d5a74be 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -62,7 +62,7 @@ pub enum Component { Separator(Separator), File(FileComponent), Container(Container), - Unknown, + Unknown(u8), // always update the macro below. } @@ -75,6 +75,8 @@ impl<'de> Deserialize<'de> for Component { where D: Deserializer<'de>, { + use serde_json::value::RawValue; + #[derive(Deserialize)] struct ComponentRaw { #[serde(rename = "type")] @@ -104,11 +106,8 @@ impl<'de> Deserialize<'de> for Component { ComponentType::Separator => Deserialize::deserialize(value).map(Component::Separator), ComponentType::File => Deserialize::deserialize(value).map(Component::File), ComponentType::Container => Deserialize::deserialize(value).map(Component::Container), - // TODO: maybe just not include it so the deserialization doesn't explode. - // With all new component types right now, the ENTIRE message won't deserialize. - // I need to do other stuff in other places too so that its as resilent as possible, it - // should not die when discord adds new stuff. - _ => Err(DeError::custom("Unknown component type")), + ComponentType::Thumbnail => Deserialize::deserialize(value).map(Component::Thumbnail), + ComponentType(i) => Ok(Component::Unknown(i)), } .map_err(DeError::custom) } @@ -157,18 +156,7 @@ pub struct Thumbnail { pub spoiler: Option, } -/// An abstraction over a resolved and unresolved unfurled media item. -#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Deserialize, Serialize)] -#[non_exhaustive] -#[serde(untagged)] -#[cfg(feature = "unstable")] -pub enum MediaItem { - Resolved(ResolvedUnfurledMediaItem), - Unresolved(UnfurledMediaItem), -} - -/// An unfurled media item, stores the url to the item. +/// An unfurled media item. /// /// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] @@ -178,18 +166,6 @@ pub enum MediaItem { pub struct UnfurledMediaItem { /// The url of this item. pub url: FixedString, -} - -/// A resolved unfurled media item, with extra metadata added by Discord. -/// -/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) -#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] -#[derive(Clone, Debug, Deserialize, Serialize)] -#[non_exhaustive] -#[cfg(feature = "unstable")] -pub struct ResolvedUnfurledMediaItem { - /// The url of this item. - pub url: FixedString, /// The proxied discord url. pub proxy_url: Option>, /// The width of the media item. From e39ee522c38abc24c01b7e6570e6a51393e662fc Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 18 Apr 2025 22:09:48 +0100 Subject: [PATCH 17/21] does CI just not pick this up? --- src/model/channel/message.rs | 4 +++- src/model/utils.rs | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 4234bc68992..d0bd1e18d8d 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -14,7 +14,9 @@ use crate::constants; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http}; use crate::model::prelude::*; -use crate::model::utils::{StrOrInt, deserialize_components, discord_colours}; +#[cfg(not(feature = "unstable"))] +use crate::model::utils::deserialize_components; +use crate::model::utils::{StrOrInt, discord_colours}; /// A representation of a message over a guild's text channel, a group, or a private channel. /// diff --git a/src/model/utils.rs b/src/model/utils.rs index 79a5c638de6..944e35df527 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -242,6 +242,7 @@ where // Custom deserialize function to deserialize components safely without knocking the whole message // out when new components are found but not supported. +#[cfg(not(feature = "unstable"))] pub fn deserialize_components<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, From 73455b6832ada2aaea8a0bf13c1dacc7eee08cda Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Fri, 18 Apr 2025 22:11:22 +0100 Subject: [PATCH 18/21] or this --- src/model/mod.rs | 1 + src/model/utils.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/model/mod.rs b/src/model/mod.rs index 2fae5389692..20745356430 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -62,6 +62,7 @@ pub use self::timestamp::Timestamp; /// use serenity::model::prelude::*; /// ``` pub mod prelude { + #[cfg(not(feature = "unstable"))] pub(crate) use serde::de::Visitor; pub(crate) use serde::{Deserialize, Deserializer}; diff --git a/src/model/utils.rs b/src/model/utils.rs index 944e35df527..533d569773e 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -3,6 +3,7 @@ use std::fmt; use arrayvec::ArrayVec; use serde::de::Error as DeError; use serde_cow::CowStr; +#[cfg(not(feature = "unstable"))] use serde_json::value::RawValue; use small_fixed_array::FixedString; From df4c85001045260a8a9e5b03ecf6c68783585907 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Tue, 22 Apr 2025 10:56:15 +0100 Subject: [PATCH 19/21] official docs --- src/model/application/component.rs | 18 +++++++++--------- src/model/channel/message.rs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 1281d5a74be..dd24129abe9 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -115,7 +115,7 @@ impl<'de> Deserialize<'de> for Component { /// A component that is a container for up to 3 text display components and an accessory. /// -/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) +/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -139,7 +139,7 @@ pub struct Section { /// /// See [`Section`] for how this fits within a section. /// -/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) +/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -158,7 +158,7 @@ pub struct Thumbnail { /// An unfurled media item. /// -/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) +/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -195,7 +195,7 @@ enum_number! { /// A text display component. /// -/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) +/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -207,7 +207,7 @@ pub struct TextDisplay { /// A media gallery component. /// -/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) +/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -224,7 +224,7 @@ pub struct MediaGallery { /// /// Belongs to [`MediaGallery`]. /// -/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) +/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -240,7 +240,7 @@ pub struct MediaGalleryItem { /// A separator component /// -/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) +/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -270,7 +270,7 @@ enum_number! { /// A file component, will not render a text preview to the user. /// -/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) +/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] @@ -287,7 +287,7 @@ pub struct FileComponent { /// A container component, similar to an embed but without all the functionality. /// -/// [Incomplete Discord docs](https://github.com/Lulalaby/discord-api-docs/pull/30) +/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index d0bd1e18d8d..53e91d504b8 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -971,7 +971,7 @@ bitflags! { /// - Files will not have a simple text preview. /// - URLs will not generate embeds. /// - /// For more details, refer to the Discord documentation: [https://github.com/Lulalaby/discord-api-docs/pull/30] + /// For more details, refer to the Discord documentation: [https://github.com/discord/discord-api-docs/pull/7487/] const IS_COMPONENTS_V2 = 1 << 15; } From f17f37ae220a58947cfd18ad019d9870972d43fa Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Wed, 23 Apr 2025 00:03:54 +0100 Subject: [PATCH 20/21] fix to official documentation, remove loading state --- src/model/application/component.rs | 45 ++++++++++-------------------- src/model/channel/message.rs | 2 +- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/model/application/component.rs b/src/model/application/component.rs index dd24129abe9..5209c8728ab 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -115,7 +115,7 @@ impl<'de> Deserialize<'de> for Component { /// A component that is a container for up to 3 text display components and an accessory. /// -/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) +/// [Discord docs](https://discord.com/developers/docs/components/reference#section) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -139,7 +139,7 @@ pub struct Section { /// /// See [`Section`] for how this fits within a section. /// -/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) +/// [Discord docs](https://discord.com/developers/docs/components/reference#thumbnail) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -156,9 +156,9 @@ pub struct Thumbnail { pub spoiler: Option, } -/// An unfurled media item. +/// A url or attachment. /// -/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) +/// [Discord docs](https://discord.com/developers/docs/components/reference#unfurled-media-item-structure) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -174,28 +174,12 @@ pub struct UnfurledMediaItem { pub height: Option, /// The content type of the media item. pub content_type: Option, - /// The loading state of the item, declaring if it has fully loaded yet. - pub loading_state: Option, } -#[cfg(feature = "unstable")] -enum_number! { - /// The loading state of the media item. - #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] - #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] - #[non_exhaustive] - pub enum UnfurledMediaItemLoadingState { - DiscordUnknown = 0, - Loading = 1, - LoadingSuccess = 2, - LoadingNotFound = 3, - _ => Unknown(u8), - } -} - -/// A text display component. +/// A component that allows you to add text to your message, similiar to the `content` field of a +/// message. /// -/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) +/// [Discord docs](https://discord.com/developers/docs/components/reference#text-display) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -205,9 +189,10 @@ pub struct TextDisplay { pub content: FixedString, } -/// A media gallery component. +/// A Media Gallery is a component that allows you to display media attachments in an organized +/// gallery format. /// -/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) +/// [Discord docs](https://discord.com/developers/docs/components/reference#media-gallery) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -224,7 +209,7 @@ pub struct MediaGallery { /// /// Belongs to [`MediaGallery`]. /// -/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) +/// [Discord docs](https://discord.com/developers/docs/components/reference#media-gallery-media-gallery-item-structure) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -238,9 +223,9 @@ pub struct MediaGalleryItem { pub spoiler: Option, } -/// A separator component +/// A component that adds vertical padding and visual division between other components. /// -/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) +/// [Discord docs](https://discord.com/developers/docs/components/reference#separator) #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] @@ -270,7 +255,7 @@ enum_number! { /// A file component, will not render a text preview to the user. /// -/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) +/// [Discord docs](https://discord.com/developers/docs/components/reference#file) #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] @@ -287,7 +272,7 @@ pub struct FileComponent { /// A container component, similar to an embed but without all the functionality. /// -/// [Discord docs](https://github.com/discord/discord-api-docs/pull/7487/) +/// [Discord docs](https://discord.com/developers/docs/components/reference#container) #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 53e91d504b8..1b32699d6e1 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -971,7 +971,7 @@ bitflags! { /// - Files will not have a simple text preview. /// - URLs will not generate embeds. /// - /// For more details, refer to the Discord documentation: [https://github.com/discord/discord-api-docs/pull/7487/] + /// For more details, refer to the Discord documentation: [https://discord.com/developers/docs/components/reference#component-reference] const IS_COMPONENTS_V2 = 1 << 15; } From 7ec77520505796f779ef845180d54422a77015f0 Mon Sep 17 00:00:00 2001 From: jamesbt365 Date: Wed, 30 Apr 2025 20:34:08 +0100 Subject: [PATCH 21/21] Remove unstable cfg flag Also changes the modal stuff back to CreateActionRow, no need for this extra layer of nesting if modals only use ActionRow? --- src/builder/create_components.rs | 28 +-------- src/builder/create_interaction_response.rs | 39 +++---------- .../create_interaction_response_followup.rs | 24 +++----- src/builder/create_message.rs | 25 +++----- src/builder/edit_interaction_response.rs | 12 +--- src/builder/edit_message.rs | 24 +++----- src/builder/edit_webhook_message.rs | 29 +++------- src/builder/execute_webhook.rs | 33 +++-------- src/builder/mod.rs | 14 ----- src/collector/quick_modal.rs | 14 +---- src/model/application/component.rs | 27 +-------- src/model/channel/message.rs | 12 ---- src/model/mod.rs | 2 - src/model/utils.rs | 57 ------------------- 14 files changed, 53 insertions(+), 287 deletions(-) diff --git a/src/builder/create_components.rs b/src/builder/create_components.rs index 6c18227dc6d..636068399f7 100644 --- a/src/builder/create_components.rs +++ b/src/builder/create_components.rs @@ -5,10 +5,8 @@ use serde::Serialize; use crate::model::prelude::*; #[derive(Clone, Debug)] -#[cfg(feature = "unstable")] struct StaticU8; -#[cfg(feature = "unstable")] impl Serialize for StaticU8 { fn serialize(&self, ser: S) -> Result { ser.serialize_u8(VAL) @@ -67,8 +65,7 @@ impl serde::Serialize for CreateActionRow<'_> { /// To send V2 components, you must set [`MessageFlags::IS_COMPONENTS_V2`]. /// /// ### Limitations -/// - You can include a maximum of **10 top-level components** per message. -/// - The total number of **nested components** is limited to **30**. +/// - The total number of components is limited to **40**.. /// - The maximum character count for text within components is **4000**. /// - The ability to set the `content` and `embeds` field will be disabled /// - No support for audio files @@ -77,7 +74,6 @@ impl serde::Serialize for CreateActionRow<'_> { #[derive(Clone, Debug, Serialize)] #[must_use] #[serde(untagged)] -#[cfg(feature = "unstable")] pub enum CreateComponent<'a> { /// Represents an action row component (V1). /// @@ -115,7 +111,6 @@ pub enum CreateComponent<'a> { /// accessory. #[derive(Clone, Debug, Serialize)] #[must_use] -#[cfg(feature = "unstable")] pub struct CreateSection<'a> { #[serde(rename = "type")] kind: StaticU8<9>, @@ -124,7 +119,6 @@ pub struct CreateSection<'a> { accessory: CreateSectionAccessory<'a>, } -#[cfg(feature = "unstable")] impl<'a> CreateSection<'a> { /// Creates a new builder with the specified components and accessory. /// @@ -171,22 +165,18 @@ impl<'a> CreateSection<'a> { #[derive(Clone, Debug, Serialize)] #[must_use] #[serde(untagged)] -#[cfg(feature = "unstable")] pub enum CreateSectionComponent<'a> { TextDisplay(CreateTextDisplay<'a>), } /// A builder to create a text display component. #[derive(Clone, Debug, Serialize)] -#[must_use] -#[cfg(feature = "unstable")] pub struct CreateTextDisplay<'a> { #[serde(rename = "type")] kind: StaticU8<10>, content: Cow<'a, str>, } -#[cfg(feature = "unstable")] impl<'a> CreateTextDisplay<'a> { /// Creates a new text display component. /// @@ -202,6 +192,7 @@ impl<'a> CreateTextDisplay<'a> { /// [`Self::new`]. /// /// Note: All components on a message shares the same **4000** character limit. + #[must_use] pub fn content(mut self, content: impl Into>) -> Self { self.content = content.into(); self @@ -212,7 +203,6 @@ impl<'a> CreateTextDisplay<'a> { #[derive(Clone, Debug, Serialize)] #[must_use] #[serde(untagged)] -#[cfg(feature = "unstable")] pub enum CreateSectionAccessory<'a> { Thumbnail(CreateThumbnail<'a>), Button(CreateButton<'a>), @@ -221,7 +211,6 @@ pub enum CreateSectionAccessory<'a> { /// A builder to create a thumbnail for a section. #[derive(Clone, Debug, Serialize)] #[must_use] -#[cfg(feature = "unstable")] pub struct CreateThumbnail<'a> { #[serde(rename = "type")] kind: StaticU8<11>, @@ -232,7 +221,6 @@ pub struct CreateThumbnail<'a> { spoiler: Option, } -#[cfg(feature = "unstable")] impl<'a> CreateThumbnail<'a> { /// Creates a new thumbnail with a media item. pub fn new(media: CreateUnfurledMediaItem<'a>) -> Self { @@ -266,12 +254,10 @@ impl<'a> CreateThumbnail<'a> { /// A builder to create a media item. #[derive(Clone, Debug, Serialize, Default)] #[must_use] -#[cfg(feature = "unstable")] pub struct CreateUnfurledMediaItem<'a> { url: Cow<'a, str>, } -#[cfg(feature = "unstable")] impl<'a> CreateUnfurledMediaItem<'a> { /// Creates a new media item. pub fn new(url: impl Into>) -> Self { @@ -292,14 +278,12 @@ impl<'a> CreateUnfurledMediaItem<'a> { /// Note: May contain up to **10** items. #[derive(Clone, Debug, Serialize)] #[must_use] -#[cfg(feature = "unstable")] pub struct CreateMediaGallery<'a> { #[serde(rename = "type")] kind: StaticU8<12>, items: Cow<'a, [CreateMediaGalleryItem<'a>]>, } -#[cfg(feature = "unstable")] impl<'a> CreateMediaGallery<'a> { /// Creates a new media gallery with up to **10** items. pub fn new(items: impl Into]>>) -> Self { @@ -330,7 +314,6 @@ impl<'a> CreateMediaGallery<'a> { /// Builder to create individual media gallery items. #[derive(Clone, Debug, Serialize, Default)] #[must_use] -#[cfg(feature = "unstable")] pub struct CreateMediaGalleryItem<'a> { media: CreateUnfurledMediaItem<'a>, #[serde(skip_serializing_if = "Option::is_none")] @@ -339,7 +322,6 @@ pub struct CreateMediaGalleryItem<'a> { spoiler: Option, } -#[cfg(feature = "unstable")] impl<'a> CreateMediaGalleryItem<'a> { /// Create a new media gallery item. pub fn new(media: CreateUnfurledMediaItem<'a>) -> Self { @@ -389,7 +371,6 @@ impl<'a> CreateMediaGalleryItem<'a> { /// refer to the [Discord Documentation](https://discord.com/developers/docs/reference#uploading-files). #[derive(Clone, Debug, Serialize)] #[must_use] -#[cfg(feature = "unstable")] pub struct CreateFile<'a> { #[serde(rename = "type")] kind: StaticU8<13>, @@ -398,7 +379,6 @@ pub struct CreateFile<'a> { spoiler: Option, } -#[cfg(feature = "unstable")] impl<'a> CreateFile<'a> { /// Create a new builder for the file component. Refer to this builders documentation for /// limits. @@ -427,7 +407,6 @@ impl<'a> CreateFile<'a> { /// A builder for creating a separator. #[derive(Clone, Debug, Serialize)] #[must_use] -#[cfg(feature = "unstable")] pub struct CreateSeparator { #[serde(rename = "type")] kind: StaticU8<14>, @@ -436,7 +415,6 @@ pub struct CreateSeparator { spacing: Option, } -#[cfg(feature = "unstable")] impl CreateSeparator { /// Creates a new separator, with or without a divider. pub fn new(divider: bool) -> Self { @@ -464,7 +442,6 @@ impl CreateSeparator { /// A builder to create a container, which acts similarly to embeds. #[derive(Clone, Debug, Serialize)] #[must_use] -#[cfg(feature = "unstable")] pub struct CreateContainer<'a> { #[serde(rename = "type")] kind: StaticU8<17>, @@ -475,7 +452,6 @@ pub struct CreateContainer<'a> { components: Cow<'a, [CreateComponent<'a>]>, } -#[cfg(feature = "unstable")] impl<'a> CreateContainer<'a> { /// Create a new container, with an array of components inside. This component may contain any /// other component except another container! diff --git a/src/builder/create_interaction_response.rs b/src/builder/create_interaction_response.rs index 552978ec6d2..480910804d8 100644 --- a/src/builder/create_interaction_response.rs +++ b/src/builder/create_interaction_response.rs @@ -1,12 +1,16 @@ use std::borrow::Cow; use std::collections::HashMap; -#[cfg(not(feature = "unstable"))] -use super::CreateActionRow; -#[cfg(feature = "unstable")] -use super::CreateComponent; use super::create_poll::Ready; -use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, CreatePoll, EditAttachments}; +use super::{ + CreateActionRow, + CreateAllowedMentions, + CreateAttachment, + CreateComponent, + CreateEmbed, + CreatePoll, + EditAttachments, +}; #[cfg(feature = "http")] use crate::http::Http; use crate::internal::prelude::*; @@ -157,12 +161,8 @@ pub struct CreateInteractionResponseMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] flags: Option, #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(feature = "unstable")] components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(not(feature = "unstable"))] - components: Option]>>, - #[serde(skip_serializing_if = "Option::is_none")] poll: Option>, attachments: EditAttachments<'a>, } @@ -274,19 +274,11 @@ impl<'a> CreateInteractionResponseMessage<'a> { } /// Sets the components of this message. - #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } - /// Sets the components of this message. - #[cfg(not(feature = "unstable"))] - pub fn components(mut self, components: impl Into]>>) -> Self { - self.components = Some(components.into()); - self - } - /// Adds a poll to the message. Only one poll can be added per message. /// /// See [`CreatePoll`] for more information on creating and configuring a poll. @@ -432,10 +424,7 @@ impl<'a> CreateAutocompleteResponse<'a> { #[derive(Clone, Debug, Default, Serialize)] #[must_use] pub struct CreateModal<'a> { - #[cfg(not(feature = "unstable"))] components: Cow<'a, [CreateActionRow<'a>]>, - #[cfg(feature = "unstable")] - components: Cow<'a, [CreateComponent<'a>]>, custom_id: Cow<'a, str>, title: Cow<'a, str>, } @@ -453,16 +442,6 @@ impl<'a> CreateModal<'a> { /// Sets the components of this message. /// /// Overwrites existing components. - #[cfg(feature = "unstable")] - pub fn components(mut self, components: impl Into]>>) -> Self { - self.components = components.into(); - self - } - - /// Sets the components of this message. - /// - /// Overwrites existing components. - #[cfg(not(feature = "unstable"))] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = components.into(); self diff --git a/src/builder/create_interaction_response_followup.rs b/src/builder/create_interaction_response_followup.rs index abc55ea6f02..f5bf6b872d2 100644 --- a/src/builder/create_interaction_response_followup.rs +++ b/src/builder/create_interaction_response_followup.rs @@ -1,11 +1,14 @@ use std::borrow::Cow; -#[cfg(not(feature = "unstable"))] -use super::CreateActionRow; -#[cfg(feature = "unstable")] -use super::CreateComponent; use super::create_poll::Ready; -use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, CreatePoll, EditAttachments}; +use super::{ + CreateAllowedMentions, + CreateAttachment, + CreateComponent, + CreateEmbed, + CreatePoll, + EditAttachments, +}; #[cfg(feature = "http")] use crate::http::Http; #[cfg(feature = "http")] @@ -26,12 +29,8 @@ pub struct CreateInteractionResponseFollowup<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(feature = "unstable")] components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(not(feature = "unstable"))] - components: Option]>>, - #[serde(skip_serializing_if = "Option::is_none")] flags: Option, #[serde(skip_serializing_if = "Option::is_none")] poll: Option>, @@ -154,13 +153,6 @@ impl<'a> CreateInteractionResponseFollowup<'a> { } /// Sets the components of this message. - #[cfg(not(feature = "unstable"))] - pub fn components(mut self, components: impl Into]>>) -> Self { - self.components = Some(components.into()); - self - } - - #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self diff --git a/src/builder/create_message.rs b/src/builder/create_message.rs index e221cf1926e..4df7a1839be 100644 --- a/src/builder/create_message.rs +++ b/src/builder/create_message.rs @@ -1,11 +1,14 @@ use std::borrow::Cow; -#[cfg(not(feature = "unstable"))] -use super::CreateActionRow; -#[cfg(feature = "unstable")] -use super::CreateComponent; use super::create_poll::Ready; -use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, CreatePoll, EditAttachments}; +use super::{ + CreateAllowedMentions, + CreateAttachment, + CreateComponent, + CreateEmbed, + CreatePoll, + EditAttachments, +}; #[cfg(feature = "http")] use crate::http::Http; #[cfg(feature = "http")] @@ -60,11 +63,7 @@ pub struct CreateMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] message_reference: Option, #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(feature = "unstable")] components: Option]>>, - #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(not(feature = "unstable"))] - components: Option]>>, sticker_ids: Cow<'a, [StickerId]>, #[serde(skip_serializing_if = "Option::is_none")] flags: Option, @@ -185,19 +184,11 @@ impl<'a> CreateMessage<'a> { } /// Sets the components of this message. - #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } - /// Sets the components of this message. - #[cfg(not(feature = "unstable"))] - pub fn components(mut self, components: impl Into]>>) -> Self { - self.components = Some(components.into()); - self - } - super::button_and_select_menu_convenience_methods!(self.components); /// Sets the flags for the message. diff --git a/src/builder/edit_interaction_response.rs b/src/builder/edit_interaction_response.rs index a128d9e19f6..0505b8a30f4 100644 --- a/src/builder/edit_interaction_response.rs +++ b/src/builder/edit_interaction_response.rs @@ -1,12 +1,9 @@ use std::borrow::Cow; -#[cfg(not(feature = "unstable"))] -use super::CreateActionRow; -#[cfg(feature = "unstable")] -use super::CreateComponent; use super::{ CreateAllowedMentions, CreateAttachment, + CreateComponent, CreateEmbed, EditAttachments, EditWebhookMessage, @@ -73,17 +70,10 @@ impl<'a> EditInteractionResponse<'a> { } /// Sets the components of this message. - #[cfg(feature = "unstable")] pub fn components(self, components: impl Into]>>) -> Self { Self(self.0.components(components)) } - /// Sets the components of this message. - #[cfg(not(feature = "unstable"))] - pub fn components(self, components: impl Into]>>) -> Self { - Self(self.0.components(components)) - } - super::button_and_select_menu_convenience_methods!(self.0.components); /// Sets attachments, see [`EditAttachments`] for more details. diff --git a/src/builder/edit_message.rs b/src/builder/edit_message.rs index ebcac16a6a9..228a84e4f5f 100644 --- a/src/builder/edit_message.rs +++ b/src/builder/edit_message.rs @@ -1,10 +1,12 @@ use std::borrow::Cow; -#[cfg(not(feature = "unstable"))] -use super::CreateActionRow; -#[cfg(feature = "unstable")] -use super::CreateComponent; -use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, EditAttachments}; +use super::{ + CreateAllowedMentions, + CreateAttachment, + CreateComponent, + CreateEmbed, + EditAttachments, +}; #[cfg(feature = "http")] use crate::http::CacheHttp; #[cfg(feature = "http")] @@ -43,12 +45,8 @@ pub struct EditMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(feature = "unstable")] components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(not(feature = "unstable"))] - components: Option]>>, - #[serde(skip_serializing_if = "Option::is_none")] attachments: Option>, } @@ -138,19 +136,11 @@ impl<'a> EditMessage<'a> { } /// Sets the components of this message. - #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } - /// Sets the components of this message. - #[cfg(not(feature = "unstable"))] - pub fn components(mut self, components: impl Into]>>) -> Self { - self.components = Some(components.into()); - self - } - super::button_and_select_menu_convenience_methods!(self.components); /// Sets the flags for the message. diff --git a/src/builder/edit_webhook_message.rs b/src/builder/edit_webhook_message.rs index 9522de2f7ba..2474e111dd9 100644 --- a/src/builder/edit_webhook_message.rs +++ b/src/builder/edit_webhook_message.rs @@ -1,10 +1,12 @@ use std::borrow::Cow; -#[cfg(not(feature = "unstable"))] -use super::CreateActionRow; -#[cfg(feature = "unstable")] -use super::CreateComponent; -use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, EditAttachments}; +use super::{ + CreateAllowedMentions, + CreateAttachment, + CreateComponent, + CreateEmbed, + EditAttachments, +}; #[cfg(feature = "http")] use crate::http::Http; #[cfg(feature = "http")] @@ -24,12 +26,8 @@ pub struct EditWebhookMessage<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(feature = "unstable")] pub(crate) components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(not(feature = "unstable"))] - pub(crate) components: Option]>>, - #[serde(skip_serializing_if = "Option::is_none")] pub(crate) attachments: Option>, #[serde(skip)] @@ -109,24 +107,11 @@ impl<'a> EditWebhookMessage<'a> { /// /// [`WebhookType::Application`]: crate::model::webhook::WebhookType /// [`WebhookType::Incoming`]: crate::model::webhook::WebhookType - #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self } - /// Sets the components for this message. Requires an application-owned webhook, meaning either - /// the webhook's `kind` field is set to [`WebhookType::Application`], or it was created by an - /// application (and has kind [`WebhookType::Incoming`]). - /// - /// [`WebhookType::Application`]: crate::model::webhook::WebhookType - /// [`WebhookType::Incoming`]: crate::model::webhook::WebhookType - #[cfg(not(feature = "unstable"))] - pub fn components(mut self, components: impl Into]>>) -> Self { - self.components = Some(components.into()); - self - } - super::button_and_select_menu_convenience_methods!(self.components); /// Sets attachments, see [`EditAttachments`] for more details. diff --git a/src/builder/execute_webhook.rs b/src/builder/execute_webhook.rs index 6e5e3ab95b3..8e19481b5d4 100644 --- a/src/builder/execute_webhook.rs +++ b/src/builder/execute_webhook.rs @@ -1,10 +1,12 @@ use std::borrow::Cow; -#[cfg(not(feature = "unstable"))] -use super::CreateActionRow; -#[cfg(feature = "unstable")] -use super::CreateComponent; -use super::{CreateAllowedMentions, CreateAttachment, CreateEmbed, EditAttachments}; +use super::{ + CreateAllowedMentions, + CreateAttachment, + CreateComponent, + CreateEmbed, + EditAttachments, +}; #[cfg(feature = "http")] use crate::http::Http; #[cfg(feature = "http")] @@ -66,12 +68,8 @@ pub struct ExecuteWebhook<'a> { #[serde(skip_serializing_if = "Option::is_none")] allowed_mentions: Option>, #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(feature = "unstable")] components: Option]>>, #[serde(skip_serializing_if = "Option::is_none")] - #[cfg(not(feature = "unstable"))] - components: Option]>>, - #[serde(skip_serializing_if = "Option::is_none")] flags: Option, #[serde(skip_serializing_if = "Option::is_none")] thread_name: Option>, @@ -209,28 +207,11 @@ impl<'a> ExecuteWebhook<'a> { self } - /// Sets the components for this message. Requires an application-owned webhook, meaning either - /// the webhook's `kind` field is set to [`WebhookType::Application`], or it was created by an - /// application (and has kind [`WebhookType::Incoming`]). - /// - /// If [`Self::with_components`] is set, non-interactive components can be used on non - /// application-owned webhooks. - /// - /// [`WebhookType::Application`]: crate::model::webhook::WebhookType - /// [`WebhookType::Incoming`]: crate::model::webhook::WebhookType - #[cfg(not(feature = "unstable"))] - pub fn components(mut self, components: impl Into]>>) -> Self { - self.components = Some(components.into()); - self - } - /// Sets the components for this message. Requires an application-owned webhook, meaning either /// the webhook's `kind` field is set to [`WebhookType::Application`], or it was created by an /// application (and has kind [`WebhookType::Incoming`]). /// /// [`WebhookType::Application`]: crate::model::webhook::WebhookType - /// [`WebhookType::Incoming`]: crate::model::webhook::WebhookType - #[cfg(feature = "unstable")] pub fn components(mut self, components: impl Into]>>) -> Self { self.components = Some(components.into()); self diff --git a/src/builder/mod.rs b/src/builder/mod.rs index a6b61e0e4a8..c6b27a84279 100644 --- a/src/builder/mod.rs +++ b/src/builder/mod.rs @@ -130,18 +130,11 @@ macro_rules! button_and_select_menu_convenience_methods { pub fn button(mut $self, button: super::CreateButton<'a>) -> Self { let rows = $self$(.$components_path)+.get_or_insert_with(Cow::default).to_mut(); let row_with_space_left = rows.last_mut().and_then(|row| match row { - #[cfg(not(feature = "unstable"))] - super::CreateActionRow::Buttons(buttons) if buttons.len() < 5 => Some(buttons.to_mut()), - - #[cfg(feature = "unstable")] super::CreateComponent::ActionRow(super::CreateActionRow::Buttons(buttons)) if buttons.len() < 5 => Some(buttons.to_mut()), _ => None, }); match row_with_space_left { Some(row) => row.push(button), - #[cfg(not(feature = "unstable"))] - None => rows.push(super::CreateActionRow::buttons(vec![button])), - #[cfg(feature = "unstable")] None => rows.push(super::CreateComponent::ActionRow(super::CreateActionRow::buttons(vec![button]))), } $self @@ -151,14 +144,7 @@ macro_rules! button_and_select_menu_convenience_methods { /// /// Convenience method that wraps [`Self::components`]. pub fn select_menu(mut $self, select_menu: super::CreateSelectMenu<'a>) -> Self { - #[cfg(not(feature = "unstable"))] - $self$(.$components_path)+ - .get_or_insert_with(Cow::default) - .to_mut() - .push(super::CreateActionRow::SelectMenu(select_menu)); - - #[cfg(feature = "unstable")] $self$(.$components_path)+ .get_or_insert_with(Cow::default) .to_mut() diff --git a/src/collector/quick_modal.rs b/src/collector/quick_modal.rs index 811aeceb97f..d4d0c40d74f 100644 --- a/src/collector/quick_modal.rs +++ b/src/collector/quick_modal.rs @@ -1,7 +1,5 @@ use std::borrow::Cow; -#[cfg(feature = "unstable")] -use crate::builder::CreateComponent; use crate::builder::{CreateActionRow, CreateInputText, CreateInteractionResponse, CreateModal}; use crate::collector::ModalInteractionCollector; use crate::gateway::client::Context; @@ -93,17 +91,7 @@ impl<'a> CreateQuickModal<'a> { .into_iter() .enumerate() .map(|(i, input_text)| { - #[cfg(not(feature = "unstable"))] - { - CreateActionRow::InputText(input_text.custom_id(i.to_string())) - } - - #[cfg(feature = "unstable")] - { - CreateComponent::ActionRow(CreateActionRow::InputText( - input_text.custom_id(i.to_string()), - )) - } + CreateActionRow::InputText(input_text.custom_id(i.to_string())) }) .collect::>(), ), diff --git a/src/model/application/component.rs b/src/model/application/component.rs index 5209c8728ab..fbb1c9aeabf 100644 --- a/src/model/application/component.rs +++ b/src/model/application/component.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "unstable")] use nonmax::NonMaxU32; use serde::de::Error as DeError; use serde::ser::{Serialize, Serializer}; @@ -21,19 +20,12 @@ enum_number! { RoleSelect = 6, MentionableSelect = 7, ChannelSelect = 8, - #[cfg(feature = "unstable")] Section = 9, - #[cfg(feature = "unstable")] TextDisplay = 10, - #[cfg(feature = "unstable")] Thumbnail = 11, - #[cfg(feature = "unstable")] MediaGallery = 12, - #[cfg(feature = "unstable")] File = 13, - #[cfg(feature = "unstable")] Separator = 14, - #[cfg(feature = "unstable")] Container = 17, _ => Unknown(u8), } @@ -50,7 +42,6 @@ enum_number! { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Serialize)] #[non_exhaustive] -#[cfg(feature = "unstable")] pub enum Component { ActionRow(ActionRow), Button(Button), @@ -63,13 +54,8 @@ pub enum Component { File(FileComponent), Container(Container), Unknown(u8), - // always update the macro below. } -// TODO: add something like this to every variant. -// The component type, it will always be [`ComponentType::Thing`]. - -#[cfg(feature = "unstable")] impl<'de> Deserialize<'de> for Component { fn deserialize(deserializer: D) -> std::result::Result where @@ -119,7 +105,6 @@ impl<'de> Deserialize<'de> for Component { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] -#[cfg(feature = "unstable")] pub struct Section { /// Always [`ComponentType::Section`] #[serde(rename = "type")] @@ -143,7 +128,6 @@ pub struct Section { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] -#[cfg(feature = "unstable")] pub struct Thumbnail { /// Always [`ComponentType::Thumbnail`] #[serde(rename = "type")] @@ -162,7 +146,6 @@ pub struct Thumbnail { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] -#[cfg(feature = "unstable")] pub struct UnfurledMediaItem { /// The url of this item. pub url: FixedString, @@ -183,8 +166,10 @@ pub struct UnfurledMediaItem { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] -#[cfg(feature = "unstable")] pub struct TextDisplay { + /// Always [`ComponentType::TextDisplay`] + #[serde(rename = "type")] + pub kind: ComponentType, /// The content of this text display component. pub content: FixedString, } @@ -196,7 +181,6 @@ pub struct TextDisplay { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] -#[cfg(feature = "unstable")] pub struct MediaGallery { /// Always [`ComponentType::MediaGallery`] #[serde(rename = "type")] @@ -213,7 +197,6 @@ pub struct MediaGallery { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] -#[cfg(feature = "unstable")] pub struct MediaGalleryItem { /// The internal media piece that this item contains. pub media: UnfurledMediaItem, @@ -229,7 +212,6 @@ pub struct MediaGalleryItem { #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[derive(Clone, Debug, Deserialize, Serialize)] #[non_exhaustive] -#[cfg(feature = "unstable")] pub struct Separator { /// Always [`ComponentType::Separator`] #[serde(rename = "type")] @@ -240,7 +222,6 @@ pub struct Separator { pub spacing: Option, } -#[cfg(feature = "unstable")] enum_number! { /// The size of a separator component. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] @@ -259,7 +240,6 @@ enum_number! { #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] -#[cfg(feature = "unstable")] pub struct FileComponent { /// Always [`ComponentType::File`] #[serde(rename = "type")] @@ -276,7 +256,6 @@ pub struct FileComponent { #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))] #[non_exhaustive] -#[cfg(feature = "unstable")] pub struct Container { /// Always [`ComponentType::Container`] #[serde(rename = "type")] diff --git a/src/model/channel/message.rs b/src/model/channel/message.rs index 1b32699d6e1..dc00ecbb010 100644 --- a/src/model/channel/message.rs +++ b/src/model/channel/message.rs @@ -14,8 +14,6 @@ use crate::constants; #[cfg(feature = "model")] use crate::http::{CacheHttp, Http}; use crate::model::prelude::*; -#[cfg(not(feature = "unstable"))] -use crate::model::utils::deserialize_components; use crate::model::utils::{StrOrInt, discord_colours}; /// A representation of a message over a guild's text channel, a group, or a private channel. @@ -112,13 +110,7 @@ pub struct Message { pub thread: Option>, /// The components of this message #[serde(default)] - #[cfg(feature = "unstable")] pub components: FixedArray, - - /// The components of this message - #[cfg(not(feature = "unstable"))] - #[serde(default, deserialize_with = "deserialize_components")] - pub components: FixedArray, /// Array of message sticker item objects. #[serde(default)] pub sticker_items: FixedArray, @@ -893,11 +885,7 @@ pub struct MessageSnapshot { pub kind: MessageType, pub flags: Option, #[serde(default)] - #[cfg(feature = "unstable")] pub components: FixedArray, - #[serde(default, deserialize_with = "deserialize_components")] - #[cfg(not(feature = "unstable"))] - pub components: FixedArray, #[serde(default)] pub sticker_items: FixedArray, } diff --git a/src/model/mod.rs b/src/model/mod.rs index 20745356430..60d0bd41bd2 100644 --- a/src/model/mod.rs +++ b/src/model/mod.rs @@ -62,8 +62,6 @@ pub use self::timestamp::Timestamp; /// use serenity::model::prelude::*; /// ``` pub mod prelude { - #[cfg(not(feature = "unstable"))] - pub(crate) use serde::de::Visitor; pub(crate) use serde::{Deserialize, Deserializer}; pub use super::guild::automod::EventType as AutomodEventType; diff --git a/src/model/utils.rs b/src/model/utils.rs index 533d569773e..42fd5249660 100644 --- a/src/model/utils.rs +++ b/src/model/utils.rs @@ -3,8 +3,6 @@ use std::fmt; use arrayvec::ArrayVec; use serde::de::Error as DeError; use serde_cow::CowStr; -#[cfg(not(feature = "unstable"))] -use serde_json::value::RawValue; use small_fixed_array::FixedString; use super::prelude::*; @@ -240,58 +238,3 @@ where }) .collect() } - -// Custom deserialize function to deserialize components safely without knocking the whole message -// out when new components are found but not supported. -#[cfg(not(feature = "unstable"))] -pub fn deserialize_components<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct MinComponent { - #[serde(rename = "type")] - kind: u8, - } - - struct ComponentsVisitor; - - impl<'de> Visitor<'de> for ComponentsVisitor { - type Value = FixedArray; - - fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - formatter.write_str("a sequence of ActionRow elements") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let mut components = Vec::with_capacity(seq.size_hint().unwrap_or_default()); - - while let Some(raw) = seq.next_element::<&RawValue>()? { - // We deserialize only the `kind` field to determine the component type. - // We later use this to check if its a supported component before deserializing the - // entire payload. - let min_component = - MinComponent::deserialize(raw).map_err(serde::de::Error::custom)?; - - // Action rows are the only top level component supported in serenity at this time. - if min_component.kind == 1 { - components.push(ActionRow::deserialize(raw).map_err(serde::de::Error::custom)?); - } else { - // Top level component is not an action row and cannot be supported on - // serenity@current without breaking changes, so we skip them. - tracing::debug!( - "Skipping component with unsupported kind: {}", - min_component.kind - ); - } - } - - Ok(FixedArray::from_vec_trunc(components)) - } - } - - deserializer.deserialize_seq(ComponentsVisitor) -}