serenity/model/application/
interaction.rs

1use serde::de::{Deserialize, Deserializer, Error as DeError};
2use serde::ser::{Serialize, Serializer};
3
4use super::{
5    CommandInteraction,
6    ComponentInteraction,
7    InstallationContext,
8    ModalInteraction,
9    PingInteraction,
10};
11use crate::internal::prelude::*;
12use crate::json::from_value;
13use crate::model::guild::PartialMember;
14use crate::model::id::{ApplicationId, GuildId, InteractionId, MessageId, UserId};
15use crate::model::monetization::Entitlement;
16use crate::model::user::User;
17use crate::model::utils::{deserialize_val, remove_from_map, StrOrInt};
18use crate::model::Permissions;
19
20/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object)
21#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
22#[derive(Clone, Debug)]
23#[non_exhaustive]
24pub enum Interaction {
25    Ping(PingInteraction),
26    Command(CommandInteraction),
27    Autocomplete(CommandInteraction),
28    Component(ComponentInteraction),
29    Modal(ModalInteraction),
30}
31
32impl Interaction {
33    /// Gets the interaction Id.
34    #[must_use]
35    pub fn id(&self) -> InteractionId {
36        match self {
37            Self::Ping(i) => i.id,
38            Self::Command(i) | Self::Autocomplete(i) => i.id,
39            Self::Component(i) => i.id,
40            Self::Modal(i) => i.id,
41        }
42    }
43
44    /// Gets the interaction type
45    #[must_use]
46    pub fn kind(&self) -> InteractionType {
47        match self {
48            Self::Ping(_) => InteractionType::Ping,
49            Self::Command(_) => InteractionType::Command,
50            Self::Component(_) => InteractionType::Component,
51            Self::Autocomplete(_) => InteractionType::Autocomplete,
52            Self::Modal(_) => InteractionType::Modal,
53        }
54    }
55
56    /// Permissions the app or bot has within the channel the interaction was sent from.
57    #[must_use]
58    pub fn app_permissions(&self) -> Option<Permissions> {
59        match self {
60            Self::Ping(_) => None,
61            Self::Command(i) | Self::Autocomplete(i) => i.app_permissions,
62            Self::Component(i) => i.app_permissions,
63            Self::Modal(i) => i.app_permissions,
64        }
65    }
66
67    /// Gets the interaction application Id
68    #[must_use]
69    pub fn application_id(&self) -> ApplicationId {
70        match self {
71            Self::Ping(i) => i.application_id,
72            Self::Command(i) | Self::Autocomplete(i) => i.application_id,
73            Self::Component(i) => i.application_id,
74            Self::Modal(i) => i.application_id,
75        }
76    }
77
78    /// Gets the interaction token.
79    #[must_use]
80    pub fn token(&self) -> &str {
81        match self {
82            Self::Ping(i) => i.token.as_str(),
83            Self::Command(i) | Self::Autocomplete(i) => i.token.as_str(),
84            Self::Component(i) => i.token.as_str(),
85            Self::Modal(i) => i.token.as_str(),
86        }
87    }
88
89    /// Gets the invoked guild locale.
90    #[must_use]
91    pub fn guild_locale(&self) -> Option<&str> {
92        match self {
93            Self::Ping(_) => None,
94            Self::Command(i) | Self::Autocomplete(i) => i.guild_locale.as_deref(),
95            Self::Component(i) => i.guild_locale.as_deref(),
96            Self::Modal(i) => i.guild_locale.as_deref(),
97        }
98    }
99
100    /// For monetized applications, gets the invoking user's granted entitlements.
101    #[must_use]
102    pub fn entitlements(&self) -> Option<&[Entitlement]> {
103        match self {
104            Self::Ping(_) => None,
105            Self::Command(i) | Self::Autocomplete(i) => Some(&i.entitlements),
106            Self::Component(i) => Some(&i.entitlements),
107            Self::Modal(i) => Some(&i.entitlements),
108        }
109    }
110
111    /// Converts this to a [`PingInteraction`]
112    #[must_use]
113    pub fn ping(self) -> Option<PingInteraction> {
114        match self {
115            Self::Ping(i) => Some(i),
116            _ => None,
117        }
118    }
119
120    /// Converts this to a [`PingInteraction`]
121    #[must_use]
122    pub fn as_ping(&self) -> Option<&PingInteraction> {
123        match self {
124            Self::Ping(i) => Some(i),
125            _ => None,
126        }
127    }
128
129    /// Converts this to a [`PingInteraction`]
130    #[must_use]
131    pub fn into_ping(self) -> Option<PingInteraction> {
132        self.ping()
133    }
134
135    /// Converts this to an [`CommandInteraction`]
136    #[must_use]
137    pub fn command(self) -> Option<CommandInteraction> {
138        match self {
139            Self::Command(i) => Some(i),
140            _ => None,
141        }
142    }
143
144    /// Converts this to an [`CommandInteraction`]
145    #[must_use]
146    pub fn as_command(&self) -> Option<&CommandInteraction> {
147        match self {
148            Self::Command(i) => Some(i),
149            _ => None,
150        }
151    }
152
153    /// Converts this to an [`CommandInteraction`]
154    #[must_use]
155    pub fn into_command(self) -> Option<CommandInteraction> {
156        self.command()
157    }
158
159    /// Converts this to a [`ComponentInteraction`]
160    #[must_use]
161    pub fn message_component(self) -> Option<ComponentInteraction> {
162        match self {
163            Self::Component(i) => Some(i),
164            _ => None,
165        }
166    }
167
168    /// Converts this to a [`ComponentInteraction`]
169    #[must_use]
170    pub fn as_message_component(&self) -> Option<&ComponentInteraction> {
171        match self {
172            Self::Component(i) => Some(i),
173            _ => None,
174        }
175    }
176
177    /// Converts this to a [`ComponentInteraction`]
178    #[must_use]
179    pub fn into_message_component(self) -> Option<ComponentInteraction> {
180        self.message_component()
181    }
182
183    /// Converts this to a [`CommandInteraction`]
184    #[must_use]
185    pub fn autocomplete(self) -> Option<CommandInteraction> {
186        match self {
187            Self::Autocomplete(i) => Some(i),
188            _ => None,
189        }
190    }
191
192    /// Converts this to a [`CommandInteraction`]
193    #[must_use]
194    pub fn as_autocomplete(&self) -> Option<&CommandInteraction> {
195        match self {
196            Self::Autocomplete(i) => Some(i),
197            _ => None,
198        }
199    }
200
201    /// Converts this to a [`CommandInteraction`]
202    #[must_use]
203    pub fn into_autocomplete(self) -> Option<CommandInteraction> {
204        self.autocomplete()
205    }
206
207    /// Converts this to a [`ModalInteraction`]
208    #[must_use]
209    pub fn modal_submit(self) -> Option<ModalInteraction> {
210        match self {
211            Self::Modal(i) => Some(i),
212            _ => None,
213        }
214    }
215
216    /// Converts this to a [`ModalInteraction`]
217    #[must_use]
218    pub fn as_modal_submit(&self) -> Option<&ModalInteraction> {
219        match self {
220            Self::Modal(i) => Some(i),
221            _ => None,
222        }
223    }
224
225    /// Converts this to a [`ModalInteraction`]
226    #[must_use]
227    pub fn into_modal_submit(self) -> Option<ModalInteraction> {
228        self.modal_submit()
229    }
230}
231
232// Manual impl needed to emulate integer enum tags
233impl<'de> Deserialize<'de> for Interaction {
234    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
235        let map = JsonMap::deserialize(deserializer)?;
236
237        let raw_kind = map.get("type").ok_or_else(|| DeError::missing_field("type"))?.clone();
238        let value = Value::from(map);
239
240        match deserialize_val(raw_kind)? {
241            InteractionType::Command => from_value(value).map(Interaction::Command),
242            InteractionType::Component => from_value(value).map(Interaction::Component),
243            InteractionType::Autocomplete => from_value(value).map(Interaction::Autocomplete),
244            InteractionType::Modal => from_value(value).map(Interaction::Modal),
245            InteractionType::Ping => from_value(value).map(Interaction::Ping),
246            InteractionType::Unknown(_) => return Err(DeError::custom("Unknown interaction type")),
247        }
248        .map_err(DeError::custom)
249    }
250}
251
252impl Serialize for Interaction {
253    fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
254        match self {
255            Self::Ping(i) => i.serialize(serializer),
256            Self::Command(i) | Self::Autocomplete(i) => i.serialize(serializer),
257            Self::Component(i) => i.serialize(serializer),
258            Self::Modal(i) => i.serialize(serializer),
259        }
260    }
261}
262
263enum_number! {
264    /// The type of an Interaction.
265    ///
266    /// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-type).
267    #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
268    #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
269    #[serde(from = "u8", into = "u8")]
270    #[non_exhaustive]
271    pub enum InteractionType {
272        Ping = 1,
273        Command = 2,
274        Component = 3,
275        Autocomplete = 4,
276        Modal = 5,
277        _ => Unknown(u8),
278    }
279}
280
281bitflags! {
282    /// The flags for an interaction response message.
283    ///
284    /// [Discord docs](https://discord.com/developers/docs/resources/channel#message-object-message-flags)
285    /// ([only some are valid in this context](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-response-object-messages))
286    #[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
287    pub struct InteractionResponseFlags: u64 {
288        /// Do not include any embeds when serializing this message.
289        const SUPPRESS_EMBEDS = 1 << 2;
290        /// Interaction message will only be visible to sender and will
291        /// be quickly deleted.
292        const EPHEMERAL = 1 << 6;
293        /// Does not trigger push notifications or desktop notifications.
294        const SUPPRESS_NOTIFICATIONS = 1 << 12;
295    }
296}
297
298/// A cleaned up enum for determining the authorizing owner for an [`Interaction`].
299///
300/// [Discord Docs](https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-authorizing-integration-owners-object)
301#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
302#[derive(Clone, Debug)]
303#[non_exhaustive]
304pub enum AuthorizingIntegrationOwner {
305    /// The [`Application`] was installed to a guild, containing the id if invoked in said guild.
306    ///
307    /// [`Application`]: super::CurrentApplicationInfo
308    GuildInstall(Option<GuildId>),
309    /// The [`Application`] was installed to a user, containing the id of said user.
310    ///
311    /// [`Application`]: super::CurrentApplicationInfo
312    UserInstall(UserId),
313    Unknown(InstallationContext),
314}
315
316#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
317#[derive(Clone, Debug, Default)]
318#[repr(transparent)]
319pub struct AuthorizingIntegrationOwners(pub Vec<AuthorizingIntegrationOwner>);
320
321impl<'de> serde::Deserialize<'de> for AuthorizingIntegrationOwners {
322    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
323        struct Visitor;
324
325        impl<'de> serde::de::Visitor<'de> for Visitor {
326            type Value = AuthorizingIntegrationOwners;
327
328            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329                formatter.write_str("a hashmap containing keys of InstallationContext and values based on those keys")
330            }
331
332            fn visit_map<A>(self, mut map: A) -> StdResult<Self::Value, A::Error>
333            where
334                A: serde::de::MapAccess<'de>,
335            {
336                let mut out = Vec::new();
337                while let Some(key_str) = map.next_key::<serde_cow::CowStr<'_>>()? {
338                    let key_int = key_str.0.parse::<u8>().map_err(serde::de::Error::custom)?;
339                    let value = match InstallationContext::from(key_int) {
340                        InstallationContext::Guild => {
341                            // GuildId here can be `0`, which signals the command is guild installed
342                            // but invoked in a DM, we have to do this fun deserialisation dance.
343                            let id_str = map.next_value::<StrOrInt<'_>>()?;
344                            let id_int = id_str.parse().map_err(A::Error::custom)?;
345                            let id = std::num::NonZeroU64::new(id_int).map(GuildId::from);
346
347                            AuthorizingIntegrationOwner::GuildInstall(id)
348                        },
349                        InstallationContext::User => {
350                            AuthorizingIntegrationOwner::UserInstall(map.next_value()?)
351                        },
352                        key => AuthorizingIntegrationOwner::Unknown(key),
353                    };
354
355                    out.push(value);
356                }
357
358                Ok(AuthorizingIntegrationOwners(out))
359            }
360        }
361
362        deserializer.deserialize_map(Visitor)
363    }
364}
365
366impl serde::Serialize for AuthorizingIntegrationOwners {
367    fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
368        use serde::ser::SerializeMap;
369
370        let mut serializer = serializer.serialize_map(Some(self.0.len()))?;
371        for value in &self.0 {
372            match value {
373                AuthorizingIntegrationOwner::GuildInstall(inner) => {
374                    serializer.serialize_entry(&InstallationContext::Guild, &inner)
375                },
376                AuthorizingIntegrationOwner::UserInstall(inner) => {
377                    serializer.serialize_entry(&InstallationContext::User, &inner)
378                },
379                AuthorizingIntegrationOwner::Unknown(inner) => {
380                    serializer.serialize_entry(&inner, &())
381                },
382            }?;
383        }
384
385        serializer.end()
386    }
387}
388
389/// Sent when a [`Message`] is a response to an [`Interaction`].
390///
391/// [`Message`]: crate::model::channel::Message
392///
393/// [Discord docs](https://discord.com/developers/docs/interactions/receiving-and-responding#message-interaction-object).
394#[cfg_attr(
395    all(not(ignore_serenity_deprecated), feature = "unstable_discord_api"),
396    deprecated = "Use Message::interaction_metadata"
397)]
398#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
399#[derive(Clone, Debug, Deserialize, Serialize)]
400#[non_exhaustive]
401pub struct MessageInteraction {
402    /// The id of the interaction.
403    pub id: InteractionId,
404    /// The type of the interaction.
405    #[serde(rename = "type")]
406    pub kind: InteractionType,
407    /// The name of the [`Command`].
408    ///
409    /// [`Command`]: crate::model::application::Command
410    pub name: String,
411    /// The user who invoked the interaction.
412    pub user: User,
413    /// The member who invoked the interaction in the guild.
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub member: Option<PartialMember>,
416}
417
418#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
419#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
420#[non_exhaustive]
421pub struct MessageCommandInteractionMetadata {
422    /// The ID of the interaction
423    pub id: InteractionId,
424    /// The user who triggered the interaction
425    pub user: User,
426    /// The IDs for installation context(s) related to an interaction.
427    pub authorizing_integration_owners: AuthorizingIntegrationOwners,
428    /// The ID of the original response message, present only on follow-up messages.
429    pub original_response_message_id: Option<MessageId>,
430    /// The user the command was run on, present only on user command interactions
431    pub target_user: Option<User>,
432    /// The ID of the message the command was run on, present only on message command
433    /// interactions.
434    pub target_message_id: Option<MessageId>,
435}
436
437#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
438#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
439#[non_exhaustive]
440pub struct MessageComponentInteractionMetadata {
441    /// The ID of the interaction
442    pub id: InteractionId,
443    /// The user who triggered the interaction
444    pub user: User,
445    /// The IDs for installation context(s) related to an interaction.
446    pub authorizing_integration_owners: AuthorizingIntegrationOwners,
447    /// The ID of the original response message, present only on follow-up messages.
448    pub original_response_message_id: Option<MessageId>,
449    /// The ID of the message that contained the interactive component
450    pub interacted_message_id: MessageId,
451}
452
453#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
454#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
455#[non_exhaustive]
456pub struct MessageModalSubmitInteractionMetadata {
457    /// The ID of the interaction
458    pub id: InteractionId,
459    /// The user who triggered the interaction
460    pub user: User,
461    /// The IDs for installation context(s) related to an interaction.
462    pub authorizing_integration_owners: AuthorizingIntegrationOwners,
463    /// The ID of the original response message, present only on follow-up messages.
464    pub original_response_message_id: Option<MessageId>,
465    /// Metadata for the interaction that was used to open the modal
466    pub triggering_interaction_metadata: Box<MessageInteractionMetadata>,
467}
468
469/// Metadata about the interaction, including the source of the interaction relevant server and
470/// user IDs.
471#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
472#[derive(Clone, Debug)]
473#[non_exhaustive]
474pub enum MessageInteractionMetadata {
475    Command(MessageCommandInteractionMetadata),
476    Component(MessageComponentInteractionMetadata),
477    ModalSubmit(MessageModalSubmitInteractionMetadata),
478    Unknown(InteractionType),
479}
480
481impl<'de> serde::Deserialize<'de> for MessageInteractionMetadata {
482    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
483        let mut data = JsonMap::deserialize(deserializer)?;
484        let kind: InteractionType = remove_from_map(&mut data, "type")?;
485
486        match kind {
487            InteractionType::Command => deserialize_val(Value::from(data)).map(Self::Command),
488            InteractionType::Component => deserialize_val(Value::from(data)).map(Self::Component),
489            InteractionType::Modal => deserialize_val(Value::from(data)).map(Self::ModalSubmit),
490
491            unknown => Ok(Self::Unknown(unknown)),
492        }
493    }
494}
495
496impl serde::Serialize for MessageInteractionMetadata {
497    fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
498        #[derive(serde::Serialize)]
499        struct WithType<T> {
500            #[serde(rename = "type")]
501            kind: InteractionType,
502            #[serde(flatten)]
503            val: T,
504        }
505
506        fn serialize_with_type<S: Serializer, T: serde::Serialize>(
507            serializer: S,
508            val: T,
509            kind: InteractionType,
510        ) -> StdResult<S::Ok, S::Error> {
511            let wrapper = WithType {
512                kind,
513                val,
514            };
515
516            wrapper.serialize(serializer)
517        }
518
519        match self {
520            MessageInteractionMetadata::Command(val) => {
521                serialize_with_type(serializer, val, InteractionType::Command)
522            },
523            MessageInteractionMetadata::Component(val) => {
524                serialize_with_type(serializer, val, InteractionType::Component)
525            },
526            MessageInteractionMetadata::ModalSubmit(val) => {
527                serialize_with_type(serializer, val, InteractionType::Modal)
528            },
529            &MessageInteractionMetadata::Unknown(kind) => {
530                tracing::warn!("Tried to serialize MessageInteractionMetadata::Unknown({}), serialising null instead", u8::from(kind));
531                serializer.serialize_none()
532            },
533        }
534    }
535}