twilight_model/http/
interaction.rs

1//! Models used when responding to interactions over HTTP.
2#![allow(deprecated)]
3
4use super::attachment::Attachment;
5use crate::{
6    application::command::CommandOptionChoice,
7    channel::message::{AllowedMentions, Component, Embed, MessageFlags},
8};
9use serde::{Deserialize, Serialize};
10use serde_repr::{Deserialize_repr, Serialize_repr};
11
12/// Interaction response sent to Discord.
13///
14/// See [Discord Docs/Interaction Object].
15///
16/// [Discord Docs/Interaction Object]: https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-interaction-structure
17#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
18pub struct InteractionResponse {
19    /// Type of the response.
20    #[serde(rename = "type")]
21    pub kind: InteractionResponseType,
22    /// Data of the response.
23    ///
24    /// This is required if the type is any of the following:
25    /// - [`ChannelMessageWithSource`]
26    /// - [`UpdateMessage`]
27    /// - [`Modal`]
28    /// - [`ApplicationCommandAutocompleteResult`]
29    ///
30    /// [`ApplicationCommandAutocompleteResult`]: InteractionResponseType::ApplicationCommandAutocompleteResult
31    /// [`ChannelMessageWithSource`]: InteractionResponseType::ChannelMessageWithSource
32    /// [`Modal`]: InteractionResponseType::Modal
33    /// [`UpdateMessage`]: InteractionResponseType::UpdateMessage
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub data: Option<InteractionResponseData>,
36}
37
38/// Data included in an interaction response.
39#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
40pub struct InteractionResponseData {
41    /// Allowed mentions of the response.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub allowed_mentions: Option<AllowedMentions>,
44    /// List of attachments on the response.
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub attachments: Option<Vec<Attachment>>,
47    /// List of autocomplete alternatives.
48    ///
49    /// Can only be used with
50    /// [`InteractionResponseType::ApplicationCommandAutocompleteResult`].
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub choices: Option<Vec<CommandOptionChoice>>,
53    /// List of components on the response.
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub components: Option<Vec<Component>>,
56    /// Content of the response.
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub content: Option<String>,
59    /// For [`InteractionResponseType::Modal`], user defined identifier.
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub custom_id: Option<String>,
62    /// Embeds of the response.
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub embeds: Option<Vec<Embed>>,
65    /// Interaction response data flags.
66    ///
67    /// The supported flags are [`MessageFlags::SUPPRESS_EMBEDS`] and
68    /// [`MessageFlags::EPHEMERAL`].
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub flags: Option<MessageFlags>,
71    /// For [`InteractionResponseType::Modal`], title of the modal.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub title: Option<String>,
74    /// Whether the response is TTS.
75    #[serde(skip_serializing_if = "Option::is_none")]
76    pub tts: Option<bool>,
77}
78
79/// Type of interaction response.
80#[derive(Clone, Copy, Debug, Deserialize_repr, Eq, Hash, PartialEq, Serialize_repr)]
81#[non_exhaustive]
82#[repr(u8)]
83pub enum InteractionResponseType {
84    /// Used when responding to a Ping from Discord.
85    Pong = 1,
86    /// Responds to an interaction with a message.
87    ChannelMessageWithSource = 4,
88    /// Acknowledges an interaction, showing a loading state, and allowing for
89    /// the message to be edited later.
90    DeferredChannelMessageWithSource = 5,
91    /// Acknowledges a component interaction, allowing for the message to be
92    /// edited later.
93    ///
94    /// This is only valid for components and modal submits.
95    DeferredUpdateMessage = 6,
96    /// Acknowledges a component interaction and edits the message.
97    ///
98    /// This is only valid for components and modal submits.
99    UpdateMessage = 7,
100    /// Respond to an autocomplete interaction with suggested choices.
101    ApplicationCommandAutocompleteResult = 8,
102    /// Respond to an interaction with a popup modal.
103    Modal = 9,
104    /// Respond to an interaction with an upgrade button, only available
105    /// for apps with monetization enabled
106    ///
107    /// Deprecated: Please send a [`InteractionResponseType::ChannelMessageWithSource`]
108    /// with an [`Button`](crate::channel::message::component::Button) with the style [`ButtonStyle::Premium`](crate::channel::message::component::ButtonStyle)
109    /// instead.
110    #[deprecated(note = "Deprecated by Discord in favor of Premium Buttons")]
111    PremiumRequired = 10,
112}
113
114#[cfg(test)]
115mod tests {
116    use crate::{
117        channel::message::MessageFlags,
118        http::{
119            attachment::Attachment,
120            interaction::{InteractionResponse, InteractionResponseData, InteractionResponseType},
121        },
122    };
123    use serde::{Deserialize, Serialize};
124    use serde_test::Token;
125    use static_assertions::{assert_fields, assert_impl_all};
126    use std::fmt::Debug;
127
128    assert_fields!(
129        InteractionResponseData: allowed_mentions,
130        choices,
131        components,
132        content,
133        embeds,
134        flags,
135        tts
136    );
137    assert_impl_all!(
138        InteractionResponseData: Clone,
139        Debug,
140        Deserialize<'static>,
141        PartialEq,
142        Send,
143        Serialize,
144        Sync
145    );
146
147    #[test]
148    fn interaction_response() {
149        let value = InteractionResponse {
150            kind: InteractionResponseType::ChannelMessageWithSource,
151            data: Some(InteractionResponseData {
152                allowed_mentions: None,
153                attachments: None,
154                choices: None,
155                components: None,
156                content: Some("test".into()),
157                custom_id: None,
158                embeds: None,
159                flags: Some(MessageFlags::EPHEMERAL),
160                title: None,
161                tts: None,
162            }),
163        };
164
165        serde_test::assert_tokens(
166            &value,
167            &[
168                Token::Struct {
169                    name: "InteractionResponse",
170                    len: 2,
171                },
172                Token::Str("type"),
173                Token::U8(4),
174                Token::Str("data"),
175                Token::Some,
176                Token::Struct {
177                    name: "InteractionResponseData",
178                    len: 2,
179                },
180                Token::Str("content"),
181                Token::Some,
182                Token::Str("test"),
183                Token::Str("flags"),
184                Token::Some,
185                Token::U64(64),
186                Token::StructEnd,
187                Token::StructEnd,
188            ],
189        );
190    }
191
192    #[test]
193    fn interaction_response_with_attachments() {
194        let value = InteractionResponse {
195            kind: InteractionResponseType::ChannelMessageWithSource,
196            data: Some(InteractionResponseData {
197                attachments: Some(Vec::from([Attachment {
198                    description: None,
199                    file: "file data".into(),
200                    filename: "filename.jpg".into(),
201                    id: 1,
202                }])),
203                ..InteractionResponseData::default()
204            }),
205        };
206
207        serde_test::assert_ser_tokens(
208            &value,
209            &[
210                Token::Struct {
211                    name: "InteractionResponse",
212                    len: 2,
213                },
214                Token::Str("type"),
215                Token::U8(InteractionResponseType::ChannelMessageWithSource as u8),
216                Token::Str("data"),
217                Token::Some,
218                Token::Struct {
219                    name: "InteractionResponseData",
220                    len: 1,
221                },
222                Token::Str("attachments"),
223                Token::Some,
224                Token::Seq { len: Some(1) },
225                Token::Struct {
226                    name: "Attachment",
227                    len: 2,
228                },
229                Token::Str("filename"),
230                Token::Str("filename.jpg"),
231                Token::Str("id"),
232                Token::U64(1),
233                Token::StructEnd,
234                Token::SeqEnd,
235                Token::StructEnd,
236                Token::StructEnd,
237            ],
238        );
239    }
240}