tulpje_framework/module/
command_builder.rs

1// Heavily influenced and largely copied from
2// https://github.com/twilight-rs/twilight/blob/main/twilight-util/src/builder/command.rs
3
4use std::collections::HashMap;
5
6use twilight_model::{
7    application::{
8        command::{Command, CommandOption, CommandOptionType, CommandType},
9        interaction::InteractionContextType,
10    },
11    guild::Permissions,
12    id::{marker::GuildMarker, Id},
13    oauth::ApplicationIntegrationType,
14};
15
16use crate::handler::command_handler::CommandFunc;
17
18#[derive(Debug, Clone)]
19pub struct CommandBuilder<T: Clone + Send + Sync> {
20    pub name: String,
21    pub name_localizations: Option<HashMap<String, String>>,
22
23    pub description: String,
24    pub description_localizations: Option<HashMap<String, String>>,
25
26    pub kind: CommandType,
27    pub guild_id: Option<Id<GuildMarker>>,
28    pub default_member_permissions: Option<Permissions>,
29    pub contexts: Option<Vec<InteractionContextType>>,
30    pub integration_types: Option<Vec<ApplicationIntegrationType>>,
31    pub nsfw: Option<bool>,
32
33    pub func: Option<CommandFunc<T>>,
34    pub groups: Vec<SubCommandGroupBuilder<T>>,
35    pub subcommands: Vec<SubCommandBuilder<T>>,
36    pub options: Vec<CommandOption>,
37}
38
39impl<T: Clone + Send + Sync> CommandBuilder<T> {
40    pub fn new(name: impl Into<String>, description: impl Into<String>, kind: CommandType) -> Self {
41        Self {
42            name: name.into(),
43            name_localizations: None,
44
45            description: description.into(),
46            description_localizations: None,
47
48            kind,
49            guild_id: None,
50            default_member_permissions: None,
51            contexts: None,
52            integration_types: None,
53            nsfw: None,
54
55            func: None,
56            groups: Vec::new(),
57            subcommands: Vec::new(),
58            options: Vec::new(),
59        }
60    }
61
62    #[must_use]
63    pub fn handler(mut self, handler: CommandFunc<T>) -> Self {
64        self.func = Some(handler);
65        self
66    }
67
68    #[must_use]
69    pub fn group(mut self, group: SubCommandGroupBuilder<T>) -> Self {
70        self.groups.push(group);
71        self
72    }
73
74    #[must_use]
75    pub fn subcommand(mut self, command: SubCommandBuilder<T>) -> Self {
76        self.subcommands.push(command);
77        self
78    }
79
80    #[must_use]
81    pub fn guild_id(mut self, guild_id: Id<GuildMarker>) -> Self {
82        self.guild_id = Some(guild_id);
83        self
84    }
85
86    #[must_use]
87    pub fn default_member_permissions(mut self, default_member_permissions: Permissions) -> Self {
88        self.default_member_permissions = Some(default_member_permissions);
89        self
90    }
91
92    #[must_use]
93    pub fn contexts(mut self, contexts: impl IntoIterator<Item = InteractionContextType>) -> Self {
94        self.contexts = Some(contexts.into_iter().collect());
95        self
96    }
97
98    #[must_use]
99    pub fn nsfw(mut self, nsfw: bool) -> Self {
100        self.nsfw = Some(nsfw);
101        self
102    }
103
104    #[must_use]
105    pub fn description_localizations<K: Into<String>, V: Into<String>>(
106        mut self,
107        localizations: impl IntoIterator<Item = (K, V)>,
108    ) -> Self {
109        self.name_localizations = Some(
110            localizations
111                .into_iter()
112                .map(|(k, v)| (k.into(), v.into()))
113                .collect(),
114        );
115        self
116    }
117
118    #[must_use]
119    pub fn name_localizations<K: Into<String>, V: Into<String>>(
120        mut self,
121        localizations: impl IntoIterator<Item = (K, V)>,
122    ) -> Self {
123        self.description_localizations = Some(
124            localizations
125                .into_iter()
126                .map(|(k, v)| (k.into(), v.into()))
127                .collect(),
128        );
129        self
130    }
131
132    #[must_use]
133    pub fn integration_types(
134        mut self,
135        integration_types: impl IntoIterator<Item = ApplicationIntegrationType>,
136    ) -> Self {
137        self.integration_types = Some(integration_types.into_iter().collect());
138        self
139    }
140
141    #[must_use]
142    pub fn option(mut self, option: impl Into<CommandOption>) -> Self {
143        self.options.push(option.into());
144        self
145    }
146
147    pub fn build(self) -> Command {
148        let group_options: Vec<CommandOption> = self.groups.into_iter().map(Into::into).collect();
149        let subcommand_options: Vec<CommandOption> =
150            self.subcommands.into_iter().map(Into::into).collect();
151
152        #[expect(
153            deprecated,
154            reason = "dm_permission is deprecated but need to specify all fields"
155        )]
156        Command {
157            name: self.name,
158            name_localizations: self.name_localizations,
159
160            description: self.description,
161            description_localizations: self.description_localizations,
162
163            guild_id: self.guild_id,
164            default_member_permissions: self.default_member_permissions,
165            contexts: self.contexts,
166            integration_types: self.integration_types,
167            nsfw: self.nsfw,
168
169            options: [self.options, group_options, subcommand_options].concat(),
170            kind: self.kind,
171
172            application_id: None,
173            id: None,
174            version: Id::new(1),
175
176            dm_permission: None,
177        }
178    }
179}
180
181impl<T: Clone + Send + Sync> From<CommandBuilder<T>> for Command {
182    fn from(value: CommandBuilder<T>) -> Self {
183        value.build()
184    }
185}
186
187#[derive(Debug, Clone)]
188pub struct SubCommandGroupBuilder<T: Clone + Send + Sync> {
189    pub name: String,
190    pub name_localizations: Option<HashMap<String, String>>,
191
192    pub description: String,
193    pub description_localizations: Option<HashMap<String, String>>,
194
195    pub commands: Vec<SubCommandBuilder<T>>,
196}
197
198impl<T: Clone + Send + Sync> SubCommandGroupBuilder<T> {
199    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
200        Self {
201            name: name.into(),
202            name_localizations: None,
203
204            description: description.into(),
205            description_localizations: None,
206
207            commands: Vec::new(),
208        }
209    }
210
211    #[must_use]
212    pub fn description_localizations<K: Into<String>, V: Into<String>>(
213        mut self,
214        localizations: impl IntoIterator<Item = (K, V)>,
215    ) -> Self {
216        self.name_localizations = Some(
217            localizations
218                .into_iter()
219                .map(|(k, v)| (k.into(), v.into()))
220                .collect(),
221        );
222        self
223    }
224
225    #[must_use]
226    pub fn name_localizations<K: Into<String>, V: Into<String>>(
227        mut self,
228        localizations: impl IntoIterator<Item = (K, V)>,
229    ) -> Self {
230        self.description_localizations = Some(
231            localizations
232                .into_iter()
233                .map(|(k, v)| (k.into(), v.into()))
234                .collect(),
235        );
236        self
237    }
238
239    #[must_use]
240    pub fn subcommand(mut self, subcommand: SubCommandBuilder<T>) -> Self {
241        self.commands.push(subcommand);
242        self
243    }
244
245    pub fn build(self) -> CommandOption {
246        CommandOption {
247            name: self.name,
248            name_localizations: self.name_localizations,
249
250            description: self.description,
251            description_localizations: self.description_localizations,
252
253            kind: CommandOptionType::SubCommandGroup,
254            options: Some(self.commands.into_iter().map(Into::into).collect()),
255
256            autocomplete: None,
257            channel_types: None,
258            choices: None,
259            max_length: None,
260            max_value: None,
261            min_length: None,
262            min_value: None,
263            required: None,
264        }
265    }
266}
267
268impl<T: Clone + Send + Sync> From<SubCommandGroupBuilder<T>> for CommandOption {
269    fn from(value: SubCommandGroupBuilder<T>) -> Self {
270        value.build()
271    }
272}
273
274#[derive(Debug, Clone)]
275pub struct SubCommandBuilder<T: Clone + Send + Sync> {
276    pub name: String,
277    pub name_localizations: Option<HashMap<String, String>>,
278
279    pub description: String,
280    pub description_localizations: Option<HashMap<String, String>>,
281
282    pub func: Option<CommandFunc<T>>,
283    pub options: Vec<CommandOption>,
284}
285
286impl<T: Clone + Send + Sync> SubCommandBuilder<T> {
287    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
288        Self {
289            name: name.into(),
290            name_localizations: None,
291
292            description: description.into(),
293            description_localizations: None,
294
295            func: None,
296            options: Vec::new(),
297        }
298    }
299
300    #[must_use]
301    pub fn handler(mut self, handler: CommandFunc<T>) -> Self {
302        self.func = Some(handler);
303        self
304    }
305
306    #[must_use]
307    pub fn description_localizations<K: Into<String>, V: Into<String>>(
308        mut self,
309        localizations: impl IntoIterator<Item = (K, V)>,
310    ) -> Self {
311        self.name_localizations = Some(
312            localizations
313                .into_iter()
314                .map(|(k, v)| (k.into(), v.into()))
315                .collect(),
316        );
317        self
318    }
319
320    #[must_use]
321    pub fn name_localizations<K: Into<String>, V: Into<String>>(
322        mut self,
323        localizations: impl IntoIterator<Item = (K, V)>,
324    ) -> Self {
325        self.description_localizations = Some(
326            localizations
327                .into_iter()
328                .map(|(k, v)| (k.into(), v.into()))
329                .collect(),
330        );
331        self
332    }
333
334    #[must_use]
335    pub fn option(mut self, option: impl Into<CommandOption>) -> Self {
336        self.options.push(option.into());
337        self
338    }
339
340    pub fn build(self) -> CommandOption {
341        CommandOption {
342            name: self.name,
343            name_localizations: self.name_localizations,
344
345            description: self.description,
346            description_localizations: self.description_localizations,
347
348            options: Some(self.options),
349            kind: CommandOptionType::SubCommand,
350
351            autocomplete: None,
352            channel_types: None,
353            choices: None,
354            max_length: None,
355            max_value: None,
356            min_length: None,
357            min_value: None,
358            required: None,
359        }
360    }
361}
362
363impl<T: Clone + Send + Sync> From<SubCommandBuilder<T>> for CommandOption {
364    fn from(value: SubCommandBuilder<T>) -> Self {
365        value.build()
366    }
367}