Skip to main content

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