Skip to main content

serenity/model/application/
component_interaction.rs

1use serde::de::Error as DeError;
2use serde::ser::{Serialize, SerializeMap as _};
3
4#[cfg(feature = "model")]
5use crate::builder::{
6    Builder,
7    CreateInteractionResponse,
8    CreateInteractionResponseFollowup,
9    CreateInteractionResponseMessage,
10    EditInteractionResponse,
11};
12#[cfg(feature = "collector")]
13use crate::client::Context;
14#[cfg(feature = "model")]
15use crate::http::{CacheHttp, Http};
16use crate::internal::prelude::*;
17use crate::json;
18use crate::model::prelude::*;
19#[cfg(all(feature = "collector", feature = "utils"))]
20use crate::utils::{CreateQuickModal, QuickModalResponse};
21
22/// An interaction triggered by a message component.
23///
24/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure).
25#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
26#[derive(Clone, Debug, Deserialize, Serialize)]
27#[serde(remote = "Self")]
28#[non_exhaustive]
29pub struct ComponentInteraction {
30    /// Id of the interaction.
31    pub id: InteractionId,
32    /// Id of the application this interaction is for.
33    pub application_id: ApplicationId,
34    /// The data of the interaction which was triggered.
35    pub data: ComponentInteractionData,
36    /// The guild Id this interaction was sent from, if there is one.
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub guild_id: Option<GuildId>,
39    /// Channel that the interaction was sent from.
40    pub channel: Option<PartialChannel>,
41    /// The channel Id this interaction was sent from.
42    pub channel_id: ChannelId,
43    /// The `member` data for the invoking user.
44    ///
45    /// **Note**: It is only present if the interaction is triggered in a guild.
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub member: Option<Member>,
48    /// The `user` object for the invoking user.
49    #[serde(default)]
50    pub user: User,
51    /// A continuation token for responding to the interaction.
52    pub token: String,
53    /// Always `1`.
54    pub version: u8,
55    /// The message this interaction was triggered by, if it is a component.
56    pub message: Box<Message>,
57    /// Permissions the app or bot has within the channel the interaction was sent from.
58    pub app_permissions: Option<Permissions>,
59    /// The selected language of the invoking user.
60    pub locale: String,
61    /// The guild's preferred locale.
62    pub guild_locale: Option<String>,
63    /// For monetized applications, any entitlements of the invoking user.
64    pub entitlements: Vec<Entitlement>,
65    /// The owners of the applications that authorized the interaction, such as a guild or user.
66    #[serde(default)]
67    pub authorizing_integration_owners: AuthorizingIntegrationOwners,
68    /// The context where the interaction was triggered from.
69    pub context: Option<InteractionContext>,
70    /// Attachment size limit in bytes.
71    pub attachment_size_limit: u32,
72}
73
74#[cfg(feature = "model")]
75impl ComponentInteraction {
76    /// Gets the interaction response.
77    ///
78    /// # Errors
79    ///
80    /// Returns an [`Error::Http`] if there is no interaction response.
81    pub async fn get_response(&self, http: impl AsRef<Http>) -> Result<Message> {
82        http.as_ref().get_original_interaction_response(&self.token).await
83    }
84
85    /// Creates a response to the interaction received.
86    ///
87    /// **Note**: Message contents must be under 2000 unicode code points.
88    ///
89    /// # Errors
90    ///
91    /// Returns an [`Error::Model`] if the message content is too long. May also return an
92    /// [`Error::Http`] if the API returns an error, or an [`Error::Json`] if there is an error in
93    /// deserializing the API response.
94    pub async fn create_response(
95        &self,
96        cache_http: impl CacheHttp,
97        builder: CreateInteractionResponse,
98    ) -> Result<()> {
99        builder.execute(cache_http, (self.id, &self.token)).await
100    }
101
102    /// Edits the initial interaction response.
103    ///
104    /// **Note**: Message contents must be under 2000 unicode code points.
105    ///
106    /// # Errors
107    ///
108    /// Returns an [`Error::Model`] if the message content is too long. May also return an
109    /// [`Error::Http`] if the API returns an error, or an [`Error::Json`] if there is an error in
110    /// deserializing the API response.
111    pub async fn edit_response(
112        &self,
113        cache_http: impl CacheHttp,
114        builder: EditInteractionResponse,
115    ) -> Result<Message> {
116        builder.execute(cache_http, &self.token).await
117    }
118
119    /// Deletes the initial interaction response.
120    ///
121    /// Does not work on ephemeral messages.
122    ///
123    /// # Errors
124    ///
125    /// May return [`Error::Http`] if the API returns an error. Such as if the response was already
126    /// deleted.
127    pub async fn delete_response(&self, http: impl AsRef<Http>) -> Result<()> {
128        http.as_ref().delete_original_interaction_response(&self.token).await
129    }
130
131    /// Creates a followup response to the response sent.
132    ///
133    /// **Note**: Message contents must be under 2000 unicode code points.
134    ///
135    /// # Errors
136    ///
137    /// Returns [`Error::Model`] if the content is too long. May also return [`Error::Http`] if the
138    /// API returns an error, or [`Error::Json`] if there is an error in deserializing the
139    /// response.
140    pub async fn create_followup(
141        &self,
142        cache_http: impl CacheHttp,
143        builder: CreateInteractionResponseFollowup,
144    ) -> Result<Message> {
145        builder.execute(cache_http, (None, &self.token)).await
146    }
147
148    /// Edits a followup response to the response sent.
149    ///
150    /// **Note**: Message contents must be under 2000 unicode code points.
151    ///
152    /// # Errors
153    ///
154    /// Returns [`Error::Model`] if the content is too long. May also return [`Error::Http`] if the
155    /// API returns an error, or [`Error::Json`] if there is an error in deserializing the
156    /// response.
157    pub async fn edit_followup(
158        &self,
159        cache_http: impl CacheHttp,
160        message_id: impl Into<MessageId>,
161        builder: CreateInteractionResponseFollowup,
162    ) -> Result<Message> {
163        builder.execute(cache_http, (Some(message_id.into()), &self.token)).await
164    }
165
166    /// Deletes a followup message.
167    ///
168    /// # Errors
169    ///
170    /// May return [`Error::Http`] if the API returns an error. Such as if the response was already
171    /// deleted.
172    pub async fn delete_followup<M: Into<MessageId>>(
173        &self,
174        http: impl AsRef<Http>,
175        message_id: M,
176    ) -> Result<()> {
177        http.as_ref().delete_followup_message(&self.token, message_id.into()).await
178    }
179
180    /// Gets a followup message.
181    ///
182    /// # Errors
183    ///
184    /// May return [`Error::Http`] if the API returns an error. Such as if the response was
185    /// deleted.
186    pub async fn get_followup<M: Into<MessageId>>(
187        &self,
188        http: impl AsRef<Http>,
189        message_id: M,
190    ) -> Result<Message> {
191        http.as_ref().get_followup_message(&self.token, message_id.into()).await
192    }
193
194    /// Helper function to defer an interaction.
195    ///
196    /// # Errors
197    ///
198    /// Returns an [`Error::Http`] if the API returns an error, or an [`Error::Json`] if there is
199    /// an error in deserializing the API response.
200    pub async fn defer(&self, cache_http: impl CacheHttp) -> Result<()> {
201        self.create_response(cache_http, CreateInteractionResponse::Acknowledge).await
202    }
203
204    /// Helper function to defer an interaction ephemerally
205    ///
206    /// # Errors
207    ///
208    /// May also return an [`Error::Http`] if the API returns an error, or an [`Error::Json`] if
209    /// there is an error in deserializing the API response.
210    pub async fn defer_ephemeral(&self, cache_http: impl CacheHttp) -> Result<()> {
211        let builder = CreateInteractionResponse::Defer(
212            CreateInteractionResponseMessage::new().ephemeral(true),
213        );
214        self.create_response(cache_http, builder).await
215    }
216
217    /// See [`CreateQuickModal`].
218    ///
219    /// # Errors
220    ///
221    /// See [`CreateQuickModal::execute()`].
222    #[cfg(all(feature = "collector", feature = "utils"))]
223    pub async fn quick_modal(
224        &self,
225        ctx: &Context,
226        builder: CreateQuickModal,
227    ) -> Result<Option<QuickModalResponse>> {
228        builder.execute(ctx, self.id, &self.token).await
229    }
230}
231
232// Manual impl needed to insert guild_id into model data
233impl<'de> Deserialize<'de> for ComponentInteraction {
234    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
235        // calls #[serde(remote)]-generated inherent method
236        let mut interaction = Self::deserialize(deserializer)?;
237        if let (Some(guild_id), Some(member)) = (interaction.guild_id, &mut interaction.member) {
238            member.guild_id = guild_id;
239            // If `member` is present, `user` wasn't sent and is still filled with default data
240            interaction.user = member.user.clone();
241        }
242        Ok(interaction)
243    }
244}
245
246impl Serialize for ComponentInteraction {
247    fn serialize<S: serde::Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
248        // calls #[serde(remote)]-generated inherent method
249        Self::serialize(self, serializer)
250    }
251}
252
253#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
254#[derive(Clone, Debug)]
255pub enum ComponentInteractionDataKind {
256    Button,
257    StringSelect { values: Vec<String> },
258    UserSelect { values: Vec<UserId> },
259    RoleSelect { values: Vec<RoleId> },
260    MentionableSelect { values: Vec<GenericId> },
261    ChannelSelect { values: Vec<ChannelId> },
262    Unknown(u8),
263}
264
265// Manual impl needed to emulate integer enum tags
266impl<'de> Deserialize<'de> for ComponentInteractionDataKind {
267    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
268        #[derive(Deserialize)]
269        struct Json {
270            component_type: ComponentType,
271            values: Option<json::Value>,
272        }
273        let json = Json::deserialize(deserializer)?;
274
275        macro_rules! parse_values {
276            () => {
277                json::from_value(json.values.ok_or_else(|| D::Error::missing_field("values"))?)
278                    .map_err(D::Error::custom)?
279            };
280        }
281
282        Ok(match json.component_type {
283            ComponentType::Button => Self::Button,
284            ComponentType::StringSelect => Self::StringSelect {
285                values: parse_values!(),
286            },
287            ComponentType::UserSelect => Self::UserSelect {
288                values: parse_values!(),
289            },
290            ComponentType::RoleSelect => Self::RoleSelect {
291                values: parse_values!(),
292            },
293            ComponentType::MentionableSelect => Self::MentionableSelect {
294                values: parse_values!(),
295            },
296            ComponentType::ChannelSelect => Self::ChannelSelect {
297                values: parse_values!(),
298            },
299            ComponentType::Unknown(x) => Self::Unknown(x),
300            x @ (ComponentType::ActionRow | ComponentType::InputText) => {
301                return Err(D::Error::custom(format_args!(
302                    "invalid message component type in this context: {x:?}",
303                )));
304            },
305        })
306    }
307}
308
309impl Serialize for ComponentInteractionDataKind {
310    #[rustfmt::skip] // Remove this for horror.
311    fn serialize<S: serde::Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
312        let mut map = serializer.serialize_map(Some(2))?;
313        map.serialize_entry("component_type", &match self {
314            Self::Button { .. } => 2,
315            Self::StringSelect { .. } => 3,
316            Self::UserSelect { .. } => 5,
317            Self::RoleSelect { .. } => 6,
318            Self::MentionableSelect { .. } => 7,
319            Self::ChannelSelect { .. } => 8,
320            Self::Unknown(x) => *x,
321        })?;
322
323        match self {
324            Self::StringSelect { values } => map.serialize_entry("values", values)?,
325            Self::UserSelect { values } => map.serialize_entry("values", values)?,
326            Self::RoleSelect { values } => map.serialize_entry("values", values)?,
327            Self::MentionableSelect { values } => map.serialize_entry("values", values)?,
328            Self::ChannelSelect { values } => map.serialize_entry("values", values)?,
329            Self::Button | Self::Unknown(_) => map.serialize_entry("values", &None::<()>)?,
330        }
331
332        map.end()
333    }
334}
335
336/// A message component interaction data, provided by [`ComponentInteraction::data`]
337///
338/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-message-component-data-structure).
339#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
340#[derive(Clone, Debug, Deserialize, Serialize)]
341#[non_exhaustive]
342pub struct ComponentInteractionData {
343    /// The custom id of the component.
344    pub custom_id: String,
345    /// Type and type-specific data of this component interaction.
346    #[serde(flatten)]
347    pub kind: ComponentInteractionDataKind,
348    /// The parameters and the given values. The converted objects from the given options.
349    #[serde(default)]
350    pub resolved: CommandDataResolved,
351}