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