twilight_util/builder/
interaction_response_data.rs

1use twilight_model::{
2    application::command::CommandOptionChoice,
3    channel::message::{AllowedMentions, Component, Embed, MessageFlags},
4    http::{attachment::Attachment, interaction::InteractionResponseData},
5};
6
7/// Create an [`InteractionResponseData`] with a builder.
8///
9/// # Example
10/// ```
11/// use twilight_model::channel::message::{
12///     component::{ActionRow, Button, ButtonStyle, Component},
13///     MessageFlags,
14/// };
15/// use twilight_util::builder::InteractionResponseDataBuilder;
16///
17/// let component = Component::ActionRow(ActionRow {
18///     components: Vec::from([Component::Button(Button {
19///         style: ButtonStyle::Primary,
20///         emoji: None,
21///         label: Some("Button label".to_string()),
22///         custom_id: Some("button_id".to_string()),
23///         url: None,
24///         disabled: false,
25///         sku_id: None,
26///     })]),
27/// });
28///
29/// let interaction_response_data = InteractionResponseDataBuilder::new()
30///     .content("Callback message")
31///     .flags(MessageFlags::EPHEMERAL)
32///     .components([component.clone()])
33///     .build();
34///
35/// assert_eq!(interaction_response_data.components, Some(vec![component]));
36/// ```
37#[derive(Clone, Debug)]
38#[must_use = "builders have no effect if unused"]
39pub struct InteractionResponseDataBuilder(InteractionResponseData);
40
41impl InteractionResponseDataBuilder {
42    /// Create a new builder to construct an [`InteractionResponseData`].
43    pub const fn new() -> Self {
44        Self(InteractionResponseData {
45            allowed_mentions: None,
46            attachments: None,
47            choices: None,
48            components: None,
49            content: None,
50            custom_id: None,
51            embeds: None,
52            flags: None,
53            title: None,
54            tts: None,
55        })
56    }
57
58    /// Consume the builder, returning an [`InteractionResponseData`].
59    #[allow(clippy::missing_const_for_fn)]
60    #[must_use = "builders have no effect if unused"]
61    pub fn build(self) -> InteractionResponseData {
62        self.0
63    }
64
65    /// Set the [`AllowedMentions`] of the callback.
66    ///
67    /// Defaults to [`None`].
68    #[allow(clippy::missing_const_for_fn)]
69    pub fn allowed_mentions(mut self, allowed_mentions: AllowedMentions) -> Self {
70        self.0.allowed_mentions = Some(allowed_mentions);
71
72        self
73    }
74
75    /// Set the attachments of the message.
76    ///
77    /// Defaults to [`None`].
78    pub fn attachments(mut self, attachments: impl IntoIterator<Item = Attachment>) -> Self {
79        self.0.attachments = Some(attachments.into_iter().collect());
80
81        self
82    }
83
84    /// Set the autocomplete choices of the response.
85    ///
86    /// Only valid when the type of the interaction is
87    /// [`ApplicationCommandAutocompleteResult`].
88    ///
89    /// [`ApplicationCommandAutocompleteResult`]: twilight_model::http::interaction::InteractionResponseType::ApplicationCommandAutocompleteResult
90    pub fn choices(mut self, choices: impl IntoIterator<Item = CommandOptionChoice>) -> Self {
91        self.0.choices = Some(choices.into_iter().collect());
92
93        self
94    }
95
96    /// Set the message [`Component`]s of the callback.
97    ///
98    /// Defaults to [`None`].
99    pub fn components(mut self, components: impl IntoIterator<Item = Component>) -> Self {
100        self.0.components = Some(components.into_iter().collect());
101
102        self
103    }
104
105    /// Set the message content of the callback.
106    ///
107    /// Defaults to [`None`].
108    pub fn content(mut self, content: impl Into<String>) -> Self {
109        self.0.content = Some(content.into());
110
111        self
112    }
113
114    /// Set the custom ID of the callback.
115    ///
116    /// Defaults to [`None`].
117    pub fn custom_id(mut self, custom_id: impl Into<String>) -> Self {
118        self.0.custom_id = Some(custom_id.into());
119
120        self
121    }
122
123    /// Set the [`Embed`]s of the callback.
124    ///
125    /// Defaults to an empty list.
126    pub fn embeds(mut self, embeds: impl IntoIterator<Item = Embed>) -> Self {
127        self.0.embeds = Some(embeds.into_iter().collect());
128
129        self
130    }
131
132    /// Set the [`MessageFlags`].
133    ///
134    /// The only supported flags are [`EPHEMERAL`] and [`SUPPRESS_EMBEDS`].
135    ///
136    /// Defaults to [`None`].
137    ///
138    /// [`EPHEMERAL`]: twilight_model::channel::message::MessageFlags::EPHEMERAL
139    /// [`SUPPRESS_EMBEDS`]: twilight_model::channel::message::MessageFlags::SUPPRESS_EMBEDS
140    pub const fn flags(mut self, flags: MessageFlags) -> Self {
141        self.0.flags = Some(flags);
142
143        self
144    }
145
146    /// Set the title of the callback.
147    ///
148    /// Defaults to [`None`].
149    pub fn title(mut self, title: impl Into<String>) -> Self {
150        self.0.title = Some(title.into());
151
152        self
153    }
154
155    /// Set whether the response has text-to-speech enabled.
156    ///
157    /// Defaults to [`None`].
158    pub const fn tts(mut self, value: bool) -> Self {
159        self.0.tts = Some(value);
160
161        self
162    }
163}
164
165impl Default for InteractionResponseDataBuilder {
166    fn default() -> Self {
167        Self::new()
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::*;
174    use static_assertions::assert_impl_all;
175    use std::fmt::Debug;
176    use twilight_model::{
177        channel::message::{
178            component::{Button, ButtonStyle},
179            MentionType,
180        },
181        util::Timestamp,
182    };
183
184    assert_impl_all!(
185        InteractionResponseDataBuilder: Clone,
186        Debug,
187        Default,
188        Send,
189        Sync
190    );
191
192    #[test]
193    fn callback_data_builder() {
194        let allowed_mentions = AllowedMentions {
195            parse: Vec::from([MentionType::Everyone]),
196            ..Default::default()
197        };
198
199        let component = Component::Button(Button {
200            style: ButtonStyle::Primary,
201            emoji: None,
202            label: Some("test label".into()),
203            custom_id: Some("test custom id".into()),
204            url: None,
205            disabled: false,
206            sku_id: None,
207        });
208
209        let embed = Embed {
210            author: None,
211            color: Some(123),
212            description: Some("a description".to_owned()),
213            fields: Vec::new(),
214            footer: None,
215            image: None,
216            kind: "rich".to_owned(),
217            provider: None,
218            thumbnail: None,
219            timestamp: Some(Timestamp::from_secs(1_580_608_922).unwrap()),
220            title: Some("a title".to_owned()),
221            url: Some("https://example.com".to_owned()),
222            video: None,
223        };
224
225        let value = InteractionResponseDataBuilder::new()
226            .allowed_mentions(allowed_mentions.clone())
227            .components([component.clone()])
228            .content("a content")
229            .embeds([embed.clone()])
230            .flags(MessageFlags::empty())
231            .tts(false)
232            .build();
233
234        let expected = InteractionResponseData {
235            allowed_mentions: Some(allowed_mentions),
236            attachments: None,
237            choices: None,
238            components: Some(vec![component]),
239            content: Some("a content".to_owned()),
240            custom_id: None,
241            embeds: Some(vec![embed]),
242            flags: Some(MessageFlags::empty()),
243            title: None,
244            tts: Some(false),
245        };
246
247        assert_eq!(value, expected);
248    }
249}