Skip to main content

tulpje_framework/context/
command_context.rs

1use 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}