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

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