pikadick_slash_framework/
command.rs1use crate::{
2 ArgumentParam,
3 BoxError,
4 BoxFuture,
5 BuilderError,
6 CheckFn,
7 DataType,
8 FromOptions,
9};
10use serenity::{
11 builder::{
12 CreateCommand,
13 CreateCommandOption,
14 },
15 client::Context,
16 model::application::{
17 CommandInteraction,
18 CommandOptionType,
19 },
20};
21use std::{
22 collections::HashMap,
23 future::Future,
24 sync::Arc,
25};
26
27type OnProcessResult = Result<(), BoxError>;
28pub type OnProcessFuture = BoxFuture<'static, OnProcessResult>;
29
30type OnProcessFutureFn = Box<dyn Fn(Context, CommandInteraction) -> OnProcessFuture + Send + Sync>;
32type OnProcessFutureFnPtr<F, A> = fn(Context, CommandInteraction, A) -> F;
33
34type HelpOnProcessFutureFn = Box<
35 dyn Fn(Context, CommandInteraction, Arc<HashMap<Box<str>, Command>>) -> OnProcessFuture
36 + Send
37 + Sync,
38>;
39type HelpOnProcessFutureFnPtr<F, A> =
40 fn(Context, CommandInteraction, Arc<HashMap<Box<str>, Command>>, A) -> F;
41
42pub struct Command {
44 name: Box<str>,
46
47 description: Box<str>,
49
50 arguments: Box<[ArgumentParam]>,
52
53 on_process: OnProcessFutureFn,
55
56 checks: Vec<CheckFn>,
58}
59
60impl Command {
61 pub fn name(&self) -> &str {
63 &self.name
64 }
65
66 pub fn description(&self) -> &str {
68 &self.description
69 }
70
71 pub fn arguments(&self) -> &[ArgumentParam] {
73 &self.arguments
74 }
75
76 pub async fn fire_on_process(
78 &self,
79 ctx: Context,
80 interaction: CommandInteraction,
81 ) -> Result<(), BoxError> {
82 (self.on_process)(ctx, interaction).await
83 }
84
85 pub fn checks(&self) -> &[CheckFn] {
87 &self.checks
88 }
89
90 pub fn register(&self, mut command: CreateCommand) -> CreateCommand {
92 command = command.name(self.name()).description(self.description());
93
94 for argument in self.arguments().iter() {
95 let option_kind = match argument.kind() {
96 DataType::Boolean => CommandOptionType::Boolean,
97 DataType::String => CommandOptionType::String,
98 DataType::Integer => CommandOptionType::Integer,
99 };
100 let option =
101 CreateCommandOption::new(option_kind, argument.name(), argument.description())
102 .required(argument.required());
103 command = command.add_option(option);
104 }
105
106 command
107 }
108}
109
110impl std::fmt::Debug for Command {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.debug_struct("Command")
113 .field("name", &self.name)
114 .field("description", &self.description)
115 .field("arguments", &self.arguments)
116 .field("on_process", &"<func>")
117 .finish()
118 }
119}
120
121pub struct CommandBuilder<'a, 'b> {
123 name: Option<&'a str>,
124 description: Option<&'b str>,
125 arguments: Vec<ArgumentParam>,
126
127 on_process: Option<OnProcessFutureFn>,
128 checks: Vec<CheckFn>,
129}
130
131impl<'a, 'b> CommandBuilder<'a, 'b> {
132 pub fn new() -> Self {
134 Self {
135 name: None,
136 description: None,
137 arguments: Vec::new(),
138
139 on_process: None,
140 checks: Vec::new(),
141 }
142 }
143
144 pub fn name(&mut self, name: &'a str) -> &mut Self {
146 self.name = Some(name);
147 self
148 }
149
150 pub fn description(&mut self, description: &'b str) -> &mut Self {
152 self.description = Some(description);
153 self
154 }
155
156 pub fn argument(&mut self, argument: ArgumentParam) -> &mut Self {
158 self.arguments.push(argument);
159 self
160 }
161
162 pub fn arguments(&mut self, arguments: impl Iterator<Item = ArgumentParam>) -> &mut Self {
164 for argument in arguments {
165 self.argument(argument);
166 }
167 self
168 }
169
170 pub fn on_process<F, A>(&mut self, on_process: OnProcessFutureFnPtr<F, A>) -> &mut Self
172 where
173 F: Future<Output = Result<(), BoxError>> + Send + 'static,
174 A: FromOptions + 'static,
175 {
176 self.on_process = Some(Box::new(move |ctx, interaction| {
178 Box::pin(async move {
179 let args = A::from_options(&interaction)?;
180 (on_process)(ctx, interaction, args).await
181 })
182 }));
183
184 self
185 }
186
187 pub fn check(&mut self, check: CheckFn) -> &mut Self {
189 self.checks.push(check);
190 self
191 }
192
193 pub fn build(&mut self) -> Result<Command, BuilderError> {
195 #[allow(clippy::or_fun_call)]
196 let name = self.name.take().ok_or(BuilderError::MissingField("name"))?;
197 #[allow(clippy::or_fun_call)]
198 let description = self
199 .description
200 .take()
201 .ok_or(BuilderError::MissingField("description"))?;
202 #[allow(clippy::or_fun_call)]
203 let on_process = self
204 .on_process
205 .take()
206 .ok_or(BuilderError::MissingField("on_process"))?;
207 let checks = std::mem::take(&mut self.checks);
208
209 Ok(Command {
210 name: name.into(),
211 description: description.into(),
212 arguments: std::mem::take(&mut self.arguments).into_boxed_slice(),
213
214 on_process,
215 checks,
216 })
217 }
218}
219
220impl std::fmt::Debug for CommandBuilder<'_, '_> {
221 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222 f.debug_struct("CommandBuilder")
223 .field("name", &self.name)
224 .field("description", &self.description)
225 .field("arguments", &self.arguments)
226 .field("on_process", &self.on_process.as_ref().map(|_| "<func>"))
227 .finish()
228 }
229}
230
231impl Default for CommandBuilder<'_, '_> {
232 fn default() -> Self {
233 Self::new()
234 }
235}
236
237pub struct HelpCommand {
239 description: Box<str>,
241
242 arguments: Box<[ArgumentParam]>,
244
245 on_process: HelpOnProcessFutureFn,
247}
248
249impl HelpCommand {
250 pub fn description(&self) -> &str {
252 &self.description
253 }
254
255 pub fn arguments(&self) -> &[ArgumentParam] {
257 &self.arguments
258 }
259
260 pub async fn fire_on_process(
262 &self,
263 ctx: Context,
264 interaction: CommandInteraction,
265 map: Arc<HashMap<Box<str>, Command>>,
266 ) -> Result<(), BoxError> {
267 (self.on_process)(ctx, interaction, map).await
268 }
269
270 pub fn register(&self, mut command: CreateCommand) -> CreateCommand {
272 command = command.name("help").description(self.description());
273
274 for argument in self.arguments().iter() {
275 let option_kind = match argument.kind() {
276 DataType::Boolean => CommandOptionType::Boolean,
277 DataType::String => CommandOptionType::String,
278 DataType::Integer => CommandOptionType::Integer,
279 };
280 let option =
281 CreateCommandOption::new(option_kind, argument.name(), argument.description())
282 .required(argument.required());
283 command = command.add_option(option);
284 }
285
286 command
287 }
288}
289
290impl std::fmt::Debug for HelpCommand {
291 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292 f.debug_struct("HelpCommand")
293 .field("description", &self.description)
294 .field("arguments", &self.arguments)
295 .field("on_process", &"<func>")
296 .finish()
297 }
298}
299
300pub struct HelpCommandBuilder<'a> {
302 description: Option<&'a str>,
303 arguments: Vec<ArgumentParam>,
304
305 on_process: Option<HelpOnProcessFutureFn>,
306}
307
308impl<'a> HelpCommandBuilder<'a> {
309 pub fn new() -> Self {
311 Self {
312 description: None,
313 arguments: Vec::new(),
314
315 on_process: None,
316 }
317 }
318
319 pub fn description(&mut self, description: &'a str) -> &mut Self {
321 self.description = Some(description);
322 self
323 }
324
325 pub fn argument(&mut self, argument: ArgumentParam) -> &mut Self {
327 self.arguments.push(argument);
328 self
329 }
330
331 pub fn on_process<F, A>(&mut self, on_process: HelpOnProcessFutureFnPtr<F, A>) -> &mut Self
333 where
334 F: Future<Output = Result<(), BoxError>> + Send + 'static,
335 A: FromOptions + 'static,
336 {
337 self.on_process = Some(Box::new(move |ctx, interaction, map| {
339 Box::pin(async move {
340 let args = A::from_options(&interaction)?;
341 (on_process)(ctx, interaction, map, args).await
342 })
343 }));
344
345 self
346 }
347
348 pub fn build(&mut self) -> Result<HelpCommand, BuilderError> {
350 #[allow(clippy::or_fun_call)]
351 let description = self
352 .description
353 .take()
354 .ok_or(BuilderError::MissingField("description"))?;
355 #[allow(clippy::or_fun_call)]
356 let on_process = self
357 .on_process
358 .take()
359 .ok_or(BuilderError::MissingField("on_process"))?;
360
361 Ok(HelpCommand {
362 description: description.into(),
363 arguments: std::mem::take(&mut self.arguments).into_boxed_slice(),
364
365 on_process,
366 })
367 }
368}
369
370impl std::fmt::Debug for HelpCommandBuilder<'_> {
371 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
372 f.debug_struct("HelpCommandBuilder")
373 .field("description", &self.description)
374 .field("arguments", &self.arguments)
375 .field("on_process", &self.on_process.as_ref().map(|_| "<func>"))
376 .finish()
377 }
378}
379
380impl Default for HelpCommandBuilder<'_> {
381 fn default() -> Self {
382 Self::new()
383 }
384}