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