1use std::sync::Arc;
2use titanium_http::HttpClient;
3use titanium_model::{
4 Embed, Interaction, InteractionCallbackData, InteractionCallbackType, InteractionResponse,
5 Message, Snowflake, User,
6};
7use tokio::sync::RwLock;
8
9use titanium_gateway::Shard;
11
12#[derive(Clone)]
14pub struct Context {
15 pub http: Arc<HttpClient>,
16 pub cache: Arc<titanium_cache::InMemoryCache>,
17 pub shard: Arc<Shard>,
18 pub interaction: Option<Arc<Interaction<'static>>>,
19 pub has_responded: Arc<RwLock<bool>>,
21}
22
23impl Context {
24 pub fn new(
25 http: Arc<HttpClient>,
26 cache: Arc<titanium_cache::InMemoryCache>,
27 shard: Arc<Shard>,
28 interaction: Option<Interaction<'static>>,
29 ) -> Self {
30 Self {
31 http,
32 cache,
33 shard,
34 interaction: interaction.map(Arc::new),
35 has_responded: Arc::new(RwLock::new(false)),
36 }
37 }
38
39 pub async fn defer(
44 &self,
45 ephemeral: bool,
46 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
47 let mut responded = self.has_responded.write().await;
48 if *responded {
49 return Ok(()); }
51
52 let interaction = self
53 .interaction
54 .as_ref()
55 .ok_or("No interaction in context")?;
56
57 let response = InteractionResponse {
58 response_type: InteractionCallbackType::DeferredChannelMessageWithSource,
59 data: Some(InteractionCallbackData {
60 flags: if ephemeral { Some(64) } else { None }, content: None,
62 tts: false,
63 embeds: vec![],
64 allowed_mentions: None,
65 components: vec![],
66 attachments: vec![],
67 choices: None,
68 custom_id: None,
69 title: None,
70 }),
71 };
72
73 self.http
74 .create_interaction_response(interaction.id, &interaction.token, &response)
75 .await?;
76
77 *responded = true;
78 Ok(())
79 }
80
81 pub async fn reply(
89 &self,
90 content: impl Into<String>,
91 ) -> Result<Message<'static>, Box<dyn std::error::Error + Send + Sync>> {
92 let content = content.into();
93 let mut responded = self.has_responded.write().await;
94 let interaction = self
95 .interaction
96 .as_ref()
97 .ok_or("No interaction in context")?;
98
99 if *responded {
100 let app_id = interaction.application_id;
104 let token = &interaction.token;
105
106 #[derive(serde::Serialize)]
108 struct EditBody {
109 content: String,
110 }
111
112 let message = self
113 .http
114 .edit_original_interaction_response(app_id, token, EditBody { content })
115 .await?;
116
117 Ok(message)
118 } else {
119 let response = InteractionResponse {
121 response_type: InteractionCallbackType::ChannelMessageWithSource,
122 data: Some(InteractionCallbackData {
123 content: Some(content.clone().into()),
124 tts: false,
125 embeds: vec![],
126 allowed_mentions: None,
127 flags: None,
128 components: vec![],
129 attachments: vec![],
130 choices: None,
131 custom_id: None,
132 title: None,
133 }),
134 };
135
136 self.http
137 .create_interaction_response(interaction.id, &interaction.token, &response)
138 .await?;
139
140 *responded = true;
141
142 let msg = self
145 .http
146 .get_original_interaction_response(interaction.application_id, &interaction.token)
147 .await?;
148 Ok(msg)
149 }
150 }
151
152 pub async fn reply_embed(
154 &self,
155 embed: impl Into<Embed<'static>>,
156 ) -> Result<Message<'static>, Box<dyn std::error::Error + Send + Sync>> {
157 let embed = embed.into();
158 let mut responded = self.has_responded.write().await;
159 let interaction = self
160 .interaction
161 .as_ref()
162 .ok_or("No interaction in context")?;
163
164 if *responded {
165 #[derive(serde::Serialize)]
167 struct EditBody {
168 embeds: Vec<Embed<'static>>,
169 }
170
171 let message = self
172 .http
173 .edit_original_interaction_response(
174 interaction.application_id,
175 &interaction.token,
176 EditBody {
177 embeds: vec![embed],
178 },
179 )
180 .await?;
181 Ok(message)
182 } else {
183 let response = InteractionResponse {
184 response_type: InteractionCallbackType::ChannelMessageWithSource,
185 data: Some(InteractionCallbackData {
186 embeds: vec![embed],
187 content: None,
188 tts: false,
189 allowed_mentions: None,
190 flags: None,
191 components: vec![],
192 attachments: vec![],
193 choices: None,
194 custom_id: None,
195 title: None,
196 }),
197 };
198
199 self.http
200 .create_interaction_response(interaction.id, &interaction.token, &response)
201 .await?;
202
203 *responded = true;
204
205 let msg = self
207 .http
208 .get_original_interaction_response(interaction.application_id, &interaction.token)
209 .await?;
210 Ok(msg)
211 }
212 }
213
214 pub async fn reply_ephemeral(
216 &self,
217 content: impl Into<String>,
218 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
219 let content = content.into();
220 let mut responded = self.has_responded.write().await;
221 let interaction = self
222 .interaction
223 .as_ref()
224 .ok_or("No interaction in context")?;
225
226 if *responded {
227 return Err("Cannot reply ephemeral after already responding".into());
229 }
230
231 let response = InteractionResponse {
232 response_type: InteractionCallbackType::ChannelMessageWithSource,
233 data: Some(InteractionCallbackData {
234 content: Some(content.into()),
235 flags: Some(64), tts: false,
237 embeds: vec![],
238 allowed_mentions: None,
239 components: vec![],
240 attachments: vec![],
241 choices: None,
242 custom_id: None,
243 title: None,
244 }),
245 };
246
247 self.http
248 .create_interaction_response(interaction.id, &interaction.token, &response)
249 .await?;
250
251 *responded = true;
252 Ok(())
253 }
254
255 pub async fn edit_reply(
257 &self,
258 content: impl Into<String>,
259 ) -> Result<Message<'static>, Box<dyn std::error::Error + Send + Sync>> {
260 let interaction = self
261 .interaction
262 .as_ref()
263 .ok_or("No interaction in context")?;
264 #[derive(serde::Serialize)]
265 struct EditBody {
266 content: String,
267 }
268
269 let message = self
270 .http
271 .edit_original_interaction_response(
272 interaction.application_id,
273 &interaction.token,
274 EditBody {
275 content: content.into(),
276 },
277 )
278 .await?;
279
280 Ok(message)
281 }
282
283 pub async fn followup(
285 &self,
286 content: impl Into<String>,
287 ) -> Result<Message<'static>, Box<dyn std::error::Error + Send + Sync>> {
288 use titanium_model::builder::ExecuteWebhook;
289 let interaction = self
290 .interaction
291 .as_ref()
292 .ok_or("No interaction in context")?;
293
294 let params = ExecuteWebhook {
295 content: Some(content.into()),
296 ..Default::default()
297 };
298
299 let msg = self
301 .http
302 .execute_webhook(interaction.application_id, &interaction.token, ¶ms)
303 .await?;
304
305 Ok(msg.ok_or("Failed to get followup message")?)
307 }
308
309 #[inline]
311 pub fn user(&self) -> Option<&User<'static>> {
312 self.interaction.as_ref().and_then(|i| {
313 i.member
314 .as_ref()
315 .and_then(|m| m.user.as_ref())
316 .or(i.user.as_ref())
317 })
318 }
319
320 #[inline]
322 #[must_use]
323 pub fn guild_id(&self) -> Option<Snowflake> {
324 self.interaction.as_ref().and_then(|i| i.guild_id)
325 }
326
327 #[inline]
329 #[must_use]
330 pub fn channel_id(&self) -> Option<Snowflake> {
331 self.interaction.as_ref().and_then(|i| i.channel_id)
332 }
333}