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}
71
72#[cfg(feature = "model")]
73impl ComponentInteraction {
74    /// Gets the interaction response.
75    ///
76    /// # Errors
77    ///
78    /// Returns an [`Error::Http`] if there is no interaction response.
79    pub async fn get_response(&self, http: impl AsRef<Http>) -> Result<Message> {
80        http.as_ref().get_original_interaction_response(&self.token).await
81    }
82
83    /// Creates a response to the interaction received.
84    ///
85    /// **Note**: Message contents must be under 2000 unicode code points.
86    ///
87    /// # Errors
88    ///
89    /// Returns an [`Error::Model`] if the message content is too long. May also return an
90    /// [`Error::Http`] if the API returns an error, or an [`Error::Json`] if there is an error in
91    /// deserializing the API response.
92    pub async fn create_response(
93        &self,
94        cache_http: impl CacheHttp,
95        builder: CreateInteractionResponse,
96    ) -> Result<()> {
97        builder.execute(cache_http, (self.id, &self.token)).await
98    }
99
100    /// Edits the initial interaction response.
101    ///
102    /// **Note**: Message contents must be under 2000 unicode code points.
103    ///
104    /// # Errors
105    ///
106    /// Returns an [`Error::Model`] if the message content is too long. May also return an
107    /// [`Error::Http`] if the API returns an error, or an [`Error::Json`] if there is an error in
108    /// deserializing the API response.
109    pub async fn edit_response(
110        &self,
111        cache_http: impl CacheHttp,
112        builder: EditInteractionResponse,
113    ) -> Result<Message> {
114        builder.execute(cache_http, &self.token).await
115    }
116
117    /// Deletes the initial interaction response.
118    ///
119    /// Does not work on ephemeral messages.
120    ///
121    /// # Errors
122    ///
123    /// May return [`Error::Http`] if the API returns an error. Such as if the response was already
124    /// deleted.
125    pub async fn delete_response(&self, http: impl AsRef<Http>) -> Result<()> {
126        http.as_ref().delete_original_interaction_response(&self.token).await
127    }
128
129    /// Creates a followup response to the response sent.
130    ///
131    /// **Note**: Message contents must be under 2000 unicode code points.
132    ///
133    /// # Errors
134    ///
135    /// Returns [`Error::Model`] if the content is too long. May also return [`Error::Http`] if the
136    /// API returns an error, or [`Error::Json`] if there is an error in deserializing the
137    /// response.
138    pub async fn create_followup(
139        &self,
140        cache_http: impl CacheHttp,
141        builder: CreateInteractionResponseFollowup,
142    ) -> Result<Message> {
143        builder.execute(cache_http, (None, &self.token)).await
144    }
145
146    /// Edits a followup response to the response sent.
147    ///
148    /// **Note**: Message contents must be under 2000 unicode code points.
149    ///
150    /// # Errors
151    ///
152    /// Returns [`Error::Model`] if the content is too long. May also return [`Error::Http`] if the
153    /// API returns an error, or [`Error::Json`] if there is an error in deserializing the
154    /// response.
155    pub async fn edit_followup(
156        &self,
157        cache_http: impl CacheHttp,
158        message_id: impl Into<MessageId>,
159        builder: CreateInteractionResponseFollowup,
160    ) -> Result<Message> {
161        builder.execute(cache_http, (Some(message_id.into()), &self.token)).await
162    }
163
164    /// Deletes a followup message.
165    ///
166    /// # Errors
167    ///
168    /// May return [`Error::Http`] if the API returns an error. Such as if the response was already
169    /// deleted.
170    pub async fn delete_followup<M: Into<MessageId>>(
171        &self,
172        http: impl AsRef<Http>,
173        message_id: M,
174    ) -> Result<()> {
175        http.as_ref().delete_followup_message(&self.token, message_id.into()).await
176    }
177
178    /// Gets a followup message.
179    ///
180    /// # Errors
181    ///
182    /// May return [`Error::Http`] if the API returns an error. Such as if the response was
183    /// deleted.
184    pub async fn get_followup<M: Into<MessageId>>(
185        &self,
186        http: impl AsRef<Http>,
187        message_id: M,
188    ) -> Result<Message> {
189        http.as_ref().get_followup_message(&self.token, message_id.into()).await
190    }
191
192    /// Helper function to defer an interaction.
193    ///
194    /// # Errors
195    ///
196    /// Returns an [`Error::Http`] if the API returns an error, or an [`Error::Json`] if there is
197    /// an error in deserializing the API response.
198    pub async fn defer(&self, cache_http: impl CacheHttp) -> Result<()> {
199        self.create_response(cache_http, CreateInteractionResponse::Acknowledge).await
200    }
201
202    /// Helper function to defer an interaction ephemerally
203    ///
204    /// # Errors
205    ///
206    /// May also return an [`Error::Http`] if the API returns an error, or an [`Error::Json`] if
207    /// there is an error in deserializing the API response.
208    pub async fn defer_ephemeral(&self, cache_http: impl CacheHttp) -> Result<()> {
209        let builder = CreateInteractionResponse::Defer(
210            CreateInteractionResponseMessage::new().ephemeral(true),
211        );
212        self.create_response(cache_http, builder).await
213    }
214
215    /// See [`CreateQuickModal`].
216    ///
217    /// # Errors
218    ///
219    /// See [`CreateQuickModal::execute()`].
220    #[cfg(all(feature = "collector", feature = "utils"))]
221    pub async fn quick_modal(
222        &self,
223        ctx: &Context,
224        builder: CreateQuickModal,
225    ) -> Result<Option<QuickModalResponse>> {
226        builder.execute(ctx, self.id, &self.token).await
227    }
228}
229
230// Manual impl needed to insert guild_id into model data
231impl<'de> Deserialize<'de> for ComponentInteraction {
232    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
233        // calls #[serde(remote)]-generated inherent method
234        let mut interaction = Self::deserialize(deserializer)?;
235        if let (Some(guild_id), Some(member)) = (interaction.guild_id, &mut interaction.member) {
236            member.guild_id = guild_id;
237            // If `member` is present, `user` wasn't sent and is still filled with default data
238            interaction.user = member.user.clone();
239        }
240        Ok(interaction)
241    }
242}
243
244impl Serialize for ComponentInteraction {
245    fn serialize<S: serde::Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
246        // calls #[serde(remote)]-generated inherent method
247        Self::serialize(self, serializer)
248    }
249}
250
251#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
252#[derive(Clone, Debug)]
253pub enum ComponentInteractionDataKind {
254    Button,
255    StringSelect { values: Vec<String> },
256    UserSelect { values: Vec<UserId> },
257    RoleSelect { values: Vec<RoleId> },
258    MentionableSelect { values: Vec<GenericId> },
259    ChannelSelect { values: Vec<ChannelId> },
260    Unknown(u8),
261}
262
263// Manual impl needed to emulate integer enum tags
264impl<'de> Deserialize<'de> for ComponentInteractionDataKind {
265    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
266        #[derive(Deserialize)]
267        struct Json {
268            component_type: ComponentType,
269            values: Option<json::Value>,
270        }
271        let json = Json::deserialize(deserializer)?;
272
273        macro_rules! parse_values {
274            () => {
275                json::from_value(json.values.ok_or_else(|| D::Error::missing_field("values"))?)
276                    .map_err(D::Error::custom)?
277            };
278        }
279
280        Ok(match json.component_type {
281            ComponentType::Button => Self::Button,
282            ComponentType::StringSelect => Self::StringSelect {
283                values: parse_values!(),
284            },
285            ComponentType::UserSelect => Self::UserSelect {
286                values: parse_values!(),
287            },
288            ComponentType::RoleSelect => Self::RoleSelect {
289                values: parse_values!(),
290            },
291            ComponentType::MentionableSelect => Self::MentionableSelect {
292                values: parse_values!(),
293            },
294            ComponentType::ChannelSelect => Self::ChannelSelect {
295                values: parse_values!(),
296            },
297            ComponentType::Unknown(x) => Self::Unknown(x),
298            x @ (ComponentType::ActionRow | ComponentType::InputText) => {
299                return Err(D::Error::custom(format_args!(
300                    "invalid message component type in this context: {x:?}",
301                )));
302            },
303        })
304    }
305}
306
307impl Serialize for ComponentInteractionDataKind {
308    #[rustfmt::skip] // Remove this for horror.
309    fn serialize<S: serde::Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
310        let mut map = serializer.serialize_map(Some(2))?;
311        map.serialize_entry("component_type", &match self {
312            Self::Button { .. } => 2,
313            Self::StringSelect { .. } => 3,
314            Self::UserSelect { .. } => 5,
315            Self::RoleSelect { .. } => 6,
316            Self::MentionableSelect { .. } => 7,
317            Self::ChannelSelect { .. } => 8,
318            Self::Unknown(x) => *x,
319        })?;
320
321        match self {
322            Self::StringSelect { values } => map.serialize_entry("values", values)?,
323            Self::UserSelect { values } => map.serialize_entry("values", values)?,
324            Self::RoleSelect { values } => map.serialize_entry("values", values)?,
325            Self::MentionableSelect { values } => map.serialize_entry("values", values)?,
326            Self::ChannelSelect { values } => map.serialize_entry("values", values)?,
327            Self::Button | Self::Unknown(_) => map.serialize_entry("values", &None::<()>)?,
328        };
329
330        map.end()
331    }
332}
333
334/// A message component interaction data, provided by [`ComponentInteraction::data`]
335///
336/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-message-component-data-structure).
337#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
338#[derive(Clone, Debug, Deserialize, Serialize)]
339#[non_exhaustive]
340pub struct ComponentInteractionData {
341    /// The custom id of the component.
342    pub custom_id: String,
343    /// Type and type-specific data of this component interaction.
344    #[serde(flatten)]
345    pub kind: ComponentInteractionDataKind,
346}