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::Pong => map.serialize_entry("data", &None::<()>)?,
98            Self::Message(x) => map.serialize_entry("data", &x)?,
99            Self::Defer(x) => map.serialize_entry("data", &x)?,
100            Self::Acknowledge => map.serialize_entry("data", &None::<()>)?,
101            Self::UpdateMessage(x) => map.serialize_entry("data", &x)?,
102            Self::Autocomplete(x) => map.serialize_entry("data", &x)?,
103            Self::Modal(x) => map.serialize_entry("data", &x)?,
104            Self::PremiumRequired => map.serialize_entry("data", &None::<()>)?,
105            Self::LaunchActivity => map.serialize_entry("data", &None::<()>)?,
106        }
107
108        map.end()
109    }
110}
111
112impl CreateInteractionResponse {
113    #[cfg(feature = "http")]
114    fn check_length(&self) -> Result<()> {
115        if let CreateInteractionResponse::Message(data)
116        | CreateInteractionResponse::Defer(data)
117        | CreateInteractionResponse::UpdateMessage(data) = self
118        {
119            if let Some(content) = &data.content {
120                check_overflow(content.chars().count(), constants::MESSAGE_CODE_LIMIT)
121                    .map_err(|overflow| Error::Model(ModelError::MessageTooLong(overflow)))?;
122            }
123
124            if let Some(embeds) = &data.embeds {
125                check_overflow(embeds.len(), constants::EMBED_MAX_COUNT)
126                    .map_err(|_| Error::Model(ModelError::EmbedAmount))?;
127
128                for embed in embeds {
129                    embed.check_length()?;
130                }
131            }
132        }
133        Ok(())
134    }
135}
136
137#[cfg(feature = "http")]
138#[async_trait::async_trait]
139impl Builder for CreateInteractionResponse {
140    type Context<'ctx> = (InteractionId, &'ctx str);
141    type Built = ();
142
143    /// Creates a response to the interaction received.
144    ///
145    /// **Note**: Message contents must be under 2000 unicode code points, and embeds must be under
146    /// 6000 code points.
147    ///
148    /// # Errors
149    ///
150    /// Returns an [`Error::Model`] if the message content is too long. May also return an
151    /// [`Error::Http`] if the API returns an error, or an [`Error::Json`] if there is an error in
152    /// deserializing the API response.
153    async fn execute(
154        mut self,
155        cache_http: impl CacheHttp,
156        ctx: Self::Context<'_>,
157    ) -> Result<Self::Built> {
158        self.check_length()?;
159        let files = match &mut self {
160            CreateInteractionResponse::Message(msg)
161            | CreateInteractionResponse::Defer(msg)
162            | CreateInteractionResponse::UpdateMessage(msg) => msg.attachments.take_files(),
163            _ => Vec::new(),
164        };
165
166        let http = cache_http.http();
167        if let Self::Message(msg) | Self::Defer(msg) | Self::UpdateMessage(msg) = &mut self {
168            if msg.allowed_mentions.is_none() {
169                msg.allowed_mentions.clone_from(&http.default_allowed_mentions);
170            }
171        };
172
173        http.create_interaction_response(ctx.0, ctx.1, &self, files).await
174    }
175}
176
177/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-messages).
178#[derive(Clone, Debug, Default, Serialize)]
179#[must_use]
180pub struct CreateInteractionResponseMessage {
181    #[serde(skip_serializing_if = "Option::is_none")]
182    tts: Option<bool>,
183    #[serde(skip_serializing_if = "Option::is_none")]
184    content: Option<String>,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    embeds: Option<Vec<CreateEmbed>>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    allowed_mentions: Option<CreateAllowedMentions>,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    flags: Option<InteractionResponseFlags>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    components: Option<Vec<CreateActionRow>>,
193    #[serde(skip_serializing_if = "Option::is_none")]
194    poll: Option<CreatePoll<Ready>>,
195    attachments: EditAttachments,
196}
197
198impl CreateInteractionResponseMessage {
199    /// Equivalent to [`Self::default`].
200    pub fn new() -> Self {
201        Self::default()
202    }
203
204    /// Set whether the message is text-to-speech.
205    ///
206    /// Think carefully before setting this to `true`.
207    ///
208    /// Defaults to `false`.
209    pub fn tts(mut self, tts: bool) -> Self {
210        self.tts = Some(tts);
211        self
212    }
213
214    /// Appends a file to the message.
215    pub fn add_file(mut self, file: CreateAttachment) -> Self {
216        self.attachments = self.attachments.add(file);
217        self
218    }
219
220    /// Appends a list of files to the message.
221    pub fn add_files(mut self, files: impl IntoIterator<Item = CreateAttachment>) -> Self {
222        for file in files {
223            self.attachments = self.attachments.add(file);
224        }
225        self
226    }
227
228    /// Sets a list of files to include in the message.
229    ///
230    /// Calling this multiple times will overwrite the file list. To append files, call
231    /// [`Self::add_file`] or [`Self::add_files`] instead.
232    pub fn files(mut self, files: impl IntoIterator<Item = CreateAttachment>) -> Self {
233        self.attachments = EditAttachments::new();
234        self.add_files(files)
235    }
236
237    /// Set the content of the message.
238    ///
239    /// **Note**: Message contents must be under 2000 unicode code points.
240    #[inline]
241    pub fn content(mut self, content: impl Into<String>) -> Self {
242        self.content = Some(content.into());
243        self
244    }
245
246    /// Adds an embed to the message.
247    ///
248    /// Calling this while editing a message will overwrite existing embeds.
249    pub fn add_embed(mut self, embed: CreateEmbed) -> Self {
250        self.embeds.get_or_insert_with(Vec::new).push(embed);
251        self
252    }
253
254    /// Adds multiple embeds for the message.
255    ///
256    /// Calling this while editing a message will overwrite existing embeds.
257    pub fn add_embeds(mut self, embeds: Vec<CreateEmbed>) -> Self {
258        self.embeds.get_or_insert_with(Vec::new).extend(embeds);
259        self
260    }
261
262    /// Sets a single embed to include in the message
263    ///
264    /// Calling this will overwrite the embed list. To append embeds, call [`Self::add_embed`]
265    /// instead.
266    pub fn embed(self, embed: CreateEmbed) -> Self {
267        self.embeds(vec![embed])
268    }
269
270    /// Sets a list of embeds to include in the message.
271    ///
272    /// Calling this will overwrite the embed list. To append embeds, call [`Self::add_embeds`]
273    /// instead.
274    pub fn embeds(mut self, embeds: Vec<CreateEmbed>) -> Self {
275        self.embeds = Some(embeds);
276        self
277    }
278
279    /// Set the allowed mentions for the message.
280    pub fn allowed_mentions(mut self, allowed_mentions: CreateAllowedMentions) -> Self {
281        self.allowed_mentions = Some(allowed_mentions);
282        self
283    }
284
285    /// Sets the flags for the message.
286    pub fn flags(mut self, flags: InteractionResponseFlags) -> Self {
287        self.flags = Some(flags);
288        self
289    }
290
291    /// Adds or removes the ephemeral flag.
292    pub fn ephemeral(mut self, ephemeral: bool) -> Self {
293        let mut flags = self.flags.unwrap_or_else(InteractionResponseFlags::empty);
294
295        if ephemeral {
296            flags |= InteractionResponseFlags::EPHEMERAL;
297        } else {
298            flags &= !InteractionResponseFlags::EPHEMERAL;
299        };
300
301        self.flags = Some(flags);
302        self
303    }
304
305    /// Sets the components of this message.
306    pub fn components(mut self, components: Vec<CreateActionRow>) -> Self {
307        self.components = Some(components);
308        self
309    }
310
311    /// Adds a poll to the message. Only one poll can be added per message.
312    ///
313    /// See [`CreatePoll`] for more information on creating and configuring a poll.
314    pub fn poll(mut self, poll: CreatePoll<Ready>) -> Self {
315        self.poll = Some(poll);
316        self
317    }
318
319    super::button_and_select_menu_convenience_methods!(self.components);
320}
321
322// Same as CommandOptionChoice according to Discord, see
323// [Autocomplete docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-autocomplete).
324#[must_use]
325#[derive(Clone, Debug, Deserialize, Serialize)]
326#[serde(transparent)]
327pub struct AutocompleteChoice(CommandOptionChoice);
328impl AutocompleteChoice {
329    pub fn new(name: impl Into<String>, value: impl Into<Value>) -> Self {
330        Self(CommandOptionChoice {
331            name: name.into(),
332            name_localizations: None,
333            value: value.into(),
334        })
335    }
336
337    pub fn add_localized_name(
338        mut self,
339        locale: impl Into<String>,
340        localized_name: impl Into<String>,
341    ) -> Self {
342        self.0
343            .name_localizations
344            .get_or_insert_with(Default::default)
345            .insert(locale.into(), localized_name.into());
346        self
347    }
348}
349
350impl<S: Into<String>> From<S> for AutocompleteChoice {
351    fn from(value: S) -> Self {
352        let value = value.into();
353        let name = value.clone();
354        Self::new(name, value)
355    }
356}
357
358/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-autocomplete)
359#[derive(Clone, Debug, Default, Serialize)]
360#[must_use]
361pub struct CreateAutocompleteResponse {
362    choices: Vec<AutocompleteChoice>,
363}
364
365impl CreateAutocompleteResponse {
366    /// Equivalent to [`Self::default`].
367    pub fn new() -> Self {
368        Self::default()
369    }
370
371    /// For autocomplete responses this sets their autocomplete suggestions.
372    ///
373    /// See the official docs on [`Application Command Option Choices`] for more information.
374    ///
375    /// [`Application Command Option Choices`]: https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-choice-structure
376    pub fn set_choices(mut self, choices: Vec<AutocompleteChoice>) -> Self {
377        self.choices = choices;
378        self
379    }
380
381    /// Add an int autocomplete choice.
382    ///
383    /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100
384    /// characters. Value must be between -2^53 and 2^53.
385    pub fn add_int_choice(self, name: impl Into<String>, value: i64) -> Self {
386        self.add_choice(AutocompleteChoice::new(name, value))
387    }
388
389    /// Adds a string autocomplete choice.
390    ///
391    /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100
392    /// characters. Value must be up to 100 characters.
393    pub fn add_string_choice(self, name: impl Into<String>, value: impl Into<String>) -> Self {
394        self.add_choice(AutocompleteChoice::new(name, value.into()))
395    }
396
397    /// Adds a number autocomplete choice.
398    ///
399    /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100
400    /// characters. Value must be between -2^53 and 2^53.
401    pub fn add_number_choice(self, name: impl Into<String>, value: f64) -> Self {
402        self.add_choice(AutocompleteChoice::new(name, value))
403    }
404
405    fn add_choice(mut self, value: AutocompleteChoice) -> Self {
406        self.choices.push(value);
407        self
408    }
409}
410
411#[cfg(feature = "http")]
412#[async_trait::async_trait]
413impl Builder for CreateAutocompleteResponse {
414    type Context<'ctx> = (InteractionId, &'ctx str);
415    type Built = ();
416
417    /// Creates a response to an autocomplete interaction.
418    ///
419    /// # Errors
420    ///
421    /// Returns an [`Error::Http`] if the API returns an error.
422    async fn execute(
423        self,
424        cache_http: impl CacheHttp,
425        ctx: Self::Context<'_>,
426    ) -> Result<Self::Built> {
427        cache_http.http().create_interaction_response(ctx.0, ctx.1, &self, Vec::new()).await
428    }
429}
430
431/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-modal).
432#[derive(Clone, Debug, Default, Serialize)]
433#[must_use]
434pub struct CreateModal {
435    components: Vec<CreateActionRow>,
436    custom_id: String,
437    title: String,
438}
439
440impl CreateModal {
441    /// Creates a new modal.
442    pub fn new(custom_id: impl Into<String>, title: impl Into<String>) -> Self {
443        Self {
444            components: Vec::new(),
445            custom_id: custom_id.into(),
446            title: title.into(),
447        }
448    }
449
450    /// Sets the components of this message.
451    ///
452    /// Overwrites existing components.
453    pub fn components(mut self, components: Vec<CreateActionRow>) -> Self {
454        self.components = components;
455        self
456    }
457}