serenity/model/application/command.rs
1use std::collections::HashMap;
2
3use serde::Serialize;
4
5use super::{InstallationContext, InteractionContext};
6#[cfg(feature = "model")]
7use crate::builder::{Builder, CreateCommand};
8#[cfg(feature = "model")]
9use crate::http::{CacheHttp, Http};
10use crate::internal::prelude::*;
11use crate::model::channel::ChannelType;
12use crate::model::id::{
13 ApplicationId,
14 CommandId,
15 CommandPermissionId,
16 CommandVersionId,
17 GuildId,
18 RoleId,
19 UserId,
20};
21use crate::model::Permissions;
22
23/// The base command model that belongs to an application.
24///
25/// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-structure).
26#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
27#[derive(Clone, Debug, Deserialize, Serialize)]
28#[non_exhaustive]
29pub struct Command {
30 /// The command Id.
31 pub id: CommandId,
32 /// The application command kind.
33 #[serde(rename = "type")]
34 pub kind: CommandType,
35 /// The parent application Id.
36 pub application_id: ApplicationId,
37 /// The command guild Id, if it is a guild command.
38 ///
39 /// **Note**: It may only be present if it is received through the gateway.
40 pub guild_id: Option<GuildId>,
41 /// The command name.
42 pub name: String,
43 /// The localized command name of the selected locale.
44 ///
45 /// If the name is localized, either this field or [`Self::name_localizations`] is set,
46 /// depending on which endpoint this data was retrieved from
47 /// ([source](https://discord.com/developers/docs/interactions/application-commands#retrieving-localized-commands)).
48 pub name_localized: Option<String>,
49 /// All localized command names.
50 ///
51 /// If the name is localized, either this field or [`Self::name_localized`] is set, depending
52 /// on which endpoint this data was retrieved from
53 /// ([source](https://discord.com/developers/docs/interactions/application-commands#retrieving-localized-commands)).
54 pub name_localizations: Option<HashMap<String, String>>,
55 /// The command description.
56 pub description: String,
57 /// The localized command description of the selected locale.
58 ///
59 /// If the description is localized, either this field or [`Self::description_localizations`]
60 /// is set, depending on which endpoint this data was retrieved from
61 /// ([source](https://discord.com/developers/docs/interactions/application-commands#retrieving-localized-commands)).
62 pub description_localized: Option<String>,
63 /// All localized command descriptions.
64 ///
65 /// If the description is localized, either this field or [`Self::description_localized`] is
66 /// set, depending on which endpoint this data was retrieved from
67 /// ([source](https://discord.com/developers/docs/interactions/application-commands#retrieving-localized-commands)).
68 pub description_localizations: Option<HashMap<String, String>>,
69 /// The parameters for the command.
70 #[serde(default)]
71 pub options: Vec<CommandOption>,
72 /// The default permissions required to execute the command.
73 pub default_member_permissions: Option<Permissions>,
74 /// Indicates whether the command is available in DMs with the app, only for globally-scoped
75 /// commands. By default, commands are visible.
76 #[serde(default)]
77 #[cfg_attr(
78 all(not(ignore_serenity_deprecated), feature = "unstable_discord_api"),
79 deprecated = "Use Command::contexts"
80 )]
81 pub dm_permission: Option<bool>,
82 /// Indicates whether the command is [age-restricted](https://discord.com/developers/docs/interactions/application-commands#agerestricted-commands),
83 /// defaults to false.
84 #[serde(default)]
85 pub nsfw: bool,
86 /// Installation context(s) where the command is available, only for globally-scoped commands.
87 ///
88 /// Defaults to [`InstallationContext::Guild`] and [`InstallationContext::User`].
89 #[serde(default)]
90 pub integration_types: Vec<InstallationContext>,
91 /// Interaction context(s) where the command can be used, only for globally-scoped commands.
92 ///
93 /// By default, all interaction context types are included.
94 pub contexts: Option<Vec<InteractionContext>>,
95 /// An autoincremented version identifier updated during substantial record changes.
96 pub version: CommandVersionId,
97 /// Only present for commands of type [`PrimaryEntryPoint`].
98 ///
99 /// [`PrimaryEntryPoint`]: CommandType::PrimaryEntryPoint
100 pub handler: Option<EntryPointHandlerType>,
101}
102
103#[cfg(feature = "model")]
104impl Command {
105 /// Create a global [`Command`], overriding an existing one with the same name if it exists.
106 ///
107 /// When a created [`Command`] is used, the [`InteractionCreate`] event will be emitted.
108 ///
109 /// **Note**: Global commands may take up to an hour to be updated in the user slash commands
110 /// list. If an outdated command data is sent by a user, discord will consider it as an error
111 /// and then will instantly update that command.
112 ///
113 /// As such, it is recommended that guild application commands be used for testing purposes.
114 ///
115 /// # Examples
116 ///
117 /// Create a simple ping command:
118 ///
119 /// ```rust,no_run
120 /// # use serenity::http::Http;
121 /// # use std::sync::Arc;
122 /// #
123 /// # async fn run() {
124 /// # let http: Arc<Http> = unimplemented!();
125 /// use serenity::builder::CreateCommand;
126 /// use serenity::model::application::Command;
127 /// use serenity::model::id::ApplicationId;
128 ///
129 /// let builder = CreateCommand::new("ping").description("A simple ping command");
130 /// let _ = Command::create_global_command(&http, builder).await;
131 /// # }
132 /// ```
133 ///
134 /// Create a command that echoes what is inserted:
135 ///
136 /// ```rust,no_run
137 /// # use serenity::http::Http;
138 /// # use std::sync::Arc;
139 /// #
140 /// # async fn run() {
141 /// # let http: Arc<Http> = unimplemented!();
142 /// use serenity::builder::{CreateCommand, CreateCommandOption as CreateOption};
143 /// use serenity::model::application::{Command, CommandOptionType};
144 /// use serenity::model::id::ApplicationId;
145 ///
146 /// let builder =
147 /// CreateCommand::new("echo").description("Makes the bot send a message").add_option(
148 /// CreateOption::new(CommandOptionType::String, "message", "The message to send")
149 /// .required(true),
150 /// );
151 /// let _ = Command::create_global_command(&http, builder).await;
152 /// # }
153 /// ```
154 ///
155 /// # Errors
156 ///
157 /// See [`CreateCommand::execute`] for a list of possible errors.
158 ///
159 /// [`InteractionCreate`]: crate::client::EventHandler::interaction_create
160 pub async fn create_global_command(
161 cache_http: impl CacheHttp,
162 builder: CreateCommand,
163 ) -> Result<Command> {
164 builder.execute(cache_http, (None, None)).await
165 }
166
167 /// Override all global application commands.
168 ///
169 /// # Errors
170 ///
171 /// Returns the same errors as [`Self::create_global_command`].
172 pub async fn set_global_commands(
173 http: impl AsRef<Http>,
174 commands: Vec<CreateCommand>,
175 ) -> Result<Vec<Command>> {
176 http.as_ref().create_global_commands(&commands).await
177 }
178
179 /// Edit a global command, given its Id.
180 ///
181 /// # Errors
182 ///
183 /// See [`CreateCommand::execute`] for a list of possible errors.
184 pub async fn edit_global_command(
185 cache_http: impl CacheHttp,
186 command_id: CommandId,
187 builder: CreateCommand,
188 ) -> Result<Command> {
189 builder.execute(cache_http, (None, Some(command_id))).await
190 }
191
192 /// Gets all global commands.
193 ///
194 /// # Errors
195 ///
196 /// If there is an error, it will be either [`Error::Http`] or [`Error::Json`].
197 pub async fn get_global_commands(http: impl AsRef<Http>) -> Result<Vec<Command>> {
198 http.as_ref().get_global_commands().await
199 }
200
201 /// Gets all global commands with localizations.
202 ///
203 /// # Errors
204 ///
205 /// If there is an error, it will be either [`Error::Http`] or [`Error::Json`].
206 pub async fn get_global_commands_with_localizations(
207 http: impl AsRef<Http>,
208 ) -> Result<Vec<Command>> {
209 http.as_ref().get_global_commands_with_localizations().await
210 }
211
212 /// Gets a global command by its Id.
213 ///
214 /// # Errors
215 ///
216 /// If there is an error, it will be either [`Error::Http`] or [`Error::Json`].
217 pub async fn get_global_command(
218 http: impl AsRef<Http>,
219 command_id: CommandId,
220 ) -> Result<Command> {
221 http.as_ref().get_global_command(command_id).await
222 }
223
224 /// Deletes a global command by its Id.
225 ///
226 /// # Errors
227 ///
228 /// If there is an error, it will be either [`Error::Http`] or [`Error::Json`].
229 pub async fn delete_global_command(
230 http: impl AsRef<Http>,
231 command_id: CommandId,
232 ) -> Result<()> {
233 http.as_ref().delete_global_command(command_id).await
234 }
235}
236
237enum_number! {
238 /// The type of an application command.
239 ///
240 /// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types).
241 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
242 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
243 #[serde(from = "u8", into = "u8")]
244 #[non_exhaustive]
245 pub enum CommandType {
246 ChatInput = 1,
247 User = 2,
248 Message = 3,
249 PrimaryEntryPoint = 4,
250 _ => Unknown(u8),
251 }
252}
253
254enum_number! {
255 /// Signifies how the invocation of a command of type [`PrimaryEntryPoint`] should be handled.
256 ///
257 /// [`PrimaryEntryPoint`]: CommandType::PrimaryEntryPoint
258 /// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-object-entry-point-command-handler-types)
259 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
260 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
261 #[serde(from = "u8", into = "u8")]
262 #[non_exhaustive]
263 pub enum EntryPointHandlerType {
264 AppHandler = 1,
265 DiscordLaunchActivity = 2,
266 _ => Unknown(u8),
267 }
268}
269
270/// The parameters for an [`Command`].
271///
272/// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-structure).
273#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
274#[derive(Clone, Debug, Deserialize, Serialize)]
275#[non_exhaustive]
276pub struct CommandOption {
277 /// The option type.
278 #[serde(rename = "type")]
279 pub kind: CommandOptionType,
280 /// The option name.
281 pub name: String,
282 /// Localizations of the option name with locale as the key
283 #[serde(skip_serializing_if = "Option::is_none")]
284 pub name_localizations: Option<std::collections::HashMap<String, String>>,
285 /// The option description.
286 pub description: String,
287 /// Localizations of the option description with locale as the key
288 #[serde(skip_serializing_if = "Option::is_none")]
289 pub description_localizations: Option<std::collections::HashMap<String, String>>,
290 /// Whether the parameter is optional or required.
291 #[serde(default)]
292 pub required: bool,
293 /// Choices the user can pick from.
294 ///
295 /// **Note**: Only available for [`String`] and [`Integer`] [`CommandOptionType`].
296 ///
297 /// [`String`]: CommandOptionType::String
298 /// [`Integer`]: CommandOptionType::Integer
299 #[serde(default)]
300 pub choices: Vec<CommandOptionChoice>,
301 /// The nested options.
302 ///
303 /// **Note**: Only available for [`SubCommand`] or [`SubCommandGroup`].
304 ///
305 /// [`SubCommand`]: CommandOptionType::SubCommand
306 /// [`SubCommandGroup`]: CommandOptionType::SubCommandGroup
307 #[serde(default)]
308 pub options: Vec<CommandOption>,
309 /// If the option is a [`Channel`], it will only be able to show these types.
310 ///
311 /// [`Channel`]: CommandOptionType::Channel
312 #[serde(default)]
313 pub channel_types: Vec<ChannelType>,
314 /// Minimum permitted value for Integer or Number options
315 #[serde(default)]
316 pub min_value: Option<serde_json::Number>,
317 /// Maximum permitted value for Integer or Number options
318 #[serde(default)]
319 pub max_value: Option<serde_json::Number>,
320 /// Minimum permitted length for String options
321 #[serde(default)]
322 pub min_length: Option<u16>,
323 /// Maximum permitted length for String options
324 #[serde(default)]
325 pub max_length: Option<u16>,
326 #[serde(default)]
327 pub autocomplete: bool,
328}
329
330enum_number! {
331 /// The type of an [`CommandOption`].
332 ///
333 /// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type).
334 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
335 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
336 #[serde(from = "u8", into = "u8")]
337 #[non_exhaustive]
338 pub enum CommandOptionType {
339 SubCommand = 1,
340 SubCommandGroup = 2,
341 String = 3,
342 Integer = 4,
343 Boolean = 5,
344 User = 6,
345 Channel = 7,
346 Role = 8,
347 Mentionable = 9,
348 Number = 10,
349 Attachment = 11,
350 _ => Unknown(u8),
351 }
352}
353
354/// The only valid values a user can pick in an [`CommandOption`].
355///
356/// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-choice-structure).
357#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
358#[derive(Clone, Debug, Deserialize, Serialize)]
359#[non_exhaustive]
360pub struct CommandOptionChoice {
361 /// The choice name.
362 pub name: String,
363 /// Localizations of the choice name, with locale as key
364 #[serde(skip_serializing_if = "Option::is_none")]
365 pub name_localizations: Option<std::collections::HashMap<String, String>>,
366 /// The choice value.
367 pub value: Value,
368}
369
370/// An [`Command`] permission.
371///
372/// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-guild-application-command-permissions-structure).
373#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
374#[derive(Clone, Debug, Deserialize, Serialize)]
375#[non_exhaustive]
376pub struct CommandPermissions {
377 /// The id of the command.
378 pub id: CommandId,
379 /// The id of the application the command belongs to.
380 pub application_id: ApplicationId,
381 /// The id of the guild.
382 pub guild_id: GuildId,
383 /// The permissions for the command in the guild.
384 pub permissions: Vec<CommandPermission>,
385}
386
387/// The [`CommandPermission`] data.
388///
389/// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permissions-structure).
390#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
391#[derive(Clone, Debug, Deserialize, Serialize)]
392#[non_exhaustive]
393pub struct CommandPermission {
394 /// The [`RoleId`] or [`UserId`], depends on `kind` value.
395 pub id: CommandPermissionId,
396 /// The type of data this permissions applies to.
397 #[serde(rename = "type")]
398 pub kind: CommandPermissionType,
399 /// Whether or not the provided data can use the command or not.
400 pub permission: bool,
401}
402
403enum_number! {
404 /// The type of a [`CommandPermission`].
405 ///
406 /// [Discord docs](https://discord.com/developers/docs/interactions/application-commands#application-command-permissions-object-application-command-permission-type).
407 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
408 #[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
409 #[serde(from = "u8", into = "u8")]
410 #[non_exhaustive]
411 pub enum CommandPermissionType {
412 Role = 1,
413 User = 2,
414 Channel = 3,
415 _ => Unknown(u8),
416 }
417}
418
419impl CommandPermissionId {
420 /// Converts this [`CommandPermissionId`] to [`UserId`].
421 #[must_use]
422 pub fn to_user_id(self) -> UserId {
423 self.into()
424 }
425
426 /// Converts this [`CommandPermissionId`] to [`RoleId`].
427 #[must_use]
428 pub fn to_role_id(self) -> RoleId {
429 self.into()
430 }
431}
432
433impl From<RoleId> for CommandPermissionId {
434 fn from(id: RoleId) -> Self {
435 Self::new(id.get())
436 }
437}
438
439impl From<UserId> for CommandPermissionId {
440 fn from(id: UserId) -> Self {
441 Self::new(id.get())
442 }
443}
444
445impl From<CommandPermissionId> for RoleId {
446 fn from(id: CommandPermissionId) -> Self {
447 Self::new(id.get())
448 }
449}
450
451impl From<CommandPermissionId> for UserId {
452 fn from(id: CommandPermissionId) -> Self {
453 Self::new(id.get())
454 }
455}