disint_model/interaction/
mod.rs

1use serde::Deserialize;
2
3pub mod embed;
4pub mod response;
5
6pub use response::InteractionResponseBuilder;
7
8#[derive(Debug, Deserialize)]
9pub struct Interaction {
10    version: i32,
11    id: String,
12    token: String,
13    #[serde(flatten)]
14    data: InteractionTypeAndData,
15}
16
17impl Interaction {
18    pub fn version(&self) -> i32 {
19        self.version
20    }
21
22    pub fn interaction_id(&self) -> u64 {
23        self.id.parse().expect("Invalid Interaction ID")
24    }
25
26    pub fn token(&self) -> &str {
27        &self.token
28    }
29
30    pub fn data(&self) -> &InteractionTypeAndData {
31        &self.data
32    }
33
34    pub fn into_data(self) -> InteractionTypeAndData {
35        self.data
36    }
37}
38
39#[derive(Debug, Deserialize)]
40#[non_exhaustive]
41#[serde(untagged)]
42pub enum InteractionTypeAndData {
43    #[serde(deserialize_with = "interaction_ping")]
44    Ping,
45    #[serde(deserialize_with = "interaction_command")]
46    ApplicationCommand {
47        guild_id: String,
48        channel_id: String,
49        member: GuildMember,
50        data: ApplicationCommandInteractionData,
51    },
52}
53
54#[derive(Debug, Deserialize)]
55pub struct GuildMember {
56    user: User,
57    nick: Option<String>,
58    roles: Vec<String>,
59    joined_at: chrono::DateTime<chrono::Utc>,
60    premium_since: Option<chrono::DateTime<chrono::Utc>>,
61    deaf: bool,
62    mute: bool,
63}
64
65impl GuildMember {
66    pub fn user(&self) -> &User {
67        &self.user
68    }
69
70    pub fn nick(&self) -> Option<&str> {
71        self.nick.as_deref()
72    }
73
74    pub fn nick_or_username(&self) -> &str {
75        self.nick.as_deref().unwrap_or(&self.user.username)
76    }
77
78    pub fn roles(&self) -> Vec<u64> {
79        self.roles
80            .iter()
81            .map(|s| s.parse())
82            .collect::<Result<_, _>>()
83            .expect("Invalid Role ID")
84    }
85
86    pub fn joined_at(&self) -> chrono::DateTime<chrono::Utc> {
87        self.joined_at
88    }
89
90    pub fn is_boosting(&self) -> bool {
91        self.premium_since.is_some()
92    }
93
94    pub fn boosting_since(&self) -> Option<chrono::DateTime<chrono::Utc>> {
95        self.premium_since
96    }
97
98    pub fn is_deaf(&self) -> bool {
99        self.deaf
100    }
101
102    pub fn is_mute(&self) -> bool {
103        self.mute
104    }
105}
106
107#[derive(Debug, Deserialize)]
108pub struct User {
109    id: String,
110    username: String,
111    discriminator: String,
112    avatar: Option<String>,
113    bot: Option<bool>,
114    system: Option<bool>,
115    mfa_enabled: Option<bool>,
116    locale: Option<String>,
117    verified: Option<bool>,
118    email: Option<String>,
119    flags: Option<i32>,
120    premium_type: Option<i32>,
121    public_flags: Option<i32>,
122}
123
124impl User {
125    pub fn id(&self) -> u64 {
126        self.id.parse().expect("Invalid User ID")
127    }
128
129    pub fn username(&self) -> &str {
130        &self.username
131    }
132
133    pub fn discriminator(&self) -> &str {
134        &self.discriminator
135    }
136
137    pub fn username_and_discriminator(&self) -> String {
138        format!("{}#{}", self.username, self.discriminator)
139    }
140
141    pub fn avatar(&self) -> Option<&str> {
142        self.avatar.as_deref()
143    }
144
145    pub fn cdn_avatar_path(&self) -> String {
146        if let Some(avatar) = &self.avatar {
147            let ext = if avatar.starts_with("a_") {
148                "gif"
149            } else {
150                "png"
151            };
152            format!("/avatars/{}/{}.{}", self.id, avatar, ext)
153        } else {
154            let discriminator = self.discriminator.parse::<u32>().unwrap();
155            format!("/embed/avatars/{}.png", discriminator % 5)
156        }
157    }
158
159    pub fn is_bot(&self) -> bool {
160        self.bot.unwrap_or(false)
161    }
162
163    pub fn is_system(&self) -> bool {
164        self.system.unwrap_or(false)
165    }
166
167    pub fn is_mfa_enabled(&self) -> bool {
168        self.mfa_enabled.unwrap_or(false)
169    }
170}
171
172#[derive(Debug, Deserialize)]
173pub struct ApplicationCommandInteractionData {
174    pub id: String,
175    pub name: String,
176    #[serde(default)]
177    pub options: Vec<ApplicationCommandInteractionDataOption>,
178}
179
180#[derive(Debug, Deserialize)]
181#[serde(untagged)]
182pub enum ApplicationCommandInteractionDataOption {
183    Value {
184        name: String,
185        value: crate::OptionValue,
186    },
187    Subcommand {
188        name: String,
189        options: Vec<ApplicationCommandInteractionDataOption>,
190    },
191}
192
193fn interaction_ping<'de, D>(d: D) -> Result<(), D::Error>
194where
195    D: serde::Deserializer<'de>,
196{
197    #[derive(Deserialize)]
198    struct Ping {
199        #[serde(rename = "type")]
200        ty: i32,
201    }
202
203    let ping = Ping::deserialize(d)?;
204    if ping.ty == 1 {
205        Ok(())
206    } else {
207        Err(serde::de::Error::custom("Not a Ping type"))
208    }
209}
210
211fn interaction_command<'de, D>(
212    d: D,
213) -> Result<
214    (
215        String,
216        String,
217        GuildMember,
218        ApplicationCommandInteractionData,
219    ),
220    D::Error,
221>
222where
223    D: serde::Deserializer<'de>,
224{
225    #[derive(Deserialize)]
226    struct ApplicationCommand {
227        #[serde(rename = "type")]
228        ty: i32,
229        guild_id: String,
230        channel_id: String,
231        member: GuildMember,
232        data: ApplicationCommandInteractionData,
233    }
234
235    let ping = ApplicationCommand::deserialize(d)?;
236    if ping.ty == 2 {
237        Ok((ping.guild_id, ping.channel_id, ping.member, ping.data))
238    } else {
239        Err(serde::de::Error::custom("Not a ApplicationCommand type"))
240    }
241}