1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use super::{CommandBorrowed, InteractionError, InteractionErrorType};
use crate::{
    client::Client,
    error::Error as HttpError,
    request::{validate, Request, RequestBuilder},
    response::ResponseFuture,
    routing::Route,
};
use twilight_model::{
    application::command::{Command, CommandOption},
    id::{ApplicationId, GuildId},
};

/// Create a new command in a guild.
///
/// The name must be between 3 and 32 characters in length, and the description
/// must be between 1 and 100 characters in length. Creating a guild command
/// with the same name as an already-existing guild command in the same guild
/// will overwrite the old command. See [the discord docs] for more information.
///
/// [the discord docs]: https://discord.com/developers/docs/interactions/slash-commands#create-guild-application-command
pub struct CreateGuildCommand<'a> {
    application_id: ApplicationId,
    default_permission: Option<bool>,
    description: &'a str,
    guild_id: GuildId,
    http: &'a Client,
    name: &'a str,
    options: Option<&'a [CommandOption]>,
}

impl<'a> CreateGuildCommand<'a> {
    pub(crate) fn new(
        http: &'a Client,
        application_id: ApplicationId,
        guild_id: GuildId,
        name: &'a str,
        description: &'a str,
    ) -> Result<Self, InteractionError> {
        if !validate::command_name(name) {
            return Err(InteractionError {
                kind: InteractionErrorType::CommandNameValidationFailed,
            });
        }

        if !validate::command_description(description) {
            return Err(InteractionError {
                kind: InteractionErrorType::CommandDescriptionValidationFailed,
            });
        }

        Ok(Self {
            application_id,
            default_permission: None,
            description,
            guild_id,
            http,
            name,
            options: None,
        })
    }

    /// Whether the command is enabled by default when the app is added to
    /// a guild.
    pub fn default_permission(mut self, default: bool) -> Self {
        self.default_permission.replace(default);

        self
    }

    /// Add a list of command options.
    ///
    /// Required command options must be added before optional options.
    ///
    /// Errors
    ///
    /// Returns an [`InteractionErrorType::CommandOptionsRequiredFirst`]
    /// if a required option was added after an optional option. The problem
    /// option's index is provided.
    pub const fn command_options(
        mut self,
        options: &'a [CommandOption],
    ) -> Result<Self, InteractionError> {
        let mut optional_option_added = false;
        let mut idx = 0;

        while idx < options.len() {
            let option = &options[idx];

            if !optional_option_added && !option.is_required() {
                optional_option_added = true;
            }

            if option.is_required() && optional_option_added {
                return Err(InteractionError {
                    kind: InteractionErrorType::CommandOptionsRequiredFirst { index: idx },
                });
            }

            idx += 1;
        }

        self.options = Some(options);

        Ok(self)
    }

    fn request(&self) -> Result<Request, HttpError> {
        Request::builder(&Route::CreateGuildCommand {
            application_id: self.application_id.0,
            guild_id: self.guild_id.0,
        })
        .json(&CommandBorrowed {
            application_id: Some(self.application_id),
            default_permission: self.default_permission,
            description: self.description,
            name: self.name,
            options: self.options,
        })
        .map(RequestBuilder::build)
    }

    /// Execute the request, returning a future resolving to a [`Response`].
    ///
    /// [`Response`]: crate::response::Response
    pub fn exec(self) -> ResponseFuture<Command> {
        match self.request() {
            Ok(request) => self.http.request(request),
            Err(source) => ResponseFuture::error(source),
        }
    }
}