1#![allow(deprecated)]
2
3use std::{
4 fmt::{Debug, Display},
5 sync::{
6 atomic::{AtomicBool, AtomicU64, Ordering},
7 Arc,
8 },
9};
10
11use twilight_http::client::InteractionClient;
12use twilight_model::{
13 application::{
14 command::CommandOptionChoice,
15 interaction::{Interaction, InteractionType},
16 },
17 channel::{
18 message::{
19 component::{ActionRow, TextInput},
20 Component, MessageFlags,
21 },
22 Message,
23 },
24 guild::Permissions,
25 http::interaction::{InteractionResponse, InteractionResponseData, InteractionResponseType},
26 id::{
27 marker::{InteractionMarker, MessageMarker},
28 Id,
29 },
30};
31
32use crate::{
33 error::{CombinedUserError, Error, ErrorExt, NoCustomError, UserError},
34 reply::Reply,
35 Bot,
36};
37
38pub mod extract;
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum DeferVisibility {
44 Ephemeral,
46 Visible,
48}
49
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
53pub enum DeferBehavior {
54 Followup,
56 Update,
58}
59
60#[derive(Clone, Debug)]
64#[allow(clippy::module_name_repetitions)]
65pub struct InteractionHandle<'bot> {
66 bot: &'bot Bot,
68 id: Id<InteractionMarker>,
70 token: String,
72 kind: InteractionType,
74 app_permissions: Permissions,
76 responded: Arc<AtomicBool>,
78 last_message_id: Arc<AtomicU64>,
82}
83
84impl Bot {
85 #[must_use]
87 pub fn interaction_handle(&self, interaction: &Interaction) -> InteractionHandle<'_> {
88 InteractionHandle {
89 bot: self,
90 id: interaction.id,
91 token: interaction.token.clone(),
92 kind: interaction.kind,
93 app_permissions: interaction.app_permissions.unwrap_or(Permissions::all()),
94 responded: Arc::new(AtomicBool::new(false)),
95 last_message_id: Arc::new(AtomicU64::new(0)),
96 }
97 }
98
99 #[must_use]
101 pub const fn interaction_client(&self) -> InteractionClient<'_> {
102 self.http.interaction(self.application.id)
103 }
104}
105
106impl InteractionHandle<'_> {
107 pub fn combined_check_permissions<C>(
118 &self,
119 required_permissions: Permissions,
120 ) -> Result<(), CombinedUserError<C>> {
121 let missing_permissions = required_permissions - self.app_permissions;
122 if !missing_permissions.is_empty() {
123 return Err(CombinedUserError::MissingPermissions(Some(
124 missing_permissions,
125 )));
126 }
127
128 Ok(())
129 }
130
131 #[deprecated(note = "use `combined_check_permissions` instead")]
142 pub fn check_permissions(&self, required_permissions: Permissions) -> Result<(), UserError> {
143 let missing_permissions = required_permissions - self.app_permissions;
144 if !missing_permissions.is_empty() {
145 return Err(UserError::MissingPermissions(Some(missing_permissions)));
146 }
147
148 Ok(())
149 }
150
151 #[deprecated(note = "Use `report_error` instead and do the internal error handling yourself")]
166 pub async fn handle_error<Custom: Display + Debug + Send + Sync + 'static>(
167 &self,
168 reply: Reply,
169 error: anyhow::Error,
170 ) -> Option<Message> {
171 if error.ignore() {
172 return None;
173 }
174
175 if let Some(internal_err) = error.internal::<Custom>() {
176 self.bot.log(internal_err).await;
177 }
178
179 match self
180 .reply(reply)
181 .await
182 .map_err(|err| anyhow::Error::new(err).internal::<Custom>())
183 {
184 Ok(message) => message,
185 Err(reply_err) => {
186 if let Some(reply_internal_err) = reply_err {
187 self.bot.log(reply_internal_err).await;
188 }
189 None
190 }
191 }
192 }
193
194 #[deprecated(note = "Use `report_error` instead and do the internal error handling yourself")]
198 pub async fn handle_error_no_custom(
199 &self,
200 reply: Reply,
201 error: anyhow::Error,
202 ) -> Option<Message> {
203 self.handle_error::<NoCustomError>(reply, error).await
204 }
205
206 #[allow(clippy::missing_errors_doc)]
218 pub async fn report_error<C: Send>(
219 &self,
220 reply: Reply,
221 error: CombinedUserError<C>,
222 ) -> Result<Option<Message>, Error> {
223 if let CombinedUserError::Ignore = error {
224 return Ok(None);
225 }
226
227 match self.reply(reply).await {
228 Ok(message) => Ok(message),
229 Err(Error::Http(err))
230 if matches!(
231 CombinedUserError::<C>::from_http_err(&err),
232 CombinedUserError::Internal
233 ) =>
234 {
235 Err(Error::Http(err))
236 }
237 Err(err) => Err(err),
238 }
239 }
240
241 pub async fn defer(&self, visibility: DeferVisibility) -> Result<(), Error> {
258 self.defer_with_behavior(visibility, DeferBehavior::Followup)
259 .await
260 }
261
262 pub async fn defer_component(
274 &self,
275 visibility: DeferVisibility,
276 behavior: DeferBehavior,
277 ) -> Result<(), Error> {
278 self.defer_with_behavior(visibility, behavior).await
279 }
280
281 #[deprecated(note = "use `defer_component` instead")]
284 #[allow(clippy::missing_errors_doc)]
285 pub async fn defer_update_message(&self) -> Result<(), Error> {
286 self.defer_with_behavior(DeferVisibility::Visible, DeferBehavior::Update)
287 .await
288 }
289
290 #[deprecated(note = "use `defer` or `defer_component` instead ")]
304 pub async fn defer_with_behavior(
305 &self,
306 visibility: DeferVisibility,
307 behavior: DeferBehavior,
308 ) -> Result<(), Error> {
309 if self.responded() {
310 return Err(Error::AlreadyResponded);
311 }
312
313 let kind = if self.kind == InteractionType::MessageComponent {
314 match behavior {
315 DeferBehavior::Followup => {
316 InteractionResponseType::DeferredChannelMessageWithSource
317 }
318 DeferBehavior::Update => InteractionResponseType::DeferredUpdateMessage,
319 }
320 } else {
321 InteractionResponseType::DeferredChannelMessageWithSource
322 };
323
324 let defer_response = InteractionResponse {
325 kind,
326 data: Some(InteractionResponseData {
327 flags: (visibility == DeferVisibility::Ephemeral)
328 .then_some(MessageFlags::EPHEMERAL),
329 ..Default::default()
330 }),
331 };
332
333 self.bot
334 .interaction_client()
335 .create_response(self.id, &self.token, &defer_response)
336 .await?;
337
338 self.set_responded(true);
339
340 Ok(())
341 }
342
343 pub async fn reply(&self, reply: Reply) -> Result<Option<Message>, Error> {
378 if self.responded() {
379 let client = self.bot.interaction_client();
380
381 if reply.update_last {
382 if let Some(last_message_id) = self.last_message_id() {
383 let mut update_followup = client.update_followup(&self.token, last_message_id);
384
385 if let Some(allowed_mentions) = &reply.allowed_mentions {
386 update_followup =
387 update_followup.allowed_mentions(allowed_mentions.as_ref());
388 }
389 update_followup
390 .content((!reply.content.is_empty()).then_some(&reply.content))?
391 .embeds(Some(&reply.embeds))?
392 .components(Some(&reply.components))?
393 .attachments(&reply.attachments)?
394 .await?;
395
396 Ok(None)
397 } else {
398 let mut update_response = client.update_response(&self.token);
399
400 if let Some(allowed_mentions) = &reply.allowed_mentions {
401 update_response =
402 update_response.allowed_mentions(allowed_mentions.as_ref());
403 }
404
405 let message = update_response
406 .content((!reply.content.is_empty()).then_some(&reply.content))?
407 .embeds(Some(&reply.embeds))?
408 .components(Some(&reply.components))?
409 .attachments(&reply.attachments)?
410 .await?
411 .model()
412 .await?;
413
414 self.set_last_message_id(message.id);
415
416 Ok(Some(message))
417 }
418 } else {
419 let mut followup = client.create_followup(&self.token);
420
421 if !reply.content.is_empty() {
422 followup = followup.content(&reply.content)?;
423 }
424 if let Some(allowed_mentions) = &reply.allowed_mentions {
425 followup = followup.allowed_mentions(allowed_mentions.as_ref());
426 }
427
428 let message = followup
429 .embeds(&reply.embeds)?
430 .components(&reply.components)?
431 .attachments(&reply.attachments)?
432 .flags(reply.flags)
433 .tts(reply.tts)
434 .await?
435 .model()
436 .await?;
437
438 self.set_last_message_id(message.id);
439
440 Ok(Some(message))
441 }
442 } else {
443 let kind = if reply.update_last && self.kind == InteractionType::MessageComponent {
444 InteractionResponseType::UpdateMessage
445 } else {
446 InteractionResponseType::ChannelMessageWithSource
447 };
448
449 self.bot
450 .interaction_client()
451 .create_response(
452 self.id,
453 &self.token,
454 &InteractionResponse {
455 kind,
456 data: Some(reply.into()),
457 },
458 )
459 .await?;
460
461 self.set_responded(true);
462
463 Ok(None)
464 }
465 }
466
467 #[deprecated(note = "Use `self.reply(reply.update_last())` instead")]
469 #[allow(clippy::missing_errors_doc)]
470 pub async fn update_message(&self, reply: Reply) -> Result<Option<Message>, Error> {
471 self.reply(reply.update_last()).await
472 }
473
474 pub async fn autocomplete(&self, choices: Vec<CommandOptionChoice>) -> Result<(), Error> {
483 if self.responded() {
484 return Err(Error::AlreadyResponded);
485 }
486
487 self.bot
488 .interaction_client()
489 .create_response(
490 self.id,
491 &self.token,
492 &InteractionResponse {
493 kind: InteractionResponseType::ApplicationCommandAutocompleteResult,
494 data: Some(InteractionResponseData {
495 choices: Some(choices),
496 ..Default::default()
497 }),
498 },
499 )
500 .await?;
501
502 self.set_responded(true);
503
504 Ok(())
505 }
506
507 pub async fn modal(
516 &self,
517 custom_id: String,
518 title: String,
519 text_inputs: Vec<TextInput>,
520 ) -> Result<(), Error> {
521 let responded = self.responded();
522
523 if responded {
524 return Err(Error::AlreadyResponded);
525 }
526
527 self.bot
528 .interaction_client()
529 .create_response(
530 self.id,
531 &self.token,
532 &InteractionResponse {
533 kind: InteractionResponseType::Modal,
534 data: Some(InteractionResponseData {
535 custom_id: Some(custom_id),
536 title: Some(title),
537 components: Some(
538 text_inputs
539 .into_iter()
540 .map(|text_input| {
541 Component::ActionRow(ActionRow {
542 components: vec![Component::TextInput(text_input)],
543 })
544 })
545 .collect(),
546 ),
547 ..Default::default()
548 }),
549 },
550 )
551 .await?;
552
553 self.set_responded(true);
554
555 Ok(())
556 }
557
558 fn responded(&self) -> bool {
559 self.responded.load(Ordering::Acquire)
560 }
561
562 fn set_responded(&self, val: bool) {
563 self.responded.store(val, Ordering::Release);
564 }
565
566 fn last_message_id(&self) -> Option<Id<MessageMarker>> {
567 let id = self.last_message_id.load(Ordering::Acquire);
568 if id == 0 {
569 None
570 } else {
571 Some(Id::new(id))
572 }
573 }
574
575 fn set_last_message_id(&self, val: Id<MessageMarker>) {
576 self.last_message_id.store(val.get(), Ordering::Release);
577 }
578}
579
580#[cfg(test)]
581mod tests {
582 use std::sync::{
583 atomic::{AtomicBool, Ordering},
584 Arc,
585 };
586
587 #[test]
588 fn atomic_preserved() {
589 let responded = Arc::new(AtomicBool::new(false));
590 let responded_clone = responded.clone();
591
592 responded.store(true, Ordering::Release);
593
594 assert!(responded_clone.load(Ordering::Acquire));
595 }
596}