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.interaction.as_ref().ok_or("No interaction in context")?;
53
54 let response = InteractionResponse {
55 response_type: InteractionCallbackType::DeferredChannelMessageWithSource,
56 data: Some(InteractionCallbackData {
57 flags: if ephemeral { Some(64) } else { None }, content: None,
59 tts: false,
60 embeds: vec![],
61 allowed_mentions: None,
62 components: vec![],
63 attachments: vec![],
64 choices: None,
65 }),
66 };
67
68 self.http
69 .create_interaction_response(interaction.id, &interaction.token, &response)
70 .await?;
71
72 *responded = true;
73 Ok(())
74 }
75
76 pub async fn reply(
84 &self,
85 content: impl Into<String>,
86 ) -> Result<Message<'static>, Box<dyn std::error::Error + Send + Sync>> {
87 let content = content.into();
88 let mut responded = self.has_responded.write().await;
89 let interaction = self.interaction.as_ref().ok_or("No interaction in context")?;
90
91 if *responded {
92 let app_id = interaction.application_id;
96 let token = &interaction.token;
97
98 #[derive(serde::Serialize)]
100 struct EditBody {
101 content: String,
102 }
103
104 let message = self
105 .http
106 .edit_original_interaction_response(app_id, token, EditBody { content })
107 .await?;
108
109 Ok(message)
110 } else {
111 let response = InteractionResponse {
113 response_type: InteractionCallbackType::ChannelMessageWithSource,
114 data: Some(InteractionCallbackData {
115 content: Some(content.clone().into()),
116 tts: false,
117 embeds: vec![],
118 allowed_mentions: None,
119 flags: None,
120 components: vec![],
121 attachments: vec![],
122 choices: None,
123 }),
124 };
125
126 self.http
127 .create_interaction_response(
128 interaction.id,
129 &interaction.token,
130 &response,
131 )
132 .await?;
133
134 *responded = true;
135
136 let msg = self
139 .http
140 .get_original_interaction_response(
141 interaction.application_id,
142 &interaction.token,
143 )
144 .await?;
145 Ok(msg)
146 }
147 }
148
149 pub async fn reply_embed(
151 &self,
152 embed: impl Into<Embed<'static>>,
153 ) -> Result<Message<'static>, Box<dyn std::error::Error + Send + Sync>> {
154 let embed = embed.into();
155 let mut responded = self.has_responded.write().await;
156 let interaction = self.interaction.as_ref().ok_or("No interaction in context")?;
157
158 if *responded {
159 #[derive(serde::Serialize)]
161 struct EditBody {
162 embeds: Vec<Embed<'static>>,
163 }
164
165 let message = self
166 .http
167 .edit_original_interaction_response(
168 interaction.application_id,
169 &interaction.token,
170 EditBody {
171 embeds: vec![embed],
172 },
173 )
174 .await?;
175 Ok(message)
176 } else {
177 let response = InteractionResponse {
178 response_type: InteractionCallbackType::ChannelMessageWithSource,
179 data: Some(InteractionCallbackData {
180 embeds: vec![embed],
181 content: None,
182 tts: false,
183 allowed_mentions: None,
184 flags: None,
185 components: vec![],
186 attachments: vec![],
187 choices: None,
188 }),
189 };
190
191 self.http
192 .create_interaction_response(
193 interaction.id,
194 &interaction.token,
195 &response,
196 )
197 .await?;
198
199 *responded = true;
200
201 let msg = self
203 .http
204 .get_original_interaction_response(
205 interaction.application_id,
206 &interaction.token,
207 )
208 .await?;
209 Ok(msg)
210 }
211 }
212
213 pub async fn reply_ephemeral(
215 &self,
216 content: impl Into<String>,
217 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
218 let content = content.into();
219 let mut responded = self.has_responded.write().await;
220 let interaction = self.interaction.as_ref().ok_or("No interaction in context")?;
221
222 if *responded {
223 return Err("Cannot reply ephemeral after already responding".into());
225 }
226
227 let response = InteractionResponse {
228 response_type: InteractionCallbackType::ChannelMessageWithSource,
229 data: Some(InteractionCallbackData {
230 content: Some(content.into()),
231 flags: Some(64), tts: false,
233 embeds: vec![],
234 allowed_mentions: None,
235 components: vec![],
236 attachments: vec![],
237 choices: None,
238 }),
239 };
240
241 self.http
242 .create_interaction_response(interaction.id, &interaction.token, &response)
243 .await?;
244
245 *responded = true;
246 Ok(())
247 }
248
249 pub async fn edit_reply(
251 &self,
252 content: impl Into<String>,
253 ) -> Result<Message<'static>, Box<dyn std::error::Error + Send + Sync>> {
254 let interaction = self.interaction.as_ref().ok_or("No interaction in context")?;
255 #[derive(serde::Serialize)]
256 struct EditBody {
257 content: String,
258 }
259
260 let message = self
261 .http
262 .edit_original_interaction_response(
263 interaction.application_id,
264 &interaction.token,
265 EditBody {
266 content: content.into(),
267 },
268 )
269 .await?;
270
271 Ok(message)
272 }
273
274 pub async fn followup(
276 &self,
277 content: impl Into<String>,
278 ) -> Result<Message<'static>, Box<dyn std::error::Error + Send + Sync>> {
279 use titanium_model::builder::ExecuteWebhook;
280 let interaction = self.interaction.as_ref().ok_or("No interaction in context")?;
281
282 let params = ExecuteWebhook {
283 content: Some(content.into()),
284 ..Default::default()
285 };
286
287 let msg = self
289 .http
290 .execute_webhook(
291 interaction.application_id,
292 &interaction.token,
293 ¶ms,
294 )
295 .await?;
296
297 Ok(msg.ok_or("Failed to get followup message")?)
299 }
300
301 #[inline]
303 pub fn user(&self) -> Option<&User<'static>> {
304 self.interaction.as_ref().and_then(|i| {
305 i.member
306 .as_ref()
307 .and_then(|m| m.user.as_ref())
308 .or(i.user.as_ref())
309 })
310 }
311
312 #[inline]
314 pub fn guild_id(&self) -> Option<Snowflake> {
315 self.interaction.as_ref().and_then(|i| i.guild_id)
316 }
317
318 #[inline]
320 pub fn channel_id(&self) -> Option<Snowflake> {
321 self.interaction.as_ref().and_then(|i| i.channel_id)
322 }
323}