disint_model/interaction/
mod.rs1use 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}