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