Skip to main content

serenity/builder/
create_interaction_response.rs

1use super::create_poll::Ready;
2#[cfg(feature = "http")]
3use super::{check_overflow, Builder};
4use super::{
5    CreateActionRow,
6    CreateAllowedMentions,
7    CreateAttachment,
8    CreateEmbed,
9    CreatePoll,
10    EditAttachments,
11};
12#[cfg(feature = "http")]
13use crate::constants;
14#[cfg(feature = "http")]
15use crate::http::CacheHttp;
16use crate::internal::prelude::*;
17use crate::model::prelude::*;
18
19/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object).
20#[derive(Clone, Debug)]
21pub enum CreateInteractionResponse {
22    /// Acknowledges a Ping (only required when your bot uses an HTTP endpoint URL).
23    ///
24    /// Corresponds to Discord's `PONG`.
25    Pong,
26    /// Responds to an interaction with a message.
27    ///
28    /// Corresponds to Discord's `CHANNEL_MESSAGE_WITH_SOURCE`.
29    Message(CreateInteractionResponseMessage),
30    /// Acknowledges the interaction in order to edit a response later. The user sees a loading
31    /// state.
32    ///
33    /// Corresponds to Discord's `DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE`.
34    Defer(CreateInteractionResponseMessage),
35    /// Only valid for component-based interactions (seems to work for modal submit interactions
36    /// too even though it's not documented).
37    ///
38    /// Acknowledges the interaction. You can optionally edit the original message later. The user
39    /// does not see a loading state.
40    ///
41    /// Corresponds to Discord's `DEFERRED_UPDATE_MESSAGE`.
42    Acknowledge,
43    /// Only valid for component-based interactions.
44    ///
45    /// Edits the message the component was attached to.
46    ///
47    /// Corresponds to Discord's `UPDATE_MESSAGE`.
48    UpdateMessage(CreateInteractionResponseMessage),
49    /// Only valid for autocomplete interactions.
50    ///
51    /// Responds to the autocomplete interaction with suggested choices.
52    ///
53    /// Corresponds to Discord's `APPLICATION_COMMAND_AUTOCOMPLETE_RESULT`.
54    Autocomplete(CreateAutocompleteResponse),
55    /// Not valid for Modal and Ping interactions
56    ///
57    /// Responds to the interaction with a popup modal.
58    ///
59    /// Corresponds to Discord's `MODAL`.
60    Modal(CreateModal),
61    /// Not valid for autocomplete and Ping interactions. Only available for applications with
62    /// monetization enabled.
63    ///
64    /// Responds to the interaction with an upgrade button.
65    ///
66    /// Corresponds to Discord's `PREMIUM_REQUIRED'.
67    #[deprecated = "use premium button components via `CreateButton::new_premium` instead"]
68    PremiumRequired,
69    /// Not valid for autocomplete and Ping interactions. Only available for applications with
70    /// Activities enabled.
71    ///
72    /// Responds to the interaction by launching the Activity associated with the app.
73    ///
74    /// Corresponds to Discord's `LAUNCH_ACTIVITY`.
75    LaunchActivity,
76}
77
78impl serde::Serialize for CreateInteractionResponse {
79    #[allow(deprecated)] // We have to cover deprecated variants
80    fn serialize<S: serde::Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
81        use serde::ser::SerializeMap as _;
82
83        let mut map = serializer.serialize_map(Some(2))?;
84        map.serialize_entry("type", &match self {
85            Self::Pong => 1,
86            Self::Message(_) => 4,
87            Self::Defer(_) => 5,
88            Self::Acknowledge => 6,
89            Self::UpdateMessage(_) => 7,
90            Self::Autocomplete(_) => 8,
91            Self::Modal(_) => 9,
92            Self::PremiumRequired => 10,
93            Self::LaunchActivity => 12,
94        })?;
95
96        match self {
97            Self::Autocomplete(x) => map.serialize_entry("data", &x)?,
98            Self::Modal(x) => map.serialize_entry("data", &x)?,
99            Self::Message(x) | Self::Defer(x) | Self::UpdateMessage(x) => {
100                map.serialize_entry("data", &x)?;
101            },
102            Self::Pong | Self::Acknowledge | Self::PremiumRequired | Self::LaunchActivity => {
103                map.serialize_entry("data", &None::<()>)?;
104            },
105        }
106
107        map.end()
108    }
109}
110
111impl CreateInteractionResponse {
112    #[cfg(feature = "http")]
113    fn check_length(&self) -> Result<()> {
114        if let CreateInteractionResponse::Message(data)
115        | CreateInteractionResponse::Defer(data)
116        | CreateInteractionResponse::UpdateMessage(data) = self
117        {
118            if let Some(content) = &data.content {
119                check_overflow(content.chars().count(), constants::MESSAGE_CODE_LIMIT)
120                    .map_err(|overflow| Error::Model(ModelError::MessageTooLong(overflow)))?;
121            }
122
123            if let Some(embeds) = &data.embeds {
124                check_overflow(embeds.len(), constants::EMBED_MAX_COUNT)
125                    .map_err(|_| Error::Model(ModelError::EmbedAmount))?;
126
127                for embed in embeds {
128                    embed.check_length()?;
129                }
130            }
131        }
132        Ok(())
133    }
134}
135
136#[cfg(feature = "http")]
137#[async_trait::async_trait]
138impl Builder for CreateInteractionResponse {
139    type Context<'ctx> = (InteractionId, &'ctx str);
140    type Built = ();
141
142    /// Creates a response to the interaction received.
143    ///
144    /// **Note**: Message contents must be under 2000 unicode code points, and embeds must be under
145    /// 6000 code points.
146    ///
147    /// # Errors
148    ///
149    /// Returns an [`Error::Model`] if the message content is too long. May also return an
150    /// [`Error::Http`] if the API returns an error, or an [`Error::Json`] if there is an error in
151    /// deserializing the API response.
152    async fn execute(
153        mut self,
154        cache_http: impl CacheHttp,
155        ctx: Self::Context<'_>,
156    ) -> Result<Self::Built> {
157        self.check_length()?;
158        let files = match &mut self {
159            CreateInteractionResponse::Message(msg)
160            | CreateInteractionResponse::Defer(msg)
161            | CreateInteractionResponse::UpdateMessage(msg) => msg.attachments.take_files(),
162            _ => Vec::new(),
163        };
164
165        let http = cache_http.http();
166        if let Self::Message(msg) | Self::Defer(msg) | Self::UpdateMessage(msg) = &mut self {
167            if msg.allowed_mentions.is_none() {
168                msg.allowed_mentions.clone_from(&http.default_allowed_mentions);
169            }
170        }
171
172        http.create_interaction_response(ctx.0, ctx.1, &self, files).await
173    }
174}
175
176/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-messages).
177#[derive(Clone, Debug, Default, Serialize)]
178#[must_use]
179pub struct CreateInteractionResponseMessage {
180    #[serde(skip_serializing_if = "Option::is_none")]
181    tts: Option<bool>,
182    #[serde(skip_serializing_if = "Option::is_none")]
183    content: Option<String>,
184    #[serde(skip_serializing_if = "Option::is_none")]
185    embeds: Option<Vec<CreateEmbed>>,
186    #[serde(skip_serializing_if = "Option::is_none")]
187    allowed_mentions: Option<CreateAllowedMentions>,
188    #[serde(skip_serializing_if = "Option::is_none")]
189    flags: Option<InteractionResponseFlags>,
190    #[serde(skip_serializing_if = "Option::is_none")]
191    components: Option<Vec<CreateActionRow>>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    poll: Option<CreatePoll<Ready>>,
194    attachments: EditAttachments,
195}
196
197impl CreateInteractionResponseMessage {
198    /// Equivalent to [`Self::default`].
199    pub fn new() -> Self {
200        Self::default()
201    }
202
203    /// Set whether the message is text-to-speech.
204    ///
205    /// Think carefully before setting this to `true`.
206    ///
207    /// Defaults to `false`.
208    pub fn tts(mut self, tts: bool) -> Self {
209        self.tts = Some(tts);
210        self
211    }
212
213    /// Appends a file to the message.
214    pub fn add_file(mut self, file: CreateAttachment) -> Self {
215        self.attachments = self.attachments.add(file);
216        self
217    }
218
219    /// Appends a list of files to the message.
220    pub fn add_files(mut self, files: impl IntoIterator<Item = CreateAttachment>) -> Self {
221        for file in files {
222            self.attachments = self.attachments.add(file);
223        }
224        self
225    }
226
227    /// Sets a list of files to include in the message.
228    ///
229    /// Calling this multiple times will overwrite the file list. To append files, call
230    /// [`Self::add_file`] or [`Self::add_files`] instead.
231    pub fn files(mut self, files: impl IntoIterator<Item = CreateAttachment>) -> Self {
232        self.attachments = EditAttachments::new();
233        self.add_files(files)
234    }
235
236    /// Set the content of the message.
237    ///
238    /// **Note**: Message contents must be under 2000 unicode code points.
239    #[inline]
240    pub fn content(mut self, content: impl Into<String>) -> Self {
241        self.content = Some(content.into());
242        self
243    }
244
245    /// Adds an embed to the message.
246    ///
247    /// Calling this while editing a message will overwrite existing embeds.
248    pub fn add_embed(mut self, embed: CreateEmbed) -> Self {
249        self.embeds.get_or_insert_with(Vec::new).push(embed);
250        self
251    }
252
253    /// Adds multiple embeds for the message.
254    ///
255    /// Calling this while editing a message will overwrite existing embeds.
256    pub fn add_embeds(mut self, embeds: Vec<CreateEmbed>) -> Self {
257        self.embeds.get_or_insert_with(Vec::new).extend(embeds);
258        self
259    }
260
261    /// Sets a single embed to include in the message
262    ///
263    /// Calling this will overwrite the embed list. To append embeds, call [`Self::add_embed`]
264    /// instead.
265    pub fn embed(self, embed: CreateEmbed) -> Self {
266        self.embeds(vec![embed])
267    }
268
269    /// Sets a list of embeds to include in the message.
270    ///
271    /// Calling this will overwrite the embed list. To append embeds, call [`Self::add_embeds`]
272    /// instead.
273    pub fn embeds(mut self, embeds: Vec<CreateEmbed>) -> Self {
274        self.embeds = Some(embeds);
275        self
276    }
277
278    /// Set the allowed mentions for the message.
279    pub fn allowed_mentions(mut self, allowed_mentions: CreateAllowedMentions) -> Self {
280        self.allowed_mentions = Some(allowed_mentions);
281        self
282    }
283
284    /// Sets the flags for the message.
285    pub fn flags(mut self, flags: InteractionResponseFlags) -> Self {
286        self.flags = Some(flags);
287        self
288    }
289
290    /// Adds or removes the ephemeral flag.
291    pub fn ephemeral(mut self, ephemeral: bool) -> Self {
292        let mut flags = self.flags.unwrap_or_else(InteractionResponseFlags::empty);
293
294        if ephemeral {
295            flags |= InteractionResponseFlags::EPHEMERAL;
296        } else {
297            flags &= !InteractionResponseFlags::EPHEMERAL;
298        }
299
300        self.flags = Some(flags);
301        self
302    }
303
304    /// Sets the components of this message.
305    pub fn components(mut self, components: Vec<CreateActionRow>) -> Self {
306        self.components = Some(components);
307        self
308    }
309
310    /// Adds a poll to the message. Only one poll can be added per message.
311    ///
312    /// See [`CreatePoll`] for more information on creating and configuring a poll.
313    pub fn poll(mut self, poll: CreatePoll<Ready>) -> Self {
314        self.poll = Some(poll);
315        self
316    }
317
318    super::button_and_select_menu_convenience_methods!(self.components);
319}
320
321// Same as CommandOptionChoice according to Discord, see
322// [Autocomplete docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-autocomplete).
323#[must_use]
324#[derive(Clone, Debug, Deserialize, Serialize)]
325#[serde(transparent)]
326pub struct AutocompleteChoice(CommandOptionChoice);
327impl AutocompleteChoice {
328    pub fn new(name: impl Into<String>, value: impl Into<Value>) -> Self {
329        Self(CommandOptionChoice {
330            name: name.into(),
331            name_localizations: None,
332            value: value.into(),
333        })
334    }
335
336    pub fn add_localized_name(
337        mut self,
338        locale: impl Into<String>,
339        localized_name: impl Into<String>,
340    ) -> Self {
341        self.0
342            .name_localizations
343            .get_or_insert_with(Default::default)
344            .insert(locale.into(), localized_name.into());
345        self
346    }
347}
348
349impl<S: Into<String>> From<S> for AutocompleteChoice {
350    fn from(value: S) -> Self {
351        let value = value.into();
352        let name = value.clone();
353        Self::new(name, value)
354    }
355}
356
357/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-autocomplete)
358#[derive(Clone, Debug, Default, Serialize)]
359#[must_use]
360pub struct CreateAutocompleteResponse {
361    choices: Vec<AutocompleteChoice>,
362}
363
364impl CreateAutocompleteResponse {
365    /// Equivalent to [`Self::default`].
366    pub fn new() -> Self {
367        Self::default()
368    }
369
370    /// For autocomplete responses this sets their autocomplete suggestions.
371    ///
372    /// See the official docs on [`Application Command Option Choices`] for more information.
373    ///
374    /// [`Application Command Option Choices`]: https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-choice-structure
375    pub fn set_choices(mut self, choices: Vec<AutocompleteChoice>) -> Self {
376        self.choices = choices;
377        self
378    }
379
380    /// Add an int autocomplete choice.
381    ///
382    /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100
383    /// characters. Value must be between -2^53 and 2^53.
384    pub fn add_int_choice(self, name: impl Into<String>, value: i64) -> Self {
385        self.add_choice(AutocompleteChoice::new(name, value))
386    }
387
388    /// Adds a string autocomplete choice.
389    ///
390    /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100
391    /// characters. Value must be up to 100 characters.
392    pub fn add_string_choice(self, name: impl Into<String>, value: impl Into<String>) -> Self {
393        self.add_choice(AutocompleteChoice::new(name, value.into()))
394    }
395
396    /// Adds a number autocomplete choice.
397    ///
398    /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100
399    /// characters. Value must be between -2^53 and 2^53.
400    pub fn add_number_choice(self, name: impl Into<String>, value: f64) -> Self {
401        self.add_choice(AutocompleteChoice::new(name, value))
402    }
403
404    fn add_choice(mut self, value: AutocompleteChoice) -> Self {
405        self.choices.push(value);
406        self
407    }
408}
409
410#[cfg(feature = "http")]
411#[async_trait::async_trait]
412impl Builder for CreateAutocompleteResponse {
413    type Context<'ctx> = (InteractionId, &'ctx str);
414    type Built = ();
415
416    /// Creates a response to an autocomplete interaction.
417    ///
418    /// # Errors
419    ///
420    /// Returns an [`Error::Http`] if the API returns an error.
421    async fn execute(
422        self,
423        cache_http: impl CacheHttp,
424        ctx: Self::Context<'_>,
425    ) -> Result<Self::Built> {
426        cache_http.http().create_interaction_response(ctx.0, ctx.1, &self, Vec::new()).await
427    }
428}
429
430/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-modal).
431#[derive(Clone, Debug, Default, Serialize)]
432#[must_use]
433pub struct CreateModal {
434    components: Vec<CreateActionRow>,
435    custom_id: String,
436    title: String,
437}
438
439impl CreateModal {
440    /// Creates a new modal.
441    pub fn new(custom_id: impl Into<String>, title: impl Into<String>) -> Self {
442        Self {
443            components: Vec::new(),
444            custom_id: custom_id.into(),
445            title: title.into(),
446        }
447    }
448
449    /// Sets the components of this message.
450    ///
451    /// Overwrites existing components.
452    pub fn components(mut self, components: Vec<CreateActionRow>) -> Self {
453        self.components = components;
454        self
455    }
456}