tulpje_framework/context/
command_context.rs

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