serenity/builder/
create_command.rs

1#[cfg(feature = "http")]
2use super::Builder;
3#[cfg(feature = "http")]
4use crate::http::CacheHttp;
5use crate::internal::prelude::*;
6use crate::model::prelude::*;
7
8/// A builder for creating a new [`CommandOption`].
9///
10/// [`Self::kind`], [`Self::name`], and [`Self::description`] are required fields.
11///
12/// [`CommandOption`]: crate::model::application::CommandOption
13///
14/// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure).
15#[derive(Clone, Debug, Serialize)]
16#[must_use]
17pub struct CreateCommandOption(CommandOption);
18
19impl CreateCommandOption {
20    /// Creates a new builder with the given option type, name, and description, leaving all other
21    /// fields empty.
22    pub fn new(
23        kind: CommandOptionType,
24        name: impl Into<String>,
25        description: impl Into<String>,
26    ) -> Self {
27        Self(CommandOption {
28            kind,
29            name: name.into(),
30            name_localizations: None,
31            description: description.into(),
32            description_localizations: None,
33            required: false,
34            autocomplete: false,
35            min_value: None,
36            max_value: None,
37            min_length: None,
38            max_length: None,
39
40            channel_types: Vec::new(),
41            choices: Vec::new(),
42            options: Vec::new(),
43        })
44    }
45
46    /// Sets the `CommandOptionType`, replacing the current value as set in [`Self::new`].
47    pub fn kind(mut self, kind: CommandOptionType) -> Self {
48        self.0.kind = kind;
49        self
50    }
51
52    /// Sets the name of the option, replacing the current value as set in [`Self::new`].
53    ///
54    /// **Note**: Must be between 1 and 32 lowercase characters, matching `r"^[\w-]{1,32}$"`.
55    pub fn name(mut self, name: impl Into<String>) -> Self {
56        self.0.name = name.into();
57        self
58    }
59
60    /// Specifies a localized name of the option.
61    ///
62    /// ```rust
63    /// # use serenity::builder::CreateCommandOption;
64    /// # use serenity::model::application::CommandOptionType;
65    /// # CreateCommandOption::new(CommandOptionType::Integer, "", "")
66    /// .name("age")
67    /// .name_localized("zh-CN", "岁数")
68    /// # ;
69    /// ```
70    pub fn name_localized(mut self, locale: impl Into<String>, name: impl Into<String>) -> Self {
71        let map = self.0.name_localizations.get_or_insert_with(Default::default);
72        map.insert(locale.into(), name.into());
73        self
74    }
75
76    /// Sets the description for the option, replacing the current value as set in [`Self::new`].
77    ///
78    /// **Note**: Must be between 1 and 100 characters.
79    pub fn description(mut self, description: impl Into<String>) -> Self {
80        self.0.description = description.into();
81        self
82    }
83    /// Specifies a localized description of the option.
84    ///
85    /// ```rust
86    /// # use serenity::builder::CreateCommandOption;
87    /// # use serenity::model::application::CommandOptionType;
88    /// # CreateCommandOption::new(CommandOptionType::String, "", "")
89    /// .description("Wish a friend a happy birthday")
90    /// .description_localized("zh-CN", "祝你朋友生日快乐")
91    /// # ;
92    /// ```
93    pub fn description_localized(
94        mut self,
95        locale: impl Into<String>,
96        description: impl Into<String>,
97    ) -> Self {
98        let map = self.0.description_localizations.get_or_insert_with(Default::default);
99        map.insert(locale.into(), description.into());
100        self
101    }
102
103    /// Sets if this option is required or optional.
104    ///
105    /// **Note**: This defaults to `false`.
106    pub fn required(mut self, required: bool) -> Self {
107        self.0.required = required;
108        self
109    }
110
111    /// Adds an optional int-choice.
112    ///
113    /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100
114    /// characters. Value must be between -2^53 and 2^53.
115    pub fn add_int_choice(self, name: impl Into<String>, value: i32) -> Self {
116        self.add_choice(CommandOptionChoice {
117            name: name.into(),
118            value: Value::from(value),
119            name_localizations: None,
120        })
121    }
122
123    /// Adds a localized optional int-choice. See [`Self::add_int_choice`] for more info.
124    pub fn add_int_choice_localized(
125        self,
126        name: impl Into<String>,
127        value: i32,
128        locales: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
129    ) -> Self {
130        self.add_choice(CommandOptionChoice {
131            name: name.into(),
132            value: Value::from(value),
133            name_localizations: Some(
134                locales.into_iter().map(|(l, n)| (l.into(), n.into())).collect(),
135            ),
136        })
137    }
138
139    /// Adds an optional string-choice.
140    ///
141    /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100
142    /// characters. Value must be up to 100 characters.
143    pub fn add_string_choice(self, name: impl Into<String>, value: impl Into<String>) -> Self {
144        self.add_choice(CommandOptionChoice {
145            name: name.into(),
146            value: Value::String(value.into()),
147            name_localizations: None,
148        })
149    }
150
151    /// Adds a localized optional string-choice. See [`Self::add_string_choice`] for more info.
152    pub fn add_string_choice_localized(
153        self,
154        name: impl Into<String>,
155        value: impl Into<String>,
156        locales: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
157    ) -> Self {
158        self.add_choice(CommandOptionChoice {
159            name: name.into(),
160            value: Value::String(value.into()),
161            name_localizations: Some(
162                locales.into_iter().map(|(l, n)| (l.into(), n.into())).collect(),
163            ),
164        })
165    }
166
167    /// Adds an optional number-choice.
168    ///
169    /// **Note**: There can be no more than 25 choices set. Name must be between 1 and 100
170    /// characters. Value must be between -2^53 and 2^53.
171    pub fn add_number_choice(self, name: impl Into<String>, value: f64) -> Self {
172        self.add_choice(CommandOptionChoice {
173            name: name.into(),
174            value: Value::from(value),
175            name_localizations: None,
176        })
177    }
178
179    /// Adds a localized optional number-choice. See [`Self::add_number_choice`] for more info.
180    pub fn add_number_choice_localized(
181        self,
182        name: impl Into<String>,
183        value: f64,
184        locales: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
185    ) -> Self {
186        self.add_choice(CommandOptionChoice {
187            name: name.into(),
188            value: Value::from(value),
189            name_localizations: Some(
190                locales.into_iter().map(|(l, n)| (l.into(), n.into())).collect(),
191            ),
192        })
193    }
194
195    fn add_choice(mut self, value: CommandOptionChoice) -> Self {
196        self.0.choices.push(value);
197        self
198    }
199
200    /// Optionally enable/disable autocomplete interactions for this option.
201    ///
202    /// **Notes**:
203    /// - May not be set to `true` if `choices` are set
204    /// - Options using `autocomplete` are not confined to only use given choices
205    pub fn set_autocomplete(mut self, value: bool) -> Self {
206        self.0.autocomplete = value;
207        self
208    }
209
210    /// If the option is a [`SubCommandGroup`] or [`SubCommand`], nested options are its parameters.
211    ///
212    /// This will overwrite any existing sub-options. To add a sub-option to the existing list, use
213    /// [`Self::add_sub_option`].
214    ///
215    /// **Note**: A command can have up to 25 subcommand groups, or subcommands. A subcommand group
216    /// can have up to 25 subcommands. A subcommand can have up to 25 options.
217    ///
218    /// [`SubCommandGroup`]: crate::model::application::CommandOptionType::SubCommandGroup
219    /// [`SubCommand`]: crate::model::application::CommandOptionType::SubCommand
220    pub fn set_sub_options(
221        mut self,
222        sub_options: impl IntoIterator<Item = CreateCommandOption>,
223    ) -> Self {
224        self.0.options = sub_options.into_iter().map(|o| o.0).collect();
225        self
226    }
227
228    /// If the option is a [`SubCommandGroup`] or [`SubCommand`], nested options are its parameters.
229    ///
230    /// **Note**: A command can have up to 25 subcommand groups, or subcommands. A subcommand group
231    /// can have up to 25 subcommands. A subcommand can have up to 25 options.
232    ///
233    /// [`SubCommandGroup`]: crate::model::application::CommandOptionType::SubCommandGroup
234    /// [`SubCommand`]: crate::model::application::CommandOptionType::SubCommand
235    pub fn add_sub_option(mut self, sub_option: CreateCommandOption) -> Self {
236        self.0.options.push(sub_option.0);
237        self
238    }
239
240    /// If the option is a [`Channel`], it will only be able to show these types.
241    ///
242    /// [`Channel`]: crate::model::application::CommandOptionType::Channel
243    pub fn channel_types(mut self, channel_types: Vec<ChannelType>) -> Self {
244        self.0.channel_types = channel_types;
245        self
246    }
247
248    /// Sets the minimum permitted value for this integer option
249    pub fn min_int_value(mut self, value: u64) -> Self {
250        self.0.min_value = Some(value.into());
251        self
252    }
253
254    /// Sets the maximum permitted value for this integer option
255    pub fn max_int_value(mut self, value: u64) -> Self {
256        self.0.max_value = Some(value.into());
257        self
258    }
259
260    /// Sets the minimum permitted value for this number option
261    pub fn min_number_value(mut self, value: f64) -> Self {
262        self.0.min_value = serde_json::Number::from_f64(value);
263        self
264    }
265
266    /// Sets the maximum permitted value for this number option
267    pub fn max_number_value(mut self, value: f64) -> Self {
268        self.0.max_value = serde_json::Number::from_f64(value);
269        self
270    }
271
272    /// Sets the minimum permitted length for this string option.
273    ///
274    /// The value of `min_length` must be greater or equal to `0`.
275    pub fn min_length(mut self, value: u16) -> Self {
276        self.0.min_length = Some(value);
277
278        self
279    }
280
281    /// Sets the maximum permitted length for this string option.
282    ///
283    /// The value of `max_length` must be greater or equal to `1`.
284    pub fn max_length(mut self, value: u16) -> Self {
285        self.0.max_length = Some(value);
286
287        self
288    }
289}
290
291/// A builder for creating a new [`Command`].
292///
293/// [`Self::name`] and [`Self::description`] are required fields.
294///
295/// [`Command`]: crate::model::application::Command
296///
297/// Discord docs:
298/// - [global command](https://discord.com/developers/docs/interactions/application-commands#create-global-application-command-json-params)
299/// - [guild command](https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command-json-params)
300#[derive(Clone, Debug, Serialize)]
301#[must_use]
302pub struct CreateCommand {
303    name: String,
304    name_localizations: HashMap<String, String>,
305    #[serde(skip_serializing_if = "Option::is_none")]
306    description: Option<String>,
307    description_localizations: HashMap<String, String>,
308    options: Vec<CreateCommandOption>,
309    #[serde(skip_serializing_if = "Option::is_none")]
310    default_member_permissions: Option<String>,
311    #[serde(skip_serializing_if = "Option::is_none")]
312    dm_permission: Option<bool>,
313    #[serde(skip_serializing_if = "Option::is_none")]
314    #[serde(rename = "type")]
315    kind: Option<CommandType>,
316    #[serde(skip_serializing_if = "Option::is_none")]
317    integration_types: Option<Vec<InstallationContext>>,
318    #[serde(skip_serializing_if = "Option::is_none")]
319    contexts: Option<Vec<InteractionContext>>,
320    nsfw: bool,
321    #[serde(skip_serializing_if = "Option::is_none")]
322    handler: Option<EntryPointHandlerType>,
323}
324
325impl CreateCommand {
326    /// Creates a new builder with the given name, leaving all other fields empty.
327    pub fn new(name: impl Into<String>) -> Self {
328        Self {
329            kind: None,
330
331            name: name.into(),
332            name_localizations: HashMap::new(),
333            description: None,
334            description_localizations: HashMap::new(),
335            default_member_permissions: None,
336            dm_permission: None,
337
338            integration_types: None,
339            contexts: None,
340
341            options: Vec::new(),
342            nsfw: false,
343            handler: None,
344        }
345    }
346
347    /// Specifies the name of the application command, replacing the current value as set in
348    /// [`Self::new`].
349    ///
350    /// **Note**: Must be between 1 and 32 lowercase characters, matching `r"^[\w-]{1,32}$"`. Two
351    /// global commands of the same app cannot have the same name. Two guild-specific commands of
352    /// the same app cannot have the same name.
353    pub fn name(mut self, name: impl Into<String>) -> Self {
354        self.name = name.into();
355        self
356    }
357
358    /// Specifies a localized name of the application command.
359    ///
360    /// ```rust
361    /// # serenity::builder::CreateCommand::new("")
362    /// .name("birthday")
363    /// .name_localized("zh-CN", "生日")
364    /// .name_localized("el", "γενέθλια")
365    /// # ;
366    /// ```
367    pub fn name_localized(mut self, locale: impl Into<String>, name: impl Into<String>) -> Self {
368        self.name_localizations.insert(locale.into(), name.into());
369        self
370    }
371
372    /// Specifies the type of the application command.
373    pub fn kind(mut self, kind: CommandType) -> Self {
374        self.kind = Some(kind);
375        self
376    }
377
378    /// Specifies the default permissions required to execute the command.
379    pub fn default_member_permissions(mut self, permissions: Permissions) -> Self {
380        self.default_member_permissions = Some(permissions.bits().to_string());
381        self
382    }
383
384    /// Specifies if the command is available in DMs.
385    #[cfg_attr(feature = "unstable_discord_api", deprecated = "Use contexts instead")]
386    pub fn dm_permission(mut self, enabled: bool) -> Self {
387        self.dm_permission = Some(enabled);
388        self
389    }
390
391    /// Specifies the description of the application command.
392    ///
393    /// **Note**: Must be between 1 and 100 characters long.
394    pub fn description(mut self, description: impl Into<String>) -> Self {
395        self.description = Some(description.into());
396        self
397    }
398
399    /// Specifies a localized description of the application command.
400    ///
401    /// ```rust
402    /// # serenity::builder::CreateCommand::new("")
403    /// .description("Wish a friend a happy birthday")
404    /// .description_localized("zh-CN", "祝你朋友生日快乐")
405    /// # ;
406    /// ```
407    pub fn description_localized(
408        mut self,
409        locale: impl Into<String>,
410        description: impl Into<String>,
411    ) -> Self {
412        self.description_localizations.insert(locale.into(), description.into());
413        self
414    }
415
416    /// Adds an application command option for the application command.
417    ///
418    /// **Note**: Application commands can have up to 25 options.
419    pub fn add_option(mut self, option: CreateCommandOption) -> Self {
420        self.options.push(option);
421        self
422    }
423
424    /// Sets all the application command options for the application command.
425    ///
426    /// **Note**: Application commands can have up to 25 options.
427    pub fn set_options(mut self, options: Vec<CreateCommandOption>) -> Self {
428        self.options = options;
429        self
430    }
431
432    /// Adds an installation context that this application command can be used in.
433    pub fn add_integration_type(mut self, integration_type: InstallationContext) -> Self {
434        self.integration_types.get_or_insert_with(Vec::default).push(integration_type);
435        self
436    }
437
438    /// Sets the installation contexts that this application command can be used in.
439    pub fn integration_types(mut self, integration_types: Vec<InstallationContext>) -> Self {
440        self.integration_types = Some(integration_types);
441        self
442    }
443
444    /// Adds an interaction context that this application command can be used in.
445    pub fn add_context(mut self, context: InteractionContext) -> Self {
446        self.contexts.get_or_insert_with(Vec::default).push(context);
447        self
448    }
449
450    /// Sets the interaction contexts that this application command can be used in.
451    pub fn contexts(mut self, contexts: Vec<InteractionContext>) -> Self {
452        self.contexts = Some(contexts);
453        self
454    }
455
456    /// Whether this command is marked NSFW (age-restricted)
457    pub fn nsfw(mut self, nsfw: bool) -> Self {
458        self.nsfw = nsfw;
459        self
460    }
461
462    /// Sets the command's entry point handler type. Only valid for commands of type
463    /// [`PrimaryEntryPoint`].
464    ///
465    /// [`PrimaryEntryPoint`]: CommandType::PrimaryEntryPoint
466    pub fn handler(mut self, handler: EntryPointHandlerType) -> Self {
467        self.handler = Some(handler);
468        self
469    }
470}
471
472#[cfg(feature = "http")]
473#[async_trait::async_trait]
474impl Builder for CreateCommand {
475    type Context<'ctx> = (Option<GuildId>, Option<CommandId>);
476    type Built = Command;
477
478    /// Create a [`Command`], overriding an existing one with the same name if it exists.
479    ///
480    /// Providing a [`GuildId`] will create a command in the corresponding [`Guild`]. Otherwise, a
481    /// global command will be created.
482    ///
483    /// Providing a [`CommandId`] will edit the corresponding command.
484    ///
485    /// # Errors
486    ///
487    /// Returns [`Error::Http`] if invalid data is given. See [Discord's docs] for more details.
488    ///
489    /// May also return [`Error::Json`] if there is an error in deserializing the API response.
490    ///
491    /// [Discord's docs]: https://discord.com/developers/docs/interactions/slash-commands
492    async fn execute(
493        self,
494        cache_http: impl CacheHttp,
495        ctx: Self::Context<'_>,
496    ) -> Result<Self::Built> {
497        let http = cache_http.http();
498        match ctx {
499            (Some(guild_id), Some(cmd_id)) => {
500                http.edit_guild_command(guild_id, cmd_id, &self).await
501            },
502            (Some(guild_id), None) => http.create_guild_command(guild_id, &self).await,
503            (None, Some(cmd_id)) => http.edit_global_command(cmd_id, &self).await,
504            (None, None) => http.create_global_command(&self).await,
505        }
506    }
507}