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