Skip to main content

twilight_http/request/application/command/create_global_command/
chat_input.rs

1use super::super::CommandBorrowed;
2#[cfg(not(target_os = "wasi"))]
3use crate::response::{Response, ResponseFuture};
4use crate::{
5    client::Client,
6    error::Error,
7    request::{Request, TryIntoRequest},
8    routing::Route,
9};
10use std::{collections::HashMap, future::IntoFuture};
11use twilight_model::{
12    application::command::{Command, CommandOption, CommandType},
13    guild::Permissions,
14    id::{Id, marker::ApplicationMarker},
15};
16use twilight_validate::command::{
17    CommandValidationError, chat_input_name as validate_chat_input_name,
18    description as validate_description, options as validate_options,
19};
20
21struct CreateGlobalChatInputCommandFields<'a> {
22    default_member_permissions: Option<Permissions>,
23    description: &'a str,
24    description_localizations: Option<&'a HashMap<String, String>>,
25    dm_permission: Option<bool>,
26    name: &'a str,
27    name_localizations: Option<&'a HashMap<String, String>>,
28    nsfw: Option<bool>,
29    options: Option<&'a [CommandOption]>,
30}
31
32/// Create a new chat input global command.
33///
34/// The description must be between 1 and 100 characters in length. Creating a
35/// command with the same name as an already-existing global command will
36/// overwrite the old command. See
37/// [Discord Docs/Create Global Application Command].
38///
39/// [Discord Docs/Create Global Application Command]: https://discord.com/developers/docs/interactions/application-commands#create-global-application-command
40#[must_use = "requests must be configured and executed"]
41pub struct CreateGlobalChatInputCommand<'a> {
42    application_id: Id<ApplicationMarker>,
43    fields: Result<CreateGlobalChatInputCommandFields<'a>, CommandValidationError>,
44    http: &'a Client,
45}
46
47impl<'a> CreateGlobalChatInputCommand<'a> {
48    pub(crate) fn new(
49        http: &'a Client,
50        application_id: Id<ApplicationMarker>,
51        name: &'a str,
52        description: &'a str,
53    ) -> Self {
54        let fields = Ok(CreateGlobalChatInputCommandFields {
55            default_member_permissions: None,
56            description,
57            description_localizations: None,
58            dm_permission: None,
59            name,
60            name_localizations: None,
61            nsfw: None,
62            options: None,
63        })
64        .and_then(|fields| {
65            validate_description(description)?;
66
67            validate_chat_input_name(name)?;
68
69            Ok(fields)
70        });
71
72        Self {
73            application_id,
74            fields,
75            http,
76        }
77    }
78
79    /// Add a list of command options.
80    ///
81    /// Required command options must be added before optional options.
82    ///
83    /// # Errors
84    ///
85    /// Returns an error of type [`OptionsRequiredFirst`] if a required option
86    /// was added after an optional option. The problem option's index is
87    /// provided.
88    ///
89    /// [`OptionsRequiredFirst`]: twilight_validate::command::CommandValidationErrorType::OptionsRequiredFirst
90    pub fn command_options(mut self, options: &'a [CommandOption]) -> Self {
91        self.fields = self.fields.and_then(|mut fields| {
92            validate_options(options)?;
93
94            fields.options = Some(options);
95
96            Ok(fields)
97        });
98
99        self
100    }
101
102    /// Default permissions required for a member to run the command.
103    ///
104    /// Defaults to [`None`].
105    pub const fn default_member_permissions(mut self, default: Permissions) -> Self {
106        if let Ok(fields) = self.fields.as_mut() {
107            fields.default_member_permissions = Some(default);
108        }
109
110        self
111    }
112
113    /// Set whether the command is available in DMs.
114    ///
115    /// Defaults to [`None`].
116    pub const fn dm_permission(mut self, dm_permission: bool) -> Self {
117        if let Ok(fields) = self.fields.as_mut() {
118            fields.dm_permission = Some(dm_permission);
119        }
120
121        self
122    }
123
124    /// Set the localization dictionary for the command description.
125    ///
126    /// Defaults to [`None`].
127    ///
128    /// # Errors
129    ///
130    /// Returns an error of type [`DescriptionInvalid`] if the description is
131    /// invalid.
132    ///
133    /// [`DescriptionInvalid`]: twilight_validate::command::CommandValidationErrorType::DescriptionInvalid
134    pub fn description_localizations(mut self, localizations: &'a HashMap<String, String>) -> Self {
135        self.fields = self.fields.and_then(|mut fields| {
136            for description in localizations.values() {
137                validate_description(description)?;
138            }
139
140            fields.description_localizations = Some(localizations);
141
142            Ok(fields)
143        });
144
145        self
146    }
147
148    /// Set the localization dictionary for the command name.
149    ///
150    /// Defaults to [`None`].
151    ///
152    /// # Errors
153    ///
154    /// Returns an error of type [`NameLengthInvalid`] if the length is invalid.
155    ///
156    /// Returns an error of type [`NameCharacterInvalid`] if the name contains a
157    /// non-alphanumeric character or an uppercase character for which a
158    /// lowercase variant exists.
159    ///
160    /// [`NameLengthInvalid`]: twilight_validate::command::CommandValidationErrorType::NameLengthInvalid
161    /// [`NameCharacterInvalid`]: twilight_validate::command::CommandValidationErrorType::NameCharacterInvalid
162    pub fn name_localizations(mut self, localizations: &'a HashMap<String, String>) -> Self {
163        self.fields = self.fields.and_then(|mut fields| {
164            for name in localizations.values() {
165                validate_chat_input_name(name)?;
166            }
167
168            fields.name_localizations = Some(localizations);
169
170            Ok(fields)
171        });
172
173        self
174    }
175
176    /// Set whether the command is age-restricted.
177    ///
178    /// Defaults to not being specified, which uses Discord's default.
179    pub const fn nsfw(mut self, nsfw: bool) -> Self {
180        if let Ok(fields) = self.fields.as_mut() {
181            fields.nsfw = Some(nsfw);
182        }
183
184        self
185    }
186}
187
188#[cfg(not(target_os = "wasi"))]
189impl IntoFuture for CreateGlobalChatInputCommand<'_> {
190    type Output = Result<Response<Command>, Error>;
191
192    type IntoFuture = ResponseFuture<Command>;
193
194    fn into_future(self) -> Self::IntoFuture {
195        let http = self.http;
196
197        match self.try_into_request() {
198            Ok(request) => http.request(request),
199            Err(source) => ResponseFuture::error(source),
200        }
201    }
202}
203
204impl TryIntoRequest for CreateGlobalChatInputCommand<'_> {
205    fn try_into_request(self) -> Result<Request, Error> {
206        let fields = self.fields.map_err(Error::validation)?;
207
208        Request::builder(&Route::CreateGlobalCommand {
209            application_id: self.application_id.get(),
210        })
211        .json(&CommandBorrowed {
212            application_id: Some(self.application_id),
213            default_member_permissions: fields.default_member_permissions,
214            dm_permission: fields.dm_permission,
215            description: Some(fields.description),
216            description_localizations: fields.description_localizations,
217            kind: CommandType::ChatInput,
218            name: fields.name,
219            name_localizations: fields.name_localizations,
220            nsfw: fields.nsfw,
221            options: fields.options,
222        })
223        .build()
224    }
225}