tulpje_framework/context/
command_context.rs1use std::{collections::HashMap, sync::Arc};
2
3use twilight_http::{client::InteractionClient, response::marker::EmptyBody, Client};
4use twilight_model::{
5 application::interaction::application_command::{
6 CommandData, CommandDataOption, CommandOptionValue,
7 },
8 channel::{message::MessageFlags, Message},
9 gateway::payload::incoming::InteractionCreate,
10 guild::Guild,
11 http::interaction::{InteractionResponse, InteractionResponseType},
12 id::{marker::ApplicationMarker, Id},
13};
14use twilight_util::builder::InteractionResponseDataBuilder;
15
16use super::Context;
17use crate::{Error, Metadata};
18
19#[derive(Clone, Debug)]
20pub struct CommandContext<T: Clone + Send + Sync> {
21 pub meta: Metadata,
22 pub application_id: Id<ApplicationMarker>,
23 pub services: Arc<T>,
24 pub client: Arc<Client>,
25
26 pub event: InteractionCreate,
27 pub command: CommandData,
28
29 pub name: String,
30 pub options: HashMap<String, CommandOptionValue>,
31}
32
33impl<T: Clone + Send + Sync> CommandContext<T> {
34 pub fn from_context(
35 meta: Metadata,
36 ctx: Context<T>,
37
38 event: InteractionCreate,
39 command: CommandData,
40
41 name: String,
42 options: &[CommandDataOption],
43 ) -> Self {
44 Self {
45 meta,
46 application_id: ctx.application_id,
47 client: ctx.client,
48 services: ctx.services,
49
50 command,
51 event,
52
53 name,
54 options: options
55 .iter()
56 .cloned()
57 .map(|opt| (opt.name, opt.value))
58 .collect(),
59 }
60 }
61
62 pub fn interaction(&self) -> InteractionClient<'_> {
63 self.client.interaction(self.application_id)
64 }
65
66 pub fn client(&self) -> Arc<Client> {
67 Arc::clone(&self.client)
68 }
69
70 pub async fn guild(&self) -> Result<Option<Guild>, Error> {
71 let Some(guild_id) = self.event.guild_id else {
72 return Ok(None);
73 };
74
75 Ok(Some(self.client.guild(guild_id).await?.model().await?))
76 }
77
78 pub async fn response(
79 &self,
80 response: InteractionResponse,
81 ) -> Result<twilight_http::Response<EmptyBody>, twilight_http::Error> {
82 self.interaction()
83 .create_response(self.event.id, &self.event.token, &response)
84 .await
85 }
86
87 pub async fn update(
88 &self,
89 message: impl Into<String>,
90 ) -> Result<twilight_http::Response<Message>, twilight_http::Error> {
91 self.interaction()
92 .update_response(&self.event.token)
93 .content(Some(&message.into()))
94 .await
95 }
96
97 pub async fn reply(
98 &self,
99 message: impl Into<String>,
100 ) -> Result<twilight_http::Response<EmptyBody>, twilight_http::Error> {
101 let response = InteractionResponseDataBuilder::new()
102 .content(message)
103 .build();
104
105 self.response(InteractionResponse {
106 kind: InteractionResponseType::ChannelMessageWithSource,
107 data: Some(response),
108 })
109 .await
110 }
111
112 pub async fn defer(&self) -> Result<twilight_http::Response<EmptyBody>, twilight_http::Error> {
113 self.response(InteractionResponse {
114 kind: InteractionResponseType::DeferredChannelMessageWithSource,
115 data: None,
116 })
117 .await
118 }
119 pub async fn defer_ephemeral(
120 &self,
121 ) -> Result<twilight_http::Response<EmptyBody>, twilight_http::Error> {
122 self.response(InteractionResponse {
123 kind: InteractionResponseType::DeferredChannelMessageWithSource,
124 data: Some(
125 InteractionResponseDataBuilder::new()
126 .flags(MessageFlags::EPHEMERAL)
127 .build(),
128 ),
129 })
130 .await
131 }
132
133 pub fn get_arg_string_optional(&self, name: &str) -> Result<Option<String>, Error> {
134 let Some(value) = self.options.get(name) else {
135 return Ok(None);
136 };
137
138 let CommandOptionValue::String(value) = value else {
139 return Err(format!("option '{}' not a string option", name).into());
140 };
141
142 Ok(Some(value.clone()))
143 }
144
145 pub fn get_arg_string(&self, name: &str) -> Result<String, Error> {
146 self.get_arg_string_optional(name)?
147 .ok_or_else(|| format!("couldn't find command argument {}", name).into())
148 }
149}