Skip to content

Commit

Permalink
Support all interactions features (#1283)
Browse files Browse the repository at this point in the history
This commit adds support to all interactions features, including slash commands permissions, which allow to restrict slash command use to a specific user/role. It also adds the `application_id` to the Http client (and Client Builder) instead of adding it to each request.
  • Loading branch information
HarmoGlace committed Apr 23, 2021
1 parent dca5c45 commit d6e86df
Show file tree
Hide file tree
Showing 28 changed files with 2,223 additions and 276 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Expand Up @@ -264,3 +264,6 @@ jobs:
- name: 'Build example 13'
working-directory: examples
run: cargo build -p e13_parallel_loops
- name: 'Build example 14'
working-directory: examples
run: cargo build -p e14_slash_commands
1 change: 1 addition & 0 deletions examples/Cargo.toml
Expand Up @@ -13,4 +13,5 @@ members = [
"e11_gateway_intents",
"e12_global_data",
"e13_parallel_loops",
"e14_slash_commands"
]
9 changes: 9 additions & 0 deletions examples/e14_slash_commands/Cargo.toml
@@ -0,0 +1,9 @@
[package]
name = "e14_slash_commands"
version = "0.1.0"
authors = ["my name <my@email.address>"]
edition = "2018"

[dependencies]
serenity = { path = "../../", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "unstable_discord_api"] }
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
13 changes: 13 additions & 0 deletions examples/e14_slash_commands/Makefile.toml
@@ -0,0 +1,13 @@
extend = "../../Makefile.toml"

[tasks.examples_build]
alias = "build"

[tasks.examples_build_release]
alias = "build_release"

[tasks.examples_run]
alias = "run"

[tasks.examples_run_release]
alias = "run_release"
56 changes: 56 additions & 0 deletions examples/e14_slash_commands/src/main.rs
@@ -0,0 +1,56 @@
use std::env;

use serenity::{
async_trait,
client::bridge::gateway::GatewayIntents,
model::{gateway::Ready, interactions::{Interaction, InteractionResponseType, ApplicationCommand}},
prelude::*,
};

struct Handler;

#[async_trait]
impl EventHandler for Handler {
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
interaction
.create_interaction_response(&ctx.http, |response| {
response
.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|message| message.content("Received event!"))
})
.await;
}

async fn ready(&self, ctx: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);

let interactions = ApplicationCommand::get_global_application_commands(&ctx.http).await;

println!("I have the following global slash command(s): {:?}", interactions);
}
}

#[tokio::main]
async fn main() {
// Configure the client with your Discord bot token in the environment.
let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");

// The Application Id is usually the Bot User Id.
let application_id: u64 =
env::var("APPLICATION_ID").expect("Expected an application id in the environment").parse().expect("application id is not a valid id");

// Build our client.
let mut client = Client::builder(token)
.event_handler(Handler)
.application_id(application_id)
.await
.expect("Error creating client");

// Finally, start a single shard, and start listening to events.
//
// Shards will automatically attempt to reconnect, and will perform
// exponential backoff until it reconnects.
if let Err(why) = client.start().await {
println!("Client error: {:?}", why);
}
}
Expand Up @@ -4,16 +4,19 @@ use serde_json::{json, Value};

use crate::{model::interactions::ApplicationCommandOptionType, utils};

/// A builder for creating a new [`ApplicationCommandInteractionDataOption`].
/// A builder for creating a new [`ApplicationCommandOption`].
///
/// [`Self::kind`], [`Self::name`], and [`Self::description`] are required fields.
///
/// [`ApplicationCommandInteractionDataOption`]: crate::model::interactions::ApplicationCommandInteractionDataOption
/// [`ApplicationCommandOption`]: crate::model::interactions::ApplicationCommandOption
/// [`kind`]: Self::kind
/// [`name`]: Self::name
/// [`description`]: Self::description
#[derive(Clone, Debug, Default)]
pub struct CreateInteractionOption(pub HashMap<&'static str, Value>);
pub struct CreateApplicationCommandOption(pub HashMap<&'static str, Value>);

impl CreateInteractionOption {
/// Set the ApplicationCommandOptionType for the InteractionOption.
impl CreateApplicationCommandOption {
/// Sets the ApplicationCommandOptionType.
pub fn kind(&mut self, kind: ApplicationCommandOptionType) -> &mut Self {
self.0.insert("type", Value::Number(serde_json::Number::from(kind as u8)));
self
Expand All @@ -35,7 +38,7 @@ impl CreateInteractionOption {
self
}

/// The first required option for the user to complete
/// The first required option for the user to complete.
///
/// **Note**: Only one option can be `default`.
pub fn default_option(&mut self, default: bool) -> &mut Self {
Expand Down Expand Up @@ -85,14 +88,14 @@ impl CreateInteractionOption {
/// [`SubCommandGroup`]: crate::model::interactions::ApplicationCommandOptionType::SubCommandGroup
pub fn create_sub_option<F>(&mut self, f: F) -> &mut Self
where
F: FnOnce(&mut CreateInteractionOption) -> &mut CreateInteractionOption,
F: FnOnce(&mut CreateApplicationCommandOption) -> &mut CreateApplicationCommandOption,
{
let mut data = CreateInteractionOption::default();
let mut data = CreateApplicationCommandOption::default();
f(&mut data);
self.add_sub_option(data)
}

pub fn add_sub_option(&mut self, sub_option: CreateInteractionOption) -> &mut Self {
pub fn add_sub_option(&mut self, sub_option: CreateApplicationCommandOption) -> &mut Self {
let new_option = utils::hashmap_to_json_map(sub_option.0);
let options = self.0.entry("options").or_insert_with(|| Value::Array(Vec::new()));
let opt_arr = options.as_array_mut().expect("Must be an array");
Expand All @@ -108,9 +111,9 @@ impl CreateInteractionOption {
///
/// [`ApplicationCommand`]: crate::model::interactions::ApplicationCommand
#[derive(Clone, Debug, Default)]
pub struct CreateInteraction(pub HashMap<&'static str, Value>);
pub struct CreateApplicationCommand(pub HashMap<&'static str, Value>);

impl CreateInteraction {
impl CreateApplicationCommand {
/// Specify the name of the Interaction.
///
/// **Note**: Must be between 1 and 32 characters long,
Expand All @@ -120,6 +123,16 @@ impl CreateInteraction {
self
}

/// Specify if the command should not be usable by default
///
/// **Note**: Setting it to false will disable it for anyone,
/// including administrators and guild owners.
pub fn default_permission(&mut self, default_permission: bool) -> &mut Self {
self.0.insert("default_permission", Value::Bool(default_permission));

self
}

/// Specify the description of the Interaction.
///
/// **Note**: Must be between 1 and 100 characters long.
Expand All @@ -131,19 +144,19 @@ impl CreateInteraction {
/// Create an interaction option for the interaction.
///
/// **Note**: Interactions can only have up to 10 options.
pub fn create_interaction_option<F>(&mut self, f: F) -> &mut Self
pub fn create_option<F>(&mut self, f: F) -> &mut Self
where
F: FnOnce(&mut CreateInteractionOption) -> &mut CreateInteractionOption,
F: FnOnce(&mut CreateApplicationCommandOption) -> &mut CreateApplicationCommandOption,
{
let mut data = CreateInteractionOption::default();
let mut data = CreateApplicationCommandOption::default();
f(&mut data);
self.add_interaction_option(data)
self.add_option(data)
}

/// Add an interaction option for the interaction.
///
/// **Note**: Interactions can only have up to 10 options.
pub fn add_interaction_option(&mut self, option: CreateInteractionOption) -> &mut Self {
pub fn add_option(&mut self, option: CreateApplicationCommandOption) -> &mut Self {
let new_option = utils::hashmap_to_json_map(option.0);
let options = self.0.entry("options").or_insert_with(|| Value::Array(Vec::new()));
let opt_arr = options.as_array_mut().expect("Must be an array");
Expand All @@ -155,7 +168,7 @@ impl CreateInteraction {
/// Sets all the interaction options for the interaction.
///
/// **Note**: Interactions can only have up to 10 options.
pub fn set_interaction_options(&mut self, options: Vec<CreateInteractionOption>) -> &mut Self {
pub fn set_options(&mut self, options: Vec<CreateApplicationCommandOption>) -> &mut Self {
let new_options = options
.into_iter()
.map(|f| Value::Object(utils::hashmap_to_json_map(f.0)))
Expand All @@ -164,3 +177,47 @@ impl CreateInteraction {
self
}
}

#[derive(Clone, Debug, Default)]
pub struct CreateApplicationCommands(pub Vec<Value>);

impl CreateApplicationCommands {
/// Creates a new application command.
pub fn create_application_command<F>(&mut self, f: F) -> &mut Self
where
F: FnOnce(&mut CreateApplicationCommand) -> &mut CreateApplicationCommand,
{
let mut data = CreateApplicationCommand::default();
f(&mut data);

self.add_application_command(data);

self
}

/// Adds a new application command.
pub fn add_application_command(&mut self, interaction: CreateApplicationCommand) -> &mut Self {
let new_data = Value::Object(utils::hashmap_to_json_map(interaction.0));

self.0.push(new_data);

self
}

/// Sets all the application commands.
pub fn set_application_commands(
&mut self,
interactions: Vec<CreateApplicationCommand>,
) -> &mut Self {
let new_application_command = interactions
.into_iter()
.map(|f| Value::Object(utils::hashmap_to_json_map(f.0)))
.collect::<Vec<Value>>();

for application_command in new_application_command {
self.0.push(application_command);
}

self
}
}

0 comments on commit d6e86df

Please sign in to comment.