From 535512327c21094445b8500cd61d50a7f0436940 Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sat, 4 Feb 2023 19:51:21 +0100 Subject: [PATCH 01/16] refactor(handler): Simplify error messages for undeferred commands --- cadency_core/src/command.rs | 2 +- cadency_core/src/error.rs | 2 ++ cadency_core/src/handler/command.rs | 21 +++++++++++++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/cadency_core/src/command.rs b/cadency_core/src/command.rs index 72d5f22..43a02bb 100644 --- a/cadency_core/src/command.rs +++ b/cadency_core/src/command.rs @@ -90,7 +90,7 @@ pub(crate) async fn setup_commands(ctx: &Context) -> Result<(), serenity::Error> pub(crate) async fn command_not_implemented( ctx: &Context, - command: ApplicationCommandInteraction, + command: &ApplicationCommandInteraction, ) -> Result<(), CadencyError> { error!("The following command is not known: {:?}", command); command diff --git a/cadency_core/src/error.rs b/cadency_core/src/error.rs index f54ead2..d7602aa 100644 --- a/cadency_core/src/error.rs +++ b/cadency_core/src/error.rs @@ -10,4 +10,6 @@ pub enum CadencyError { Join, #[error("Response failed")] Response, + #[error("Command execution failed: {message}")] + Command { message: String }, } diff --git a/cadency_core/src/handler/command.rs b/cadency_core/src/handler/command.rs index 60cb485..3c53838 100644 --- a/cadency_core/src/handler/command.rs +++ b/cadency_core/src/handler/command.rs @@ -6,7 +6,7 @@ use serenity::{ use crate::{ command::{command_not_implemented, setup_commands}, - utils, + utils, CadencyError, }; use crate::utils::set_bot_presence; @@ -38,10 +38,27 @@ impl EventHandler for Handler { .find(|cadency_command| cadency_command.name() == command_name); let cmd_execution = match cmd_target { Some(target) => target.execute(&ctx, &mut command).await, - None => command_not_implemented(&ctx, command).await, + None => command_not_implemented(&ctx, &command).await, }; if let Err(execution_err) = cmd_execution { error!("❌ Command execution failed: {execution_err:?}"); + match execution_err { + CadencyError::Command { message } => { + utils::create_response(&ctx, &mut command, &message).await + } + _ => { + utils::create_response( + &ctx, + &mut command, + "**Oops! Something went terrible wrong.**", + ) + .await + } + } + .map_err(|err| { + error!("❌ Fatal error! Is discord down? {:?}", err); + }) + .expect("Unable to send response"); } }; } From 85825686ea8ab2c897331e02bf41ff75b7bdbcdd Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sat, 4 Feb 2023 19:53:00 +0100 Subject: [PATCH 02/16] refactor(commands): Delegate error messages to command handler --- cadency_commands/src/fib.rs | 12 +++--- cadency_commands/src/inspire.rs | 13 +++--- cadency_commands/src/slap.rs | 72 ++++++++++++++++----------------- 3 files changed, 46 insertions(+), 51 deletions(-) diff --git a/cadency_commands/src/fib.rs b/cadency_commands/src/fib.rs index 7c7aeac..2584e8d 100644 --- a/cadency_commands/src/fib.rs +++ b/cadency_commands/src/fib.rs @@ -46,7 +46,7 @@ impl CadencyCommand for Fib { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - let number_option = utils::get_option_value_at_position(command.data.options.as_ref(), 0) + let number = utils::get_option_value_at_position(command.data.options.as_ref(), 0) .and_then(|option_value| { if let CommandDataOptionValue::Integer(fib_value) = option_value { Some(fib_value) @@ -58,11 +58,11 @@ impl CadencyCommand for Fib { ); None } - }); - let fib_msg = match number_option { - Some(number) => Self::calc(number).to_string(), - None => "Invalid number input!".to_string(), - }; + }) + .ok_or(CadencyError::Command { + message: "Invalid number input".to_string(), + })?; + let fib_msg = Self::calc(number).to_string(); utils::create_response(ctx, command, &fib_msg).await?; Ok(()) } diff --git a/cadency_commands/src/inspire.rs b/cadency_commands/src/inspire.rs index a0c63f9..3be07f1 100644 --- a/cadency_commands/src/inspire.rs +++ b/cadency_commands/src/inspire.rs @@ -37,13 +37,12 @@ impl CadencyCommand for Inspire { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - let inspire_url = Self::request_inspire_image_url().await.map_or_else( - |err| { - error!("{:?}", err); - String::from("The source of my inspiration is currently unavailable.") - }, - |url| url, - ); + let inspire_url = Self::request_inspire_image_url().await.map_err(|err| { + error!("{:?}", err); + CadencyError::Command { + message: "**The source of my inspiration is currently unavailable :(**".to_string(), + } + })?; utils::create_response(ctx, command, &inspire_url).await?; Ok(()) } diff --git a/cadency_commands/src/slap.rs b/cadency_commands/src/slap.rs index 099ed57..b428388 100644 --- a/cadency_commands/src/slap.rs +++ b/cadency_commands/src/slap.rs @@ -36,7 +36,7 @@ impl CadencyCommand for Slap { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - let user_option = utils::get_option_value_at_position(command.data.options.as_ref(), 0) + let user = utils::get_option_value_at_position(command.data.options.as_ref(), 0) .and_then(|option_value| { if let CommandDataOptionValue::User(user, _) = option_value { Some(user) @@ -44,43 +44,39 @@ impl CadencyCommand for Slap { error!("Command option is not a user"); None } - }); - match user_option { - Some(user) => { - if user.id == command.user.id { - utils::create_response( - ctx, - command, - &format!("**Why do you want to slap yourself, {}?**", command.user), - ) - .await?; - } else if user.id.0 == command.application_id.0 { - utils::create_response( - ctx, - command, - &format!( - "**Nope!\n{} slaps {} around a bit with a large trout!**", - user, command.user - ), - ) - .await?; - } else { - utils::create_response( - ctx, - command, - &format!( - "**{} slaps {} around a bit with a large trout!**", - command.user, user - ), - ) - .await?; - } - } - None => { - error!("Invalid user input"); - utils::create_response(ctx, command, ":x: *Invalid user provided*").await?; - } - }; + }) + .ok_or(CadencyError::Command { + message: ":x: *Invalid user provided*".to_string(), + })?; + + if user.id == command.user.id { + utils::create_response( + ctx, + command, + &format!("**Why do you want to slap yourself, {}?**", command.user), + ) + .await?; + } else if user.id.0 == command.application_id.0 { + utils::create_response( + ctx, + command, + &format!( + "**Nope!\n{} slaps {} around a bit with a large trout!**", + user, command.user + ), + ) + .await?; + } else { + utils::create_response( + ctx, + command, + &format!( + "**{} slaps {} around a bit with a large trout!**", + command.user, user + ), + ) + .await?; + } Ok(()) } } From 0b00cf05afdb22f387d9649e7262e9311936dc84 Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sun, 5 Feb 2023 01:12:23 +0100 Subject: [PATCH 03/16] refactor(derive): Add name, description and deferred attributes --- cadency_codegen/src/derive.rs | 57 +++++++++++++++++++++++++-- cadency_codegen/src/lib.rs | 4 +- cadency_commands/src/lib.rs | 73 ++++++++++++++++++++++++++++++++--- cadency_core/src/command.rs | 1 + 4 files changed, 125 insertions(+), 10 deletions(-) diff --git a/cadency_codegen/src/derive.rs b/cadency_codegen/src/derive.rs index c7c205e..2211f9d 100644 --- a/cadency_codegen/src/derive.rs +++ b/cadency_codegen/src/derive.rs @@ -1,17 +1,68 @@ use proc_macro::TokenStream; -use syn::DeriveInput; +use syn::{DeriveInput, Lit, Meta}; pub(crate) fn impl_command_baseline(derive_input: DeriveInput) -> TokenStream { let struct_name = derive_input.ident; + let mut command_name = struct_name.to_string().to_lowercase(); + let mut description = "".to_string(); + let mut deferred = false; + for attr in derive_input.attrs.iter() { + let attr_meta = attr.parse_meta().unwrap(); + if let Meta::NameValue(derive_attr) = attr_meta { + match derive_attr.path.get_ident().unwrap().to_string().as_str() { + "name" => { + if let Lit::Str(name_attr_value) = derive_attr.lit { + command_name = name_attr_value.value(); + } else { + return syn::Error::new( + derive_attr.lit.span(), + "'name' attribute must be a string", + ) + .to_compile_error() + .into(); + } + } + "description" => { + if let Lit::Str(description_attr_value) = derive_attr.lit { + description = description_attr_value.value(); + } else { + return syn::Error::new( + derive_attr.lit.span(), + "'description' attribute must be a string", + ) + .to_compile_error() + .into(); + } + } + "deferred" => { + if let Lit::Bool(deferred_attr_value) = derive_attr.lit { + deferred = deferred_attr_value.value(); + } else { + return syn::Error::new( + derive_attr.lit.span(), + "'deferred' attribute must be a bool", + ) + .to_compile_error() + .into(); + } + } + _ => (), + } + } + } quote! { use cadency_core::{self, CadencyCommandBaseline}; impl cadency_core::CadencyCommandBaseline for #struct_name { fn name(&self) -> String { - String::from(stringify!(#struct_name)).to_lowercase() + String::from(#command_name) } fn description(&self) -> String { - self.description.to_string() + String::from(#description) + } + + fn deferred(&self) -> bool { + #deferred } fn options(&self) -> &Vec { diff --git a/cadency_codegen/src/lib.rs b/cadency_codegen/src/lib.rs index f1d6b91..78f15d4 100644 --- a/cadency_codegen/src/lib.rs +++ b/cadency_codegen/src/lib.rs @@ -7,10 +7,10 @@ use syn::{parse_macro_input, DeriveInput, ItemFn}; mod attribute; mod derive; -#[proc_macro_derive(CommandBaseline)] +#[proc_macro_derive(CommandBaseline, attributes(name, description, deferred))] pub fn derive_command_baseline(input_item: TokenStream) -> TokenStream { // Parse token stream into derive syntax tree - let tree: DeriveInput = parse_macro_input!(input_item as DeriveInput); + let tree: DeriveInput = parse_macro_input!(input_item); // Implement command trait derive::impl_command_baseline(tree) } diff --git a/cadency_commands/src/lib.rs b/cadency_commands/src/lib.rs index 8367494..96bf0b3 100644 --- a/cadency_commands/src/lib.rs +++ b/cadency_commands/src/lib.rs @@ -38,7 +38,6 @@ mod test { fn impl_commandbaseline_trait_with_macro() { #[derive(cadency_codegen::CommandBaseline)] struct Test { - description: String, options: Vec, } assert!(true) @@ -47,27 +46,43 @@ mod test { #[test] fn return_lowercase_struct_name_as_name() { #[derive(cadency_codegen::CommandBaseline)] + #[description = "123"] struct Test { - description: String, options: Vec, } let test = Test { - description: "123".to_string(), options: Vec::new(), }; let name: String = test.name(); assert_eq!(name, "test", "Test command name to be lowercase {name}") } + #[test] + fn return_attribute_name_as_struct_name() { + #[derive(cadency_codegen::CommandBaseline)] + #[name = "my_test"] + #[description = "123"] + struct Test { + options: Vec, + } + let test = Test { + options: Vec::new(), + }; + let name: String = test.name(); + assert_eq!( + name, "my_test", + "Test command name should match with name attribute" + ) + } + #[test] fn not_return_uppercase_struct_name_as_name() { #[derive(cadency_codegen::CommandBaseline)] + #[description = "123"] struct Test { - description: String, options: Vec, } let test = Test { - description: "123".to_string(), options: Vec::new(), }; let name: String = test.name(); @@ -76,4 +91,52 @@ mod test { "Testing that the first char is not uppercase: {name}" ) } + + #[test] + fn return_attribute_description() { + #[derive(cadency_codegen::CommandBaseline)] + #[description = "123"] + struct Test { + options: Vec, + } + let test = Test { + options: Vec::new(), + }; + assert_eq!( + test.description(), + "123", + "Test command description should match" + ) + } + + #[test] + fn return_default_deferred_config() { + #[derive(cadency_codegen::CommandBaseline)] + #[description = "123"] + struct Test { + options: Vec, + } + let test = Test { + options: Vec::new(), + }; + assert_eq!( + test.deferred(), + false, + "Test command should not be deferred" + ) + } + + #[test] + fn return_deferred_attribute() { + #[derive(cadency_codegen::CommandBaseline)] + #[description = "123"] + #[deferred = true] + struct Test { + options: Vec, + } + let test = Test { + options: Vec::new(), + }; + assert!(test.deferred(), "Test command should be deferred") + } } diff --git a/cadency_core/src/command.rs b/cadency_core/src/command.rs index 43a02bb..56f54a6 100644 --- a/cadency_core/src/command.rs +++ b/cadency_core/src/command.rs @@ -32,6 +32,7 @@ macro_rules! setup_commands { pub trait CadencyCommandBaseline { fn name(&self) -> String; fn description(&self) -> String; + fn deferred(&self) -> bool; fn options(&self) -> &Vec; } From 4bc71280cf9254d2ed006361875ea6ba2e507771 Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sun, 5 Feb 2023 01:13:46 +0100 Subject: [PATCH 04/16] refactor(commands): Use derive attributes to reduce boilerplate --- cadency_commands/src/fib.rs | 3 +-- cadency_commands/src/inspire.rs | 13 ++----------- cadency_commands/src/now.rs | 13 ++----------- cadency_commands/src/pause.rs | 14 +++----------- cadency_commands/src/ping.rs | 13 ++----------- cadency_commands/src/play.rs | 4 ++-- cadency_commands/src/resume.rs | 14 +++----------- cadency_commands/src/skip.rs | 14 +++----------- cadency_commands/src/slap.rs | 3 +-- cadency_commands/src/stop.rs | 14 +++----------- cadency_commands/src/tracks.rs | 14 +++----------- cadency_commands/src/urban.rs | 4 ++-- .../custom_commands/examples/custom_commands.rs | 4 +--- 13 files changed, 28 insertions(+), 99 deletions(-) diff --git a/cadency_commands/src/fib.rs b/cadency_commands/src/fib.rs index 2584e8d..a20b0f4 100644 --- a/cadency_commands/src/fib.rs +++ b/cadency_commands/src/fib.rs @@ -9,15 +9,14 @@ use serenity::{ }; #[derive(CommandBaseline)] +#[description = "Calculate the nth number in the fibonacci sequence"] pub struct Fib { - description: &'static str, options: Vec, } impl std::default::Default for Fib { fn default() -> Self { Self { - description: "Calculate the nth number in the fibonacci sequence", options: vec![CadencyCommandOption { name: "number", description: "The number in the fibonacci sequence", diff --git a/cadency_commands/src/inspire.rs b/cadency_commands/src/inspire.rs index 3be07f1..2dee55a 100644 --- a/cadency_commands/src/inspire.rs +++ b/cadency_commands/src/inspire.rs @@ -4,21 +4,12 @@ use serenity::{ model::application::interaction::application_command::ApplicationCommandInteraction, }; -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] +#[description = "Say something really inspiring!"] pub struct Inspire { - description: &'static str, options: Vec, } -impl std::default::Default for Inspire { - fn default() -> Self { - Self { - description: "Say something really inspiring!", - options: vec![], - } - } -} - impl Inspire { async fn request_inspire_image_url() -> Result { debug!("Requesting inspirobot and unpack body"); diff --git a/cadency_commands/src/now.rs b/cadency_commands/src/now.rs index 4ee3e82..5390cc3 100644 --- a/cadency_commands/src/now.rs +++ b/cadency_commands/src/now.rs @@ -4,21 +4,12 @@ use serenity::{ model::application::interaction::application_command::ApplicationCommandInteraction, }; -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] +#[description = "Shows current song"] pub struct Now { - description: &'static str, options: Vec, } -impl std::default::Default for Now { - fn default() -> Self { - Self { - description: "Shows current song", - options: vec![], - } - } -} - #[async_trait] impl CadencyCommand for Now { #[command] diff --git a/cadency_commands/src/pause.rs b/cadency_commands/src/pause.rs index aed0afe..563f242 100644 --- a/cadency_commands/src/pause.rs +++ b/cadency_commands/src/pause.rs @@ -4,21 +4,13 @@ use serenity::{ model::application::interaction::application_command::ApplicationCommandInteraction, }; -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] +#[description = "Pause the current song"] +#[deferred = true] pub struct Pause { - description: &'static str, options: Vec, } -impl std::default::Default for Pause { - fn default() -> Self { - Self { - description: "Pause the current song", - options: vec![], - } - } -} - #[async_trait] impl CadencyCommand for Pause { #[command] diff --git a/cadency_commands/src/ping.rs b/cadency_commands/src/ping.rs index 6547938..6a6b190 100644 --- a/cadency_commands/src/ping.rs +++ b/cadency_commands/src/ping.rs @@ -4,21 +4,12 @@ use serenity::{ model::application::interaction::application_command::ApplicationCommandInteraction, }; -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] +#[description = "Play Ping-Pong"] pub struct Ping { - description: &'static str, options: Vec, } -impl std::default::Default for Ping { - fn default() -> Self { - Self { - description: "Play Ping-Pong", - options: vec![], - } - } -} - #[async_trait] impl CadencyCommand for Ping { #[command] diff --git a/cadency_commands/src/play.rs b/cadency_commands/src/play.rs index 39b70f6..10c6f3b 100644 --- a/cadency_commands/src/play.rs +++ b/cadency_commands/src/play.rs @@ -13,15 +13,15 @@ use serenity::{ use songbird::events::Event; #[derive(CommandBaseline)] +#[description = "Play a song from Youtube"] +#[deferred = true] pub struct Play { - description: &'static str, options: Vec, } impl std::default::Default for Play { fn default() -> Self { Self { - description: "Play a song from Youtube", options: vec![CadencyCommandOption { name: "query", description: "URL or search query like: 'Hey Jude Beatles'", diff --git a/cadency_commands/src/resume.rs b/cadency_commands/src/resume.rs index 1fe14e5..0a188f0 100644 --- a/cadency_commands/src/resume.rs +++ b/cadency_commands/src/resume.rs @@ -4,21 +4,13 @@ use serenity::{ model::application::interaction::application_command::ApplicationCommandInteraction, }; -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] +#[description = "Resume current song if paused"] +#[deferred = true] pub struct Resume { - description: &'static str, options: Vec, } -impl std::default::Default for Resume { - fn default() -> Self { - Self { - description: "Resume current song if paused", - options: vec![], - } - } -} - #[async_trait] impl CadencyCommand for Resume { #[command] diff --git a/cadency_commands/src/skip.rs b/cadency_commands/src/skip.rs index 18d2de6..886acd1 100644 --- a/cadency_commands/src/skip.rs +++ b/cadency_commands/src/skip.rs @@ -4,21 +4,13 @@ use serenity::{ model::application::interaction::application_command::ApplicationCommandInteraction, }; -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] +#[description = "Skip current song"] +#[deferred = true] pub struct Skip { - description: &'static str, options: Vec, } -impl std::default::Default for Skip { - fn default() -> Self { - Self { - description: "Skip current song", - options: vec![], - } - } -} - #[async_trait] impl CadencyCommand for Skip { #[command] diff --git a/cadency_commands/src/slap.rs b/cadency_commands/src/slap.rs index b428388..51ea56e 100644 --- a/cadency_commands/src/slap.rs +++ b/cadency_commands/src/slap.rs @@ -9,15 +9,14 @@ use serenity::{ }; #[derive(CommandBaseline)] +#[description = "Slap someone with a large trout!"] pub struct Slap { - description: &'static str, options: Vec, } impl std::default::Default for Slap { fn default() -> Self { Self { - description: "Slap someone with a large trout!", options: vec![CadencyCommandOption { name: "target", description: "The user you want to slap", diff --git a/cadency_commands/src/stop.rs b/cadency_commands/src/stop.rs index de9cfb3..75def37 100644 --- a/cadency_commands/src/stop.rs +++ b/cadency_commands/src/stop.rs @@ -4,21 +4,13 @@ use serenity::{ model::application::interaction::application_command::ApplicationCommandInteraction, }; -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] +#[description = "Stop music and clear the track list"] +#[deferred = true] pub struct Stop { - description: &'static str, options: Vec, } -impl std::default::Default for Stop { - fn default() -> Self { - Self { - description: "Stop music and clear the track list", - options: vec![], - } - } -} - #[async_trait] impl CadencyCommand for Stop { #[command] diff --git a/cadency_commands/src/tracks.rs b/cadency_commands/src/tracks.rs index e4dfd63..fa74b0c 100644 --- a/cadency_commands/src/tracks.rs +++ b/cadency_commands/src/tracks.rs @@ -5,21 +5,13 @@ use serenity::{ utils::Color, }; -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] +#[description = "List all tracks in the queue"] +#[deferred = true] pub struct Tracks { - description: &'static str, options: Vec, } -impl std::default::Default for Tracks { - fn default() -> Self { - Self { - description: "List all tracks in the queue", - options: vec![], - } - } -} - #[async_trait] impl CadencyCommand for Tracks { #[command] diff --git a/cadency_commands/src/urban.rs b/cadency_commands/src/urban.rs index 3ec4cb5..19e4900 100644 --- a/cadency_commands/src/urban.rs +++ b/cadency_commands/src/urban.rs @@ -32,15 +32,15 @@ struct UrbanResult { } #[derive(CommandBaseline)] +#[description = "Searches the Urbandictionary for your query"] +#[deferred = true] pub struct Urban { - description: &'static str, options: Vec, } impl std::default::Default for Urban { fn default() -> Self { Self { - description: "Searches the Urbandictionary for your query", options: vec![CadencyCommandOption { name: "query", description: "Your search query", diff --git a/examples/custom_commands/examples/custom_commands.rs b/examples/custom_commands/examples/custom_commands.rs index cd12945..67c762f 100644 --- a/examples/custom_commands/examples/custom_commands.rs +++ b/examples/custom_commands/examples/custom_commands.rs @@ -18,9 +18,8 @@ use serenity::{ // This is your custom command with the name "hello" #[derive(CommandBaseline)] +#[description = "Say Hello to a user"] struct Hello { - // Description of the command in the discord UI - description: String, // The allowed list of command arguments options: Vec, } @@ -28,7 +27,6 @@ struct Hello { impl std::default::Default for Hello { fn default() -> Self { Self { - description: "Say Hello to a user".to_string(), options: vec![CadencyCommandOption { name: "user", description: "The user to greet", From 3002c9092c09950d26e9a5b698d8489085db5894 Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sun, 5 Feb 2023 01:28:37 +0100 Subject: [PATCH 05/16] refactor(command): Create deferred resposne in command handler --- cadency_commands/src/pause.rs | 1 - cadency_commands/src/play.rs | 1 - cadency_commands/src/resume.rs | 1 - cadency_commands/src/skip.rs | 1 - cadency_commands/src/stop.rs | 1 - cadency_commands/src/tracks.rs | 1 - cadency_commands/src/urban.rs | 1 - cadency_core/src/handler/command.rs | 9 ++++++++- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cadency_commands/src/pause.rs b/cadency_commands/src/pause.rs index 563f242..5629504 100644 --- a/cadency_commands/src/pause.rs +++ b/cadency_commands/src/pause.rs @@ -20,7 +20,6 @@ impl CadencyCommand for Pause { command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { if let Some(guild_id) = command.guild_id { - utils::voice::create_deferred_response(ctx, command).await?; let manager = utils::voice::get_songbird(ctx).await; if let Some(call) = manager.get(guild_id) { let handler = call.lock().await; diff --git a/cadency_commands/src/play.rs b/cadency_commands/src/play.rs index 10c6f3b..37c1221 100644 --- a/cadency_commands/src/play.rs +++ b/cadency_commands/src/play.rs @@ -40,7 +40,6 @@ impl CadencyCommand for Play { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - utils::voice::create_deferred_response(ctx, command).await?; let search_data = utils::get_option_value_at_position(command.data.options.as_ref(), 0) .and_then(|option_value| { if let CommandDataOptionValue::String(string_value) = option_value { diff --git a/cadency_commands/src/resume.rs b/cadency_commands/src/resume.rs index 0a188f0..0c07324 100644 --- a/cadency_commands/src/resume.rs +++ b/cadency_commands/src/resume.rs @@ -20,7 +20,6 @@ impl CadencyCommand for Resume { command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { if let Some(guild_id) = command.guild_id { - utils::voice::create_deferred_response(ctx, command).await?; let manager = utils::voice::get_songbird(ctx).await; if let Some(call) = manager.get(guild_id) { let handler = call.lock().await; diff --git a/cadency_commands/src/skip.rs b/cadency_commands/src/skip.rs index 886acd1..f4d2adc 100644 --- a/cadency_commands/src/skip.rs +++ b/cadency_commands/src/skip.rs @@ -20,7 +20,6 @@ impl CadencyCommand for Skip { command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { if let Some(guild_id) = command.guild_id { - utils::voice::create_deferred_response(ctx, command).await?; let manager = utils::voice::get_songbird(ctx).await; if let Some(call) = manager.get(guild_id) { let handler = call.lock().await; diff --git a/cadency_commands/src/stop.rs b/cadency_commands/src/stop.rs index 75def37..8857f52 100644 --- a/cadency_commands/src/stop.rs +++ b/cadency_commands/src/stop.rs @@ -20,7 +20,6 @@ impl CadencyCommand for Stop { command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { if let Some(guild_id) = command.guild_id { - utils::voice::create_deferred_response(ctx, command).await?; let manager = utils::voice::get_songbird(ctx).await; if let Some(call) = manager.get(guild_id) { let handler = call.lock().await; diff --git a/cadency_commands/src/tracks.rs b/cadency_commands/src/tracks.rs index fa74b0c..f21aa75 100644 --- a/cadency_commands/src/tracks.rs +++ b/cadency_commands/src/tracks.rs @@ -21,7 +21,6 @@ impl CadencyCommand for Tracks { command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { if let Some(guild_id) = command.guild_id { - utils::voice::create_deferred_response(ctx, command).await?; let manager = utils::voice::get_songbird(ctx).await; if let Some(call) = manager.get(guild_id) { let handler = call.lock().await; diff --git a/cadency_commands/src/urban.rs b/cadency_commands/src/urban.rs index 19e4900..9ed34b4 100644 --- a/cadency_commands/src/urban.rs +++ b/cadency_commands/src/urban.rs @@ -98,7 +98,6 @@ impl CadencyCommand for Urban { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - utils::voice::create_deferred_response(ctx, command).await?; let query_option = utils::get_option_value_at_position(command.data.options.as_ref(), 0) .and_then(|option_value| { if let CommandDataOptionValue::String(query) = option_value { diff --git a/cadency_core/src/handler/command.rs b/cadency_core/src/handler/command.rs index 3c53838..3b8eed2 100644 --- a/cadency_core/src/handler/command.rs +++ b/cadency_core/src/handler/command.rs @@ -37,7 +37,14 @@ impl EventHandler for Handler { .iter() .find(|cadency_command| cadency_command.name() == command_name); let cmd_execution = match cmd_target { - Some(target) => target.execute(&ctx, &mut command).await, + Some(target) => { + if target.deferred() { + utils::voice::create_deferred_response(&ctx, &mut command) + .await + .expect("Unable to deferre response"); + } + target.execute(&ctx, &mut command).await + } None => command_not_implemented(&ctx, &command).await, }; if let Err(execution_err) = cmd_execution { From bc340d53f072a7e95b97060186b21e117fe9b76a Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sun, 5 Feb 2023 01:37:26 +0100 Subject: [PATCH 06/16] refactor(command): Remove command attribute macro --- cadency_codegen/src/attribute.rs | 26 ------------------- cadency_codegen/src/lib.rs | 11 +------- cadency_commands/src/fib.rs | 1 - cadency_commands/src/inspire.rs | 1 - cadency_commands/src/now.rs | 1 - cadency_commands/src/pause.rs | 1 - cadency_commands/src/ping.rs | 1 - cadency_commands/src/play.rs | 1 - cadency_commands/src/resume.rs | 1 - cadency_commands/src/skip.rs | 1 - cadency_commands/src/slap.rs | 1 - cadency_commands/src/stop.rs | 1 - cadency_commands/src/tracks.rs | 1 - cadency_commands/src/urban.rs | 1 - .../examples/custom_commands.rs | 1 - 15 files changed, 1 insertion(+), 49 deletions(-) delete mode 100644 cadency_codegen/src/attribute.rs diff --git a/cadency_codegen/src/attribute.rs b/cadency_codegen/src/attribute.rs deleted file mode 100644 index 1159dbb..0000000 --- a/cadency_codegen/src/attribute.rs +++ /dev/null @@ -1,26 +0,0 @@ -pub(crate) mod command { - use proc_macro::TokenStream; - use syn::{ItemFn, Stmt}; - - fn log_command_usage() -> Result { - syn::parse( - quote!( - debug!("Execute {} command", self.name()); - ) - .into(), - ) - } - - fn add_start_log(function: &mut ItemFn) { - let logger = log_command_usage().expect("Failed to parse log statement"); - function.block.stmts.insert(0, logger); - } - - pub(crate) fn complete_command(mut function: ItemFn) -> TokenStream { - add_start_log(&mut function); - quote!( - #function - ) - .into() - } -} diff --git a/cadency_codegen/src/lib.rs b/cadency_codegen/src/lib.rs index 78f15d4..bb58608 100644 --- a/cadency_codegen/src/lib.rs +++ b/cadency_codegen/src/lib.rs @@ -2,9 +2,8 @@ extern crate quote; use proc_macro::TokenStream; -use syn::{parse_macro_input, DeriveInput, ItemFn}; +use syn::{parse_macro_input, DeriveInput}; -mod attribute; mod derive; #[proc_macro_derive(CommandBaseline, attributes(name, description, deferred))] @@ -14,11 +13,3 @@ pub fn derive_command_baseline(input_item: TokenStream) -> TokenStream { // Implement command trait derive::impl_command_baseline(tree) } - -#[proc_macro_attribute] -pub fn command(_: TokenStream, input_item: TokenStream) -> TokenStream { - // Parse function - let input_function = parse_macro_input!(input_item as ItemFn); - // Return modified function - attribute::command::complete_command(input_function) -} diff --git a/cadency_commands/src/fib.rs b/cadency_commands/src/fib.rs index a20b0f4..82d34c2 100644 --- a/cadency_commands/src/fib.rs +++ b/cadency_commands/src/fib.rs @@ -39,7 +39,6 @@ impl Fib { #[async_trait] impl CadencyCommand for Fib { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/cadency_commands/src/inspire.rs b/cadency_commands/src/inspire.rs index 2dee55a..8a9fb09 100644 --- a/cadency_commands/src/inspire.rs +++ b/cadency_commands/src/inspire.rs @@ -22,7 +22,6 @@ impl Inspire { #[async_trait] impl CadencyCommand for Inspire { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/cadency_commands/src/now.rs b/cadency_commands/src/now.rs index 5390cc3..0fbba3d 100644 --- a/cadency_commands/src/now.rs +++ b/cadency_commands/src/now.rs @@ -12,7 +12,6 @@ pub struct Now { #[async_trait] impl CadencyCommand for Now { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/cadency_commands/src/pause.rs b/cadency_commands/src/pause.rs index 5629504..93aaf32 100644 --- a/cadency_commands/src/pause.rs +++ b/cadency_commands/src/pause.rs @@ -13,7 +13,6 @@ pub struct Pause { #[async_trait] impl CadencyCommand for Pause { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/cadency_commands/src/ping.rs b/cadency_commands/src/ping.rs index 6a6b190..61c77f1 100644 --- a/cadency_commands/src/ping.rs +++ b/cadency_commands/src/ping.rs @@ -12,7 +12,6 @@ pub struct Ping { #[async_trait] impl CadencyCommand for Ping { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/cadency_commands/src/play.rs b/cadency_commands/src/play.rs index 37c1221..cca2a07 100644 --- a/cadency_commands/src/play.rs +++ b/cadency_commands/src/play.rs @@ -34,7 +34,6 @@ impl std::default::Default for Play { #[async_trait] impl CadencyCommand for Play { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/cadency_commands/src/resume.rs b/cadency_commands/src/resume.rs index 0c07324..8a3deb6 100644 --- a/cadency_commands/src/resume.rs +++ b/cadency_commands/src/resume.rs @@ -13,7 +13,6 @@ pub struct Resume { #[async_trait] impl CadencyCommand for Resume { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/cadency_commands/src/skip.rs b/cadency_commands/src/skip.rs index f4d2adc..e3eb375 100644 --- a/cadency_commands/src/skip.rs +++ b/cadency_commands/src/skip.rs @@ -13,7 +13,6 @@ pub struct Skip { #[async_trait] impl CadencyCommand for Skip { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/cadency_commands/src/slap.rs b/cadency_commands/src/slap.rs index 51ea56e..62a49ce 100644 --- a/cadency_commands/src/slap.rs +++ b/cadency_commands/src/slap.rs @@ -29,7 +29,6 @@ impl std::default::Default for Slap { #[async_trait] impl CadencyCommand for Slap { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/cadency_commands/src/stop.rs b/cadency_commands/src/stop.rs index 8857f52..924ec1d 100644 --- a/cadency_commands/src/stop.rs +++ b/cadency_commands/src/stop.rs @@ -13,7 +13,6 @@ pub struct Stop { #[async_trait] impl CadencyCommand for Stop { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/cadency_commands/src/tracks.rs b/cadency_commands/src/tracks.rs index f21aa75..eca3931 100644 --- a/cadency_commands/src/tracks.rs +++ b/cadency_commands/src/tracks.rs @@ -14,7 +14,6 @@ pub struct Tracks { #[async_trait] impl CadencyCommand for Tracks { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/cadency_commands/src/urban.rs b/cadency_commands/src/urban.rs index 9ed34b4..302a85f 100644 --- a/cadency_commands/src/urban.rs +++ b/cadency_commands/src/urban.rs @@ -92,7 +92,6 @@ impl Urban { #[async_trait] impl CadencyCommand for Urban { - #[command] async fn execute<'a>( &self, ctx: &Context, diff --git a/examples/custom_commands/examples/custom_commands.rs b/examples/custom_commands/examples/custom_commands.rs index 67c762f..b7c498e 100644 --- a/examples/custom_commands/examples/custom_commands.rs +++ b/examples/custom_commands/examples/custom_commands.rs @@ -40,7 +40,6 @@ impl std::default::Default for Hello { #[async_trait] impl CadencyCommand for Hello { // The following code will get executed by the cadency command handler if the command is called - #[command] async fn execute<'a>( &self, ctx: &Context, From ce1ee541ed882324d2f7225fb13b088cab32c6b6 Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sun, 5 Feb 2023 01:37:53 +0100 Subject: [PATCH 07/16] refactor(command): Log execution in command handler --- cadency_core/src/handler/command.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cadency_core/src/handler/command.rs b/cadency_core/src/handler/command.rs index 3b8eed2..985489b 100644 --- a/cadency_core/src/handler/command.rs +++ b/cadency_core/src/handler/command.rs @@ -38,6 +38,7 @@ impl EventHandler for Handler { .find(|cadency_command| cadency_command.name() == command_name); let cmd_execution = match cmd_target { Some(target) => { + info!("⚡ Execute {} command", target.name()); if target.deferred() { utils::voice::create_deferred_response(&ctx, &mut command) .await From 421ea4622e9e1e20156eac142dc711494212cc6f Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sun, 5 Feb 2023 12:21:04 +0100 Subject: [PATCH 08/16] feat(core): Create cadency response with builder --- cadency_core/Cargo.toml | 1 + cadency_core/src/lib.rs | 1 + cadency_core/src/response.rs | 80 ++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) create mode 100644 cadency_core/src/response.rs diff --git a/cadency_core/Cargo.toml b/cadency_core/Cargo.toml index 35a974d..26f3dd6 100644 --- a/cadency_core/Cargo.toml +++ b/cadency_core/Cargo.toml @@ -11,6 +11,7 @@ log = "0.4.17" reqwest = "0.11.14" thiserror = "1.0.38" mockall_double = "0.3.0" +derive_builder = "0.12.0" [dependencies.serenity] version = "0.11.5" diff --git a/cadency_core/src/lib.rs b/cadency_core/src/lib.rs index dd777ae..50b296d 100644 --- a/cadency_core/src/lib.rs +++ b/cadency_core/src/lib.rs @@ -10,4 +10,5 @@ mod error; pub use error::CadencyError; pub mod handler; mod intents; +pub mod response; pub mod utils; diff --git a/cadency_core/src/response.rs b/cadency_core/src/response.rs new file mode 100644 index 0000000..f650ff2 --- /dev/null +++ b/cadency_core/src/response.rs @@ -0,0 +1,80 @@ +use crate::CadencyError; +use derive_builder::Builder; +use serenity::{ + builder::CreateEmbed, + model::prelude::interaction::{ + application_command::ApplicationCommandInteraction, InteractionResponseType, + }, + prelude::Context, +}; + +#[derive(Debug, Clone)] +pub enum ResponseTiming { + Deferred, + DeferredInfo, + Instant, +} + +#[derive(Builder)] +pub struct Response { + timing: ResponseTiming, + #[builder(default)] + message: Option, + #[builder(default)] + embeds: Vec, +} + +impl ResponseBuilder { + pub fn new(timing: ResponseTiming) -> Self { + Self { + timing: Some(timing), + ..Default::default() + } + } +} + +impl Response { + pub async fn submit<'a>( + self, + ctx: &Context, + command: &'a mut ApplicationCommandInteraction, + ) -> Result<(), CadencyError> { + match self.timing { + ResponseTiming::Instant => { + command + .create_interaction_response(&ctx.http, |response| { + response + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|message| { + if let Some(msg) = self.message { + message.content(msg); + } + message.add_embeds(self.embeds) + }) + }) + .await + } + ResponseTiming::DeferredInfo => { + command + .create_interaction_response(&ctx.http, |response| { + response.kind(InteractionResponseType::DeferredChannelMessageWithSource) + }) + .await + } + ResponseTiming::Deferred => command + .edit_original_interaction_response(&ctx.http, |previous_response| { + if let Some(msg) = self.message { + previous_response.content(msg); + } + previous_response.add_embeds(self.embeds) + }) + .await + .map(|_| ()), + } + .map_err(|err| { + error!("Failed to submit response: {}", err); + CadencyError::Response + })?; + Ok(()) + } +} From f445f949b3d53c2aee9ebe7be561b1f652073bfb Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sun, 5 Feb 2023 12:25:05 +0100 Subject: [PATCH 09/16] refactor(core): Use response builder for command handler messages --- cadency_core/src/handler/command.rs | 88 ++++++++++++++++------------- 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/cadency_core/src/handler/command.rs b/cadency_core/src/handler/command.rs index 985489b..c6b7d05 100644 --- a/cadency_core/src/handler/command.rs +++ b/cadency_core/src/handler/command.rs @@ -1,16 +1,16 @@ +use crate::{ + command::{command_not_implemented, setup_commands}, + response::{ResponseBuilder, ResponseTiming}, + utils, + utils::set_bot_presence, + CadencyError, +}; use serenity::{ async_trait, client::{Context, EventHandler}, model::{application::interaction::Interaction, event::ResumedEvent, gateway::Ready}, }; -use crate::{ - command::{command_not_implemented, setup_commands}, - utils, CadencyError, -}; - -use crate::utils::set_bot_presence; - pub(crate) struct Handler; #[async_trait] @@ -31,42 +31,52 @@ impl EventHandler for Handler { async fn interaction_create(&self, ctx: Context, interaction: Interaction) { if let Interaction::ApplicationCommand(mut command) = interaction { - let cadency_commands = utils::get_commands(&ctx).await; - let command_name = command.data.name.as_str(); - let cmd_target = cadency_commands - .iter() - .find(|cadency_command| cadency_command.name() == command_name); - let cmd_execution = match cmd_target { - Some(target) => { - info!("⚡ Execute {} command", target.name()); - if target.deferred() { - utils::voice::create_deferred_response(&ctx, &mut command) - .await - .expect("Unable to deferre response"); - } - target.execute(&ctx, &mut command).await + let cmd_target = utils::get_commands(&ctx) + .await + .into_iter() + .find(|cadency_command| cadency_command.name() == command.data.name.as_str()); + + if let Some(cmd) = cmd_target { + info!("⚡ Execute '{}' command", cmd.name()); + if cmd.deferred() { + ResponseBuilder::new(ResponseTiming::DeferredInfo) + .build() + .expect("Failed to build response") + .submit(&ctx, &mut command) + .await + .expect("Unable to submit deferred info"); } - None => command_not_implemented(&ctx, &command).await, - }; - if let Err(execution_err) = cmd_execution { - error!("❌ Command execution failed: {execution_err:?}"); - match execution_err { - CadencyError::Command { message } => { - utils::create_response(&ctx, &mut command, &message).await + if let Err(command_error) = cmd.execute(&ctx, &mut command).await { + error!("❌ Command execution failed: {command_error:?}"); + let mut error_res_builder = ResponseBuilder::default(); + if cmd.deferred() { + error_res_builder.timing(ResponseTiming::Deferred); + } else { + error_res_builder.timing(ResponseTiming::Instant); } - _ => { - utils::create_response( - &ctx, - &mut command, - "**Oops! Something went terrible wrong.**", - ) - .await + match command_error { + CadencyError::Command { message } => { + error_res_builder.message(Some(message)); + error_res_builder.build() + } + _ => error_res_builder + .message(Some("**Oops! Something went terrible wrong.**".to_string())) + .build(), } + .expect("Unable to build error response") + .submit(&ctx, &mut command) + .await + .map_err(|err| { + error!("❌ Fatal error! Is discord down? {:?}", err); + }) + .expect("Unable to send error response"); + } else { + info!("✅ Command '{}' was successful", cmd.name()) } - .map_err(|err| { - error!("❌ Fatal error! Is discord down? {:?}", err); - }) - .expect("Unable to send response"); + } else { + command_not_implemented(&ctx, &command) + .await + .expect("Failed to submit not-implemented error"); } }; } From c7e26753e5ef114c1aee9b18a274b8edadde1d24 Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sun, 5 Feb 2023 15:23:59 +0100 Subject: [PATCH 10/16] refactor(core): Pass deferred command errors up to command handler --- cadency_commands/src/now.rs | 48 +++---- cadency_commands/src/pause.rs | 55 +++----- cadency_commands/src/play.rs | 237 +++++++++++++++------------------ cadency_commands/src/resume.rs | 55 +++----- cadency_commands/src/skip.rs | 52 +++----- cadency_commands/src/stop.rs | 36 ++--- cadency_commands/src/tracks.rs | 86 +++++------- cadency_commands/src/urban.rs | 62 ++++----- 8 files changed, 249 insertions(+), 382 deletions(-) diff --git a/cadency_commands/src/now.rs b/cadency_commands/src/now.rs index 0fbba3d..0237581 100644 --- a/cadency_commands/src/now.rs +++ b/cadency_commands/src/now.rs @@ -17,34 +17,26 @@ impl CadencyCommand for Now { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - if let Some(guild_id) = command.guild_id { - let manager = utils::voice::get_songbird(ctx).await; - let call = manager.get(guild_id).unwrap(); - let handler = call.lock().await; - match handler.queue().current() { - Some(track) => { - utils::create_response( - ctx, - command, - &track.metadata().title.as_ref().map_or( - String::from(":x: **Could not add audio source to the queue!**"), - |title| format!(":newspaper: `{title}`"), - ), - ) - .await?; - } - None => { - utils::create_response(ctx, command, ":x: **No song is playing**").await?; - } - }; - } else { - utils::create_response( - ctx, - command, - ":x: **This command can only be executed on a server**", - ) - .await?; - } + let guild_id = command.guild_id.ok_or(CadencyError::Command { + message: ":x: **This command can only be executed on a server**".to_string(), + })?; + let manager = utils::voice::get_songbird(ctx).await; + let call = manager.get(guild_id).ok_or(CadencyError::Command { + message: ":x: **No active voice session on the server**".to_string(), + })?; + let handler = call.lock().await; + let track = handler.queue().current().ok_or(CadencyError::Command { + message: ":x: **No song is playing**".to_string(), + })?; + utils::create_response( + ctx, + command, + &track.metadata().title.as_ref().map_or( + String::from(":x: **Could not add audio source to the queue!**"), + |title| format!(":newspaper: `{title}`"), + ), + ) + .await?; Ok(()) } } diff --git a/cadency_commands/src/pause.rs b/cadency_commands/src/pause.rs index 93aaf32..3354ae2 100644 --- a/cadency_commands/src/pause.rs +++ b/cadency_commands/src/pause.rs @@ -18,45 +18,24 @@ impl CadencyCommand for Pause { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - if let Some(guild_id) = command.guild_id { - let manager = utils::voice::get_songbird(ctx).await; - if let Some(call) = manager.get(guild_id) { - let handler = call.lock().await; - if handler.queue().is_empty() { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to pause**") - .await?; - } else { - match handler.queue().pause() { - Ok(_) => { - utils::voice::edit_deferred_response( - ctx, - command, - ":pause_button: **Paused**", - ) - .await?; - } - Err(err) => { - error!("Failed to pause: {err:?}"); - utils::voice::edit_deferred_response( - ctx, - command, - ":x: **Could not pause**", - ) - .await?; - } - }; - } - } else { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to pause**") - .await?; - } + let guild_id = command.guild_id.ok_or(CadencyError::Command { + message: ":x: **This command can only be executed on a server**".to_string(), + })?; + let manager = utils::voice::get_songbird(ctx).await; + let call = manager.get(guild_id).ok_or(CadencyError::Command { + message: ":x: **No active voice session on the server**".to_string(), + })?; + let handler = call.lock().await; + if handler.queue().is_empty() { + utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to pause**").await?; } else { - utils::create_response( - ctx, - command, - ":x: **This command can only be executed on a server**", - ) - .await?; + handler.queue().pause().map_err(|err| { + error!("Failed to pause: {err:?}"); + CadencyError::Command { + message: ":x: **Could not pause the track**".to_string(), + } + })?; + utils::voice::edit_deferred_response(ctx, command, ":pause_button: **Paused**").await?; } Ok(()) } diff --git a/cadency_commands/src/play.rs b/cadency_commands/src/play.rs index cca2a07..9d1f7fd 100644 --- a/cadency_commands/src/play.rs +++ b/cadency_commands/src/play.rs @@ -39,149 +39,126 @@ impl CadencyCommand for Play { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - let search_data = utils::get_option_value_at_position(command.data.options.as_ref(), 0) - .and_then(|option_value| { - if let CommandDataOptionValue::String(string_value) = option_value { - let (is_valid_url, is_playlist): (bool, bool) = Url::parse(string_value) - .ok() - .map_or((false, false), |valid_url| { - let is_playlist: bool = valid_url - .query_pairs() - .find(|(key, _)| key == "list") - .map_or(false, |_| true); - (true, is_playlist) - }); - Some((string_value, is_valid_url, is_playlist)) - } else { - None - } - }); - let joined_voice = utils::voice::join(ctx, command).await; - match (search_data, joined_voice) { - (Some((search_payload, is_url, is_playlist)), Ok((manager, guild_id, _channel_id))) => { - let call = manager.get(guild_id).unwrap(); - let mut is_queue_empty = { - let call_handler = call.lock().await; - call_handler.queue().is_empty() - }; - if is_playlist { - let playlist_items = - cadency_yt_playlist::fetch_playlist_songs(search_payload.clone()).unwrap(); - playlist_items - .messages - .iter() - .for_each(|entry| info!("Unable to parse song from playlist: {entry:?}",)); - let songs = playlist_items.data; - let mut amount = 0; - let mut total_duration = 0_f32; - for song in songs { - // Add max the first 30 songs of the playlist - // and only if the duration of the song is below 10mins - if amount <= 30 && song.duration <= 600_f32 { - match utils::voice::add_song( - call.clone(), - song.url, - true, - !is_queue_empty, // Don't add first song lazy to the queue - ) - .await - { - Ok(added_song) => { - amount += 1; - total_duration += song.duration; - is_queue_empty = false; - debug!("Added song '{:?}' from playlist", added_song.title); - } - Err(err) => { - error!("Failed to add song: {err}"); - } - } - } + let (search_payload, is_url, is_playlist) = + utils::get_option_value_at_position(command.data.options.as_ref(), 0) + .and_then(|option_value| { + if let CommandDataOptionValue::String(string_value) = option_value { + let (is_valid_url, is_playlist): (bool, bool) = Url::parse(string_value) + .ok() + .map_or((false, false), |valid_url| { + let is_playlist: bool = valid_url + .query_pairs() + .find(|(key, _)| key == "list") + .map_or(false, |_| true); + (true, is_playlist) + }); + Some((string_value, is_valid_url, is_playlist)) + } else { + None } - total_duration /= 60_f32; - let mut handler = call.lock().await; - handler.remove_all_global_events(); - handler.add_global_event( - Event::Periodic(std::time::Duration::from_secs(120), None), - InactiveHandler { guild_id, manager }, - ); - drop(handler); - utils::voice::edit_deferred_response( - ctx, - command, - &format!( - ":white_check_mark: **Added ___{amount}___ songs to the queue with a duration of ___{total_duration:.2}___ mins** \n**Playing** :notes: `{search_payload}`", - ), - ) - .await?; - } else { + }) + .ok_or(CadencyError::Command { + message: ":x: **No search string provided**".to_string(), + })?; + let (manager, guild_id, _channel_id) = utils::voice::join(ctx, command).await?; + let call = manager.get(guild_id).ok_or(CadencyError::Command { + message: ":x: **No active voice session on the server**".to_string(), + })?; + let mut is_queue_empty = { + let call_handler = call.lock().await; + call_handler.queue().is_empty() + }; + if is_playlist { + let playlist_items = + cadency_yt_playlist::fetch_playlist_songs(search_payload.clone()).unwrap(); + playlist_items + .messages + .iter() + .for_each(|entry| debug!("🚧 Unable to parse song from playlist: {entry:?}",)); + let songs = playlist_items.data; + let mut amount = 0; + let mut total_duration = 0_f32; + for song in songs { + // Add max the first 30 songs of the playlist + // and only if the duration of the song is below 10mins + if amount <= 30 && song.duration <= 600_f32 { match utils::voice::add_song( call.clone(), - search_payload.clone(), - is_url, + song.url, + true, !is_queue_empty, // Don't add first song lazy to the queue ) .await { Ok(added_song) => { - let mut handler = call.lock().await; - handler.remove_all_global_events(); - handler.add_global_event( - Event::Periodic(std::time::Duration::from_secs(120), None), - InactiveHandler { guild_id, manager }, - ); - let song_url = if is_url { - search_payload - } else { - added_song - .source_url - .as_ref() - .map_or("unknown url", |url| url) - }; - utils::voice::edit_deferred_response( - ctx, - command, - &format!( - ":white_check_mark: **Added song to the queue and started playing:** \n:notes: `{}` \n:link: `{}`", - song_url, - added_song - .title - .as_ref() - .map_or(":x: **Unknown title**", |title| title) - ), - ) - .await?; + amount += 1; + total_duration += song.duration; + is_queue_empty = false; + debug!("➕ Added song '{:?}' from playlist", added_song.title); } Err(err) => { - error!("Failed to add song to queue: {}", err); - utils::voice::edit_deferred_response( - ctx, - command, - ":x: **Couldn't add audio source to the queue!**", - ) - .await?; + error!("❌ Failed to add song: {err}"); } - }; + } } } - (None, _) => { - utils::voice::edit_deferred_response( - ctx, - command, - ":x: **Couldn't find a search string**", - ) - .await?; - } - (_, Err(err)) => { - error!("{err}"); - utils::voice::edit_deferred_response( - ctx, - command, - ":x: **Couldn't join your voice channel**", - ) - .await?; - } - }; + total_duration /= 60_f32; + let mut handler = call.lock().await; + handler.remove_all_global_events(); + handler.add_global_event( + Event::Periodic(std::time::Duration::from_secs(120), None), + InactiveHandler { guild_id, manager }, + ); + drop(handler); + utils::voice::edit_deferred_response( + ctx, + command, + &format!( + ":white_check_mark: **Added ___{amount}___ songs to the queue with a duration of ___{total_duration:.2}___ mins** \n**Playing** :notes: `{search_payload}`", + ), + ) + .await?; + } else { + let added_song = utils::voice::add_song( + call.clone(), + search_payload.clone(), + is_url, + !is_queue_empty, // Don't add first song lazy to the queue + ) + .await + .map_err(|err| { + error!("❌ Failed to add song to queue: {}", err); + CadencyError::Command { + message: ":x: **Couldn't add audio source to the queue!**".to_string(), + } + })?; + let mut handler = call.lock().await; + handler.remove_all_global_events(); + handler.add_global_event( + Event::Periodic(std::time::Duration::from_secs(120), None), + InactiveHandler { guild_id, manager }, + ); + let song_url = if is_url { + search_payload + } else { + added_song + .source_url + .as_ref() + .map_or("unknown url", |url| url) + }; + utils::voice::edit_deferred_response( + ctx, + command, + &format!( + ":white_check_mark: **Added song to the queue and started playing:** \n:notes: `{}` \n:link: `{}`", + song_url, + added_song + .title + .as_ref() + .map_or(":x: **Unknown title**", |title| title) + ), + ).await?; + } Ok(()) } } diff --git a/cadency_commands/src/resume.rs b/cadency_commands/src/resume.rs index 8a3deb6..5a88a48 100644 --- a/cadency_commands/src/resume.rs +++ b/cadency_commands/src/resume.rs @@ -18,45 +18,24 @@ impl CadencyCommand for Resume { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - if let Some(guild_id) = command.guild_id { - let manager = utils::voice::get_songbird(ctx).await; - if let Some(call) = manager.get(guild_id) { - let handler = call.lock().await; - if handler.queue().is_empty() { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to resume**") - .await?; - } else { - match handler.queue().resume() { - Ok(_) => { - utils::voice::edit_deferred_response( - ctx, - command, - ":play_pause: **Resumed**", - ) - .await?; - } - Err(err) => { - error!("Failed to resume: {err:?}"); - utils::voice::edit_deferred_response( - ctx, - command, - ":x: **Could not resume**", - ) - .await?; - } - }; - } - } else { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to resume**") - .await?; - } + let guild_id = command.guild_id.ok_or(CadencyError::Command { + message: ":x: **This command can only be executed on a server**".to_string(), + })?; + let manager = utils::voice::get_songbird(ctx).await; + let call = manager.get(guild_id).ok_or(CadencyError::Command { + message: ":x: **No active voice session on the server**".to_string(), + })?; + let handler = call.lock().await; + if handler.queue().is_empty() { + utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to resume**").await?; } else { - utils::create_response( - ctx, - command, - ":x: **This command can only be executed on a server**", - ) - .await?; + handler.queue().resume().map_err(|err| { + error!("Failed to resume: {err:?}"); + CadencyError::Command { + message: ":x: **Could not resume**".to_string(), + } + })?; + utils::voice::edit_deferred_response(ctx, command, ":play_pause: **Resumed**").await?; } Ok(()) } diff --git a/cadency_commands/src/skip.rs b/cadency_commands/src/skip.rs index e3eb375..bc5c83c 100644 --- a/cadency_commands/src/skip.rs +++ b/cadency_commands/src/skip.rs @@ -18,43 +18,27 @@ impl CadencyCommand for Skip { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - if let Some(guild_id) = command.guild_id { - let manager = utils::voice::get_songbird(ctx).await; - if let Some(call) = manager.get(guild_id) { - let handler = call.lock().await; - if handler.queue().is_empty() { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to skip**") - .await?; - } else { - match handler.queue().skip() { - Ok(_) => { - utils::voice::edit_deferred_response( - ctx, - command, - ":fast_forward: **Skipped current song**", - ) - .await?; - } - Err(err) => { - error!("Failed to skip: {err:?}"); - utils::voice::edit_deferred_response( - ctx, - command, - ":x: **Could not skip**", - ) - .await?; - } - }; - } - } else { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to skip**") - .await?; - } + let guild_id = command.guild_id.ok_or(CadencyError::Command { + message: ":x: **This command can only be executed on a server**".to_string(), + })?; + let manager = utils::voice::get_songbird(ctx).await; + let call = manager.get(guild_id).ok_or(CadencyError::Command { + message: ":x: **No active voice session on the server**".to_string(), + })?; + let handler = call.lock().await; + if handler.queue().is_empty() { + utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to skip**").await?; } else { - utils::create_response( + handler.queue().skip().map_err(|err| { + error!("Failed to skip: {err:?}"); + CadencyError::Command { + message: ":x: **Could not skip the track**".to_string(), + } + })?; + utils::voice::edit_deferred_response( ctx, command, - ":x: **This command can only be executed on a server**", + ":fast_forward: **Skipped current song**", ) .await?; } diff --git a/cadency_commands/src/stop.rs b/cadency_commands/src/stop.rs index 924ec1d..9c54f0b 100644 --- a/cadency_commands/src/stop.rs +++ b/cadency_commands/src/stop.rs @@ -18,33 +18,25 @@ impl CadencyCommand for Stop { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - if let Some(guild_id) = command.guild_id { - let manager = utils::voice::get_songbird(ctx).await; - if let Some(call) = manager.get(guild_id) { - let handler = call.lock().await; - if handler.queue().is_empty() { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to stop**") - .await?; - } else { - handler.queue().stop(); - utils::voice::edit_deferred_response( + let guild_id = command.guild_id.ok_or(CadencyError::Command { + message: ":x: **This command can only be executed on a server**".to_string(), + })?; + let manager = utils::voice::get_songbird(ctx).await; + let call = manager.get(guild_id).ok_or(CadencyError::Command { + message: ":x: **No active voice session on the server**".to_string(), + })?; + + let handler = call.lock().await; + if handler.queue().is_empty() { + utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to stop**").await?; + } else { + handler.queue().stop(); + utils::voice::edit_deferred_response( ctx, command, ":white_check_mark: :wastebasket: **Successfully stopped and cleared the playlist**", ) .await?; - } - } else { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to stop**") - .await?; - } - } else { - utils::create_response( - ctx, - command, - ":x: **This command can only be executed on a server**", - ) - .await?; } Ok(()) } diff --git a/cadency_commands/src/tracks.rs b/cadency_commands/src/tracks.rs index eca3931..52b2fac 100644 --- a/cadency_commands/src/tracks.rs +++ b/cadency_commands/src/tracks.rs @@ -19,61 +19,41 @@ impl CadencyCommand for Tracks { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - if let Some(guild_id) = command.guild_id { - let manager = utils::voice::get_songbird(ctx).await; - if let Some(call) = manager.get(guild_id) { - let handler = call.lock().await; - if handler.queue().is_empty() { - utils::voice::edit_deferred_response( - ctx, - command, - ":x: **No tracks in the queue**", - ) - .await?; - } else { - let queue_snapshot = handler.queue().current_queue(); - let mut embeded_tracks = CreateEmbed::default(); - embeded_tracks.color(Color::BLURPLE); - embeded_tracks.title("Track List"); - for (index, track) in queue_snapshot.into_iter().enumerate() { - let position = index + 1; - let metadata = track.metadata(); - let title = metadata - .title - .as_ref() - .map_or("**No title provided**", |t| t); - let url = metadata - .source_url - .as_ref() - .map_or("**No url provided**", |u| u); - embeded_tracks.field( - format!("{position}. :newspaper: `{title}`"), - format!(":notes: `{url}`"), - false, - ); - } - utils::voice::edit_deferred_response_with_embeded( - ctx, - command, - vec![embeded_tracks], - ) - .await?; - } - } else { - utils::voice::edit_deferred_response( - ctx, - command, - ":x: **No tracks in the queue**", - ) + let guild_id = command.guild_id.ok_or(CadencyError::Command { + message: ":x: **This command can only be executed on a server**".to_string(), + })?; + let manager = utils::voice::get_songbird(ctx).await; + let call = manager.get(guild_id).ok_or(CadencyError::Command { + message: ":x: **No active voice session on the server**".to_string(), + })?; + let handler = call.lock().await; + if handler.queue().is_empty() { + utils::voice::edit_deferred_response(ctx, command, ":x: **No tracks in the queue**") .await?; - } } else { - utils::create_response( - ctx, - command, - ":x: **This command can only be executed on a server**", - ) - .await?; + let queue_snapshot = handler.queue().current_queue(); + let mut embeded_tracks = CreateEmbed::default(); + embeded_tracks.color(Color::BLURPLE); + embeded_tracks.title("Track List"); + for (index, track) in queue_snapshot.into_iter().enumerate() { + let position = index + 1; + let metadata = track.metadata(); + let title = metadata + .title + .as_ref() + .map_or("**No title provided**", |t| t); + let url = metadata + .source_url + .as_ref() + .map_or("**No url provided**", |u| u); + embeded_tracks.field( + format!("{position}. :newspaper: `{title}`"), + format!(":notes: `{url}`"), + false, + ); + } + utils::voice::edit_deferred_response_with_embeded(ctx, command, vec![embeded_tracks]) + .await?; } Ok(()) } diff --git a/cadency_commands/src/urban.rs b/cadency_commands/src/urban.rs index 302a85f..85ae507 100644 --- a/cadency_commands/src/urban.rs +++ b/cadency_commands/src/urban.rs @@ -97,52 +97,36 @@ impl CadencyCommand for Urban { ctx: &Context, command: &'a mut ApplicationCommandInteraction, ) -> Result<(), CadencyError> { - let query_option = utils::get_option_value_at_position(command.data.options.as_ref(), 0) + let query = utils::get_option_value_at_position(command.data.options.as_ref(), 0) .and_then(|option_value| { if let CommandDataOptionValue::String(query) = option_value { Some(query) } else { + error!("Urban command option empty"); None } - }); - match query_option { - Some(query) => { - let urbans_entrys = Self::request_urban_dictionary_entries(query).await; - match urbans_entrys { - Ok(urbans) => { - if urbans.is_empty() { - utils::voice::edit_deferred_response( - ctx, - command, - ":x: *Nothing found*", - ) - .await?; - } else { - utils::voice::edit_deferred_response_with_embeded( - ctx, - command, - Self::create_embed(urbans), - ) - .await?; - } - } - Err(err) => { - error!("Failed to request urban dictionary entries : {:?}", err); - utils::voice::edit_deferred_response( - ctx, - command, - ":x: *Failed to request urban dictionary*", - ) - .await?; - } + }) + .ok_or(CadencyError::Command { + message: ":x: *Empty or invalid query*".to_string(), + })?; + let urbans = Self::request_urban_dictionary_entries(query) + .await + .map_err(|err| { + error!("Failed to request urban dictionary entries : {:?}", err); + CadencyError::Command { + message: ":x: *Failed to request urban dictionary*".to_string(), } - } - None => { - error!("Urban command option empty"); - utils::voice::edit_deferred_response(ctx, command, ":x: *Empty or invalid query*") - .await?; - } - }; + })?; + if urbans.is_empty() { + utils::voice::edit_deferred_response(ctx, command, ":x: *Nothing found*").await?; + } else { + utils::voice::edit_deferred_response_with_embeded( + ctx, + command, + Self::create_embed(urbans), + ) + .await?; + } Ok(()) } } From a94605cdb95752489a0a3562a3820bc56d9962ca Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sun, 5 Feb 2023 17:55:59 +0100 Subject: [PATCH 11/16] refactor: Enforce a response as command exec result --- cadency_commands/src/fib.rs | 13 ++-- cadency_commands/src/inspire.rs | 15 +++-- cadency_commands/src/now.rs | 20 +++--- cadency_commands/src/pause.rs | 14 +++-- cadency_commands/src/ping.rs | 17 +++-- cadency_commands/src/play.rs | 44 ++++++------- cadency_commands/src/resume.rs | 18 +++--- cadency_commands/src/skip.rs | 23 ++++--- cadency_commands/src/slap.rs | 52 +++++++--------- cadency_commands/src/stop.rs | 23 ++++--- cadency_commands/src/tracks.rs | 20 +++--- cadency_commands/src/urban.rs | 25 ++++---- cadency_core/src/command.rs | 9 ++- cadency_core/src/error.rs | 3 + cadency_core/src/handler/command.rs | 60 +++++++++++------- cadency_core/src/utils/mod.rs | 51 +-------------- cadency_core/src/utils/voice.rs | 62 ++----------------- .../examples/custom_commands.rs | 11 ++-- 18 files changed, 205 insertions(+), 275 deletions(-) diff --git a/cadency_commands/src/fib.rs b/cadency_commands/src/fib.rs index 82d34c2..b74f5cc 100644 --- a/cadency_commands/src/fib.rs +++ b/cadency_commands/src/fib.rs @@ -1,4 +1,7 @@ -use cadency_core::{utils, CadencyCommand, CadencyCommandOption, CadencyError}; +use cadency_core::{ + response::{Response, ResponseBuilder}, + utils, CadencyCommand, CadencyCommandOption, CadencyError, +}; use serenity::{ async_trait, client::Context, @@ -41,9 +44,10 @@ impl Fib { impl CadencyCommand for Fib { async fn execute<'a>( &self, - ctx: &Context, + _ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + response_builder: &'a mut ResponseBuilder, + ) -> Result { let number = utils::get_option_value_at_position(command.data.options.as_ref(), 0) .and_then(|option_value| { if let CommandDataOptionValue::Integer(fib_value) = option_value { @@ -61,7 +65,6 @@ impl CadencyCommand for Fib { message: "Invalid number input".to_string(), })?; let fib_msg = Self::calc(number).to_string(); - utils::create_response(ctx, command, &fib_msg).await?; - Ok(()) + Ok(response_builder.message(Some(fib_msg)).build()?) } } diff --git a/cadency_commands/src/inspire.rs b/cadency_commands/src/inspire.rs index 8a9fb09..3655f5a 100644 --- a/cadency_commands/src/inspire.rs +++ b/cadency_commands/src/inspire.rs @@ -1,4 +1,7 @@ -use cadency_core::{utils, CadencyCommand, CadencyCommandOption, CadencyError}; +use cadency_core::{ + response::{Response, ResponseBuilder}, + CadencyCommand, CadencyCommandOption, CadencyError, +}; use serenity::{ async_trait, client::Context, model::application::interaction::application_command::ApplicationCommandInteraction, @@ -24,16 +27,16 @@ impl Inspire { impl CadencyCommand for Inspire { async fn execute<'a>( &self, - ctx: &Context, - command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + _ctx: &Context, + _command: &'a mut ApplicationCommandInteraction, + response_builder: &'a mut ResponseBuilder, + ) -> Result { let inspire_url = Self::request_inspire_image_url().await.map_err(|err| { error!("{:?}", err); CadencyError::Command { message: "**The source of my inspiration is currently unavailable :(**".to_string(), } })?; - utils::create_response(ctx, command, &inspire_url).await?; - Ok(()) + Ok(response_builder.message(Some(inspire_url)).build()?) } } diff --git a/cadency_commands/src/now.rs b/cadency_commands/src/now.rs index 0237581..49161cc 100644 --- a/cadency_commands/src/now.rs +++ b/cadency_commands/src/now.rs @@ -1,4 +1,7 @@ -use cadency_core::{utils, CadencyCommand, CadencyCommandOption, CadencyError}; +use cadency_core::{ + response::{Response, ResponseBuilder}, + utils, CadencyCommand, CadencyCommandOption, CadencyError, +}; use serenity::{ async_trait, client::Context, model::application::interaction::application_command::ApplicationCommandInteraction, @@ -16,7 +19,8 @@ impl CadencyCommand for Now { &self, ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + response_builder: &'a mut ResponseBuilder, + ) -> Result { let guild_id = command.guild_id.ok_or(CadencyError::Command { message: ":x: **This command can only be executed on a server**".to_string(), })?; @@ -28,15 +32,11 @@ impl CadencyCommand for Now { let track = handler.queue().current().ok_or(CadencyError::Command { message: ":x: **No song is playing**".to_string(), })?; - utils::create_response( - ctx, - command, - &track.metadata().title.as_ref().map_or( + Ok(response_builder + .message(Some(track.metadata().title.as_ref().map_or( String::from(":x: **Could not add audio source to the queue!**"), |title| format!(":newspaper: `{title}`"), - ), - ) - .await?; - Ok(()) + ))) + .build()?) } } diff --git a/cadency_commands/src/pause.rs b/cadency_commands/src/pause.rs index 3354ae2..bcd7263 100644 --- a/cadency_commands/src/pause.rs +++ b/cadency_commands/src/pause.rs @@ -1,4 +1,7 @@ -use cadency_core::{utils, CadencyCommand, CadencyCommandOption, CadencyError}; +use cadency_core::{ + response::{Response, ResponseBuilder}, + utils, CadencyCommand, CadencyCommandOption, CadencyError, +}; use serenity::{ async_trait, client::Context, model::application::interaction::application_command::ApplicationCommandInteraction, @@ -17,7 +20,8 @@ impl CadencyCommand for Pause { &self, ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + response_builder: &'a mut ResponseBuilder, + ) -> Result { let guild_id = command.guild_id.ok_or(CadencyError::Command { message: ":x: **This command can only be executed on a server**".to_string(), })?; @@ -27,7 +31,7 @@ impl CadencyCommand for Pause { })?; let handler = call.lock().await; if handler.queue().is_empty() { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to pause**").await?; + response_builder.message(Some(":x: **Nothing to pause**".to_string())); } else { handler.queue().pause().map_err(|err| { error!("Failed to pause: {err:?}"); @@ -35,8 +39,8 @@ impl CadencyCommand for Pause { message: ":x: **Could not pause the track**".to_string(), } })?; - utils::voice::edit_deferred_response(ctx, command, ":pause_button: **Paused**").await?; + response_builder.message(Some(":pause_button: **Paused**".to_string())); } - Ok(()) + Ok(response_builder.build()?) } } diff --git a/cadency_commands/src/ping.rs b/cadency_commands/src/ping.rs index 61c77f1..badb91c 100644 --- a/cadency_commands/src/ping.rs +++ b/cadency_commands/src/ping.rs @@ -1,4 +1,7 @@ -use cadency_core::{utils, CadencyCommand, CadencyCommandOption, CadencyError}; +use cadency_core::{ + response::{Response, ResponseBuilder}, + CadencyCommand, CadencyCommandOption, CadencyError, +}; use serenity::{ async_trait, client::Context, model::application::interaction::application_command::ApplicationCommandInteraction, @@ -14,10 +17,12 @@ pub struct Ping { impl CadencyCommand for Ping { async fn execute<'a>( &self, - ctx: &Context, - command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { - utils::create_response(ctx, command, "Pong!").await?; - Ok(()) + _ctx: &Context, + _command: &'a mut ApplicationCommandInteraction, + response_builder: &'a mut ResponseBuilder, + ) -> Result { + Ok(response_builder + .message(Some("Pong!".to_string())) + .build()?) } } diff --git a/cadency_commands/src/play.rs b/cadency_commands/src/play.rs index 9d1f7fd..0c3b67b 100644 --- a/cadency_commands/src/play.rs +++ b/cadency_commands/src/play.rs @@ -1,5 +1,7 @@ use cadency_core::{ - handler::voice::InactiveHandler, utils, CadencyCommand, CadencyCommandOption, CadencyError, + handler::voice::InactiveHandler, + response::{Response, ResponseBuilder}, + utils, CadencyCommand, CadencyCommandOption, CadencyError, }; use reqwest::Url; use serenity::{ @@ -38,7 +40,8 @@ impl CadencyCommand for Play { &self, ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + response_builder: &'a mut ResponseBuilder, + ) -> Result { let (search_payload, is_url, is_playlist) = utils::get_option_value_at_position(command.data.options.as_ref(), 0) .and_then(|option_value| { @@ -68,7 +71,7 @@ impl CadencyCommand for Play { let call_handler = call.lock().await; call_handler.queue().is_empty() }; - if is_playlist { + let response_builder = if is_playlist { let playlist_items = cadency_yt_playlist::fetch_playlist_songs(search_payload.clone()).unwrap(); playlist_items @@ -110,14 +113,9 @@ impl CadencyCommand for Play { InactiveHandler { guild_id, manager }, ); drop(handler); - utils::voice::edit_deferred_response( - ctx, - command, - &format!( - ":white_check_mark: **Added ___{amount}___ songs to the queue with a duration of ___{total_duration:.2}___ mins** \n**Playing** :notes: `{search_payload}`", - ), - ) - .await?; + response_builder.message(Some(format!( + ":white_check_mark: **Added ___{amount}___ songs to the queue with a duration of ___{total_duration:.2}___ mins** \n**Playing** :notes: `{search_payload}`", + ))) } else { let added_song = utils::voice::add_song( call.clone(), @@ -146,19 +144,15 @@ impl CadencyCommand for Play { .as_ref() .map_or("unknown url", |url| url) }; - utils::voice::edit_deferred_response( - ctx, - command, - &format!( - ":white_check_mark: **Added song to the queue and started playing:** \n:notes: `{}` \n:link: `{}`", - song_url, - added_song - .title - .as_ref() - .map_or(":x: **Unknown title**", |title| title) - ), - ).await?; - } - Ok(()) + response_builder.message(Some(format!( + ":white_check_mark: **Added song to the queue and started playing:** \n:notes: `{}` \n:link: `{}`", + song_url, + added_song + .title + .as_ref() + .map_or(":x: **Unknown title**", |title| title) + ))) + }; + Ok(response_builder.build()?) } } diff --git a/cadency_commands/src/resume.rs b/cadency_commands/src/resume.rs index 5a88a48..b85424a 100644 --- a/cadency_commands/src/resume.rs +++ b/cadency_commands/src/resume.rs @@ -1,4 +1,7 @@ -use cadency_core::{utils, CadencyCommand, CadencyCommandOption, CadencyError}; +use cadency_core::{ + response::{Response, ResponseBuilder}, + utils, CadencyCommand, CadencyCommandOption, CadencyError, +}; use serenity::{ async_trait, client::Context, model::application::interaction::application_command::ApplicationCommandInteraction, @@ -17,7 +20,8 @@ impl CadencyCommand for Resume { &self, ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + response_builder: &'a mut ResponseBuilder, + ) -> Result { let guild_id = command.guild_id.ok_or(CadencyError::Command { message: ":x: **This command can only be executed on a server**".to_string(), })?; @@ -26,8 +30,8 @@ impl CadencyCommand for Resume { message: ":x: **No active voice session on the server**".to_string(), })?; let handler = call.lock().await; - if handler.queue().is_empty() { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to resume**").await?; + let response_builder = if handler.queue().is_empty() { + response_builder.message(Some(":x: **Nothing to resume**".to_string())) } else { handler.queue().resume().map_err(|err| { error!("Failed to resume: {err:?}"); @@ -35,8 +39,8 @@ impl CadencyCommand for Resume { message: ":x: **Could not resume**".to_string(), } })?; - utils::voice::edit_deferred_response(ctx, command, ":play_pause: **Resumed**").await?; - } - Ok(()) + response_builder.message(Some(":play_pause: **Resumed**".to_string())) + }; + Ok(response_builder.build()?) } } diff --git a/cadency_commands/src/skip.rs b/cadency_commands/src/skip.rs index bc5c83c..10f63f3 100644 --- a/cadency_commands/src/skip.rs +++ b/cadency_commands/src/skip.rs @@ -1,4 +1,7 @@ -use cadency_core::{utils, CadencyCommand, CadencyCommandOption, CadencyError}; +use cadency_core::{ + response::{Response, ResponseBuilder}, + utils, CadencyCommand, CadencyCommandOption, CadencyError, +}; use serenity::{ async_trait, client::Context, model::application::interaction::application_command::ApplicationCommandInteraction, @@ -17,7 +20,8 @@ impl CadencyCommand for Skip { &self, ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + response_builder: &'a mut ResponseBuilder, + ) -> Result { let guild_id = command.guild_id.ok_or(CadencyError::Command { message: ":x: **This command can only be executed on a server**".to_string(), })?; @@ -26,8 +30,8 @@ impl CadencyCommand for Skip { message: ":x: **No active voice session on the server**".to_string(), })?; let handler = call.lock().await; - if handler.queue().is_empty() { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to skip**").await?; + let response_builder = if handler.queue().is_empty() { + response_builder.message(Some(":x: **Nothing to skip**".to_string())) } else { handler.queue().skip().map_err(|err| { error!("Failed to skip: {err:?}"); @@ -35,13 +39,8 @@ impl CadencyCommand for Skip { message: ":x: **Could not skip the track**".to_string(), } })?; - utils::voice::edit_deferred_response( - ctx, - command, - ":fast_forward: **Skipped current song**", - ) - .await?; - } - Ok(()) + response_builder.message(Some(":fast_forward: **Skipped current song**".to_string())) + }; + Ok(response_builder.build()?) } } diff --git a/cadency_commands/src/slap.rs b/cadency_commands/src/slap.rs index 62a49ce..2a7a695 100644 --- a/cadency_commands/src/slap.rs +++ b/cadency_commands/src/slap.rs @@ -1,4 +1,7 @@ -use cadency_core::{utils, CadencyCommand, CadencyCommandOption, CadencyError}; +use cadency_core::{ + response::{Response, ResponseBuilder}, + utils, CadencyCommand, CadencyCommandOption, CadencyError, +}; use serenity::{ async_trait, client::Context, @@ -31,9 +34,10 @@ impl std::default::Default for Slap { impl CadencyCommand for Slap { async fn execute<'a>( &self, - ctx: &Context, + _ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + response_builder: &'a mut ResponseBuilder, + ) -> Result { let user = utils::get_option_value_at_position(command.data.options.as_ref(), 0) .and_then(|option_value| { if let CommandDataOptionValue::User(user, _) = option_value { @@ -47,34 +51,22 @@ impl CadencyCommand for Slap { message: ":x: *Invalid user provided*".to_string(), })?; - if user.id == command.user.id { - utils::create_response( - ctx, - command, - &format!("**Why do you want to slap yourself, {}?**", command.user), - ) - .await?; + let response_builder = if user.id == command.user.id { + response_builder.message(Some(format!( + "**Why do you want to slap yourself, {}?**", + command.user + ))) } else if user.id.0 == command.application_id.0 { - utils::create_response( - ctx, - command, - &format!( - "**Nope!\n{} slaps {} around a bit with a large trout!**", - user, command.user - ), - ) - .await?; + response_builder.message(Some(format!( + "**Nope!\n{} slaps {} around a bit with a large trout!**", + user, command.user + ))) } else { - utils::create_response( - ctx, - command, - &format!( - "**{} slaps {} around a bit with a large trout!**", - command.user, user - ), - ) - .await?; - } - Ok(()) + response_builder.message(Some(format!( + "**{} slaps {} around a bit with a large trout!**", + command.user, user + ))) + }; + Ok(response_builder.build()?) } } diff --git a/cadency_commands/src/stop.rs b/cadency_commands/src/stop.rs index 9c54f0b..65aac17 100644 --- a/cadency_commands/src/stop.rs +++ b/cadency_commands/src/stop.rs @@ -1,4 +1,7 @@ -use cadency_core::{utils, CadencyCommand, CadencyCommandOption, CadencyError}; +use cadency_core::{ + response::{Response, ResponseBuilder}, + utils, CadencyCommand, CadencyCommandOption, CadencyError, +}; use serenity::{ async_trait, client::Context, model::application::interaction::application_command::ApplicationCommandInteraction, @@ -17,7 +20,8 @@ impl CadencyCommand for Stop { &self, ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + response_builder: &'a mut ResponseBuilder, + ) -> Result { let guild_id = command.guild_id.ok_or(CadencyError::Command { message: ":x: **This command can only be executed on a server**".to_string(), })?; @@ -27,17 +31,12 @@ impl CadencyCommand for Stop { })?; let handler = call.lock().await; - if handler.queue().is_empty() { - utils::voice::edit_deferred_response(ctx, command, ":x: **Nothing to stop**").await?; + let response_builder = if handler.queue().is_empty() { + response_builder.message(Some(":x: **Nothing to stop**".to_string())) } else { handler.queue().stop(); - utils::voice::edit_deferred_response( - ctx, - command, - ":white_check_mark: :wastebasket: **Successfully stopped and cleared the playlist**", - ) - .await?; - } - Ok(()) + response_builder.message(Some(":white_check_mark: :wastebasket: **Successfully stopped and cleared the playlist**".to_string())) + }; + Ok(response_builder.build()?) } } diff --git a/cadency_commands/src/tracks.rs b/cadency_commands/src/tracks.rs index 52b2fac..e63e2a7 100644 --- a/cadency_commands/src/tracks.rs +++ b/cadency_commands/src/tracks.rs @@ -1,4 +1,7 @@ -use cadency_core::{utils, CadencyCommand, CadencyCommandOption, CadencyError}; +use cadency_core::{ + response::{Response, ResponseBuilder}, + utils, CadencyCommand, CadencyCommandOption, CadencyError, +}; use serenity::{ async_trait, builder::CreateEmbed, client::Context, model::application::interaction::application_command::ApplicationCommandInteraction, @@ -18,7 +21,8 @@ impl CadencyCommand for Tracks { &self, ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + response_builder: &'a mut ResponseBuilder, + ) -> Result { let guild_id = command.guild_id.ok_or(CadencyError::Command { message: ":x: **This command can only be executed on a server**".to_string(), })?; @@ -27,9 +31,8 @@ impl CadencyCommand for Tracks { message: ":x: **No active voice session on the server**".to_string(), })?; let handler = call.lock().await; - if handler.queue().is_empty() { - utils::voice::edit_deferred_response(ctx, command, ":x: **No tracks in the queue**") - .await?; + let response_builder = if handler.queue().is_empty() { + response_builder.message(Some(":x: **No tracks in the queue**".to_string())) } else { let queue_snapshot = handler.queue().current_queue(); let mut embeded_tracks = CreateEmbed::default(); @@ -52,9 +55,8 @@ impl CadencyCommand for Tracks { false, ); } - utils::voice::edit_deferred_response_with_embeded(ctx, command, vec![embeded_tracks]) - .await?; - } - Ok(()) + response_builder.embeds(vec![embeded_tracks]) + }; + Ok(response_builder.build()?) } } diff --git a/cadency_commands/src/urban.rs b/cadency_commands/src/urban.rs index 85ae507..76e224d 100644 --- a/cadency_commands/src/urban.rs +++ b/cadency_commands/src/urban.rs @@ -1,4 +1,7 @@ -use cadency_core::{utils, CadencyCommand, CadencyCommandOption, CadencyError}; +use cadency_core::{ + response::{Response, ResponseBuilder}, + utils, CadencyCommand, CadencyCommandOption, CadencyError, +}; use serenity::{ async_trait, builder::CreateEmbed, @@ -94,9 +97,10 @@ impl Urban { impl CadencyCommand for Urban { async fn execute<'a>( &self, - ctx: &Context, + _ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + respone_builder: &'a mut ResponseBuilder, + ) -> Result { let query = utils::get_option_value_at_position(command.data.options.as_ref(), 0) .and_then(|option_value| { if let CommandDataOptionValue::String(query) = option_value { @@ -117,16 +121,11 @@ impl CadencyCommand for Urban { message: ":x: *Failed to request urban dictionary*".to_string(), } })?; - if urbans.is_empty() { - utils::voice::edit_deferred_response(ctx, command, ":x: *Nothing found*").await?; + let respone_builder = if urbans.is_empty() { + respone_builder.message(Some(":x: *Nothing found*".to_string())) } else { - utils::voice::edit_deferred_response_with_embeded( - ctx, - command, - Self::create_embed(urbans), - ) - .await?; - } - Ok(()) + respone_builder.embeds(Self::create_embed(urbans)) + }; + Ok(respone_builder.build()?) } } diff --git a/cadency_core/src/command.rs b/cadency_core/src/command.rs index 56f54a6..c77782a 100644 --- a/cadency_core/src/command.rs +++ b/cadency_core/src/command.rs @@ -1,4 +1,8 @@ -use crate::{error::CadencyError, utils}; +use crate::{ + error::CadencyError, + response::{Response, ResponseBuilder}, + utils, +}; use serenity::{ async_trait, client::Context, @@ -68,7 +72,8 @@ pub trait CadencyCommand: Sync + Send + CadencyCommandBaseline { &self, ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError>; + response_builder: &'a mut ResponseBuilder, + ) -> Result; } pub(crate) struct Commands; diff --git a/cadency_core/src/error.rs b/cadency_core/src/error.rs index d7602aa..0163afb 100644 --- a/cadency_core/src/error.rs +++ b/cadency_core/src/error.rs @@ -1,3 +1,4 @@ +use crate::response::ResponseBuilderError; use thiserror::Error; #[derive(Error, Debug)] @@ -12,4 +13,6 @@ pub enum CadencyError { Response, #[error("Command execution failed: {message}")] Command { message: String }, + #[error("Response building failed")] + ResponseBuilder(#[from] ResponseBuilderError), } diff --git a/cadency_core/src/handler/command.rs b/cadency_core/src/handler/command.rs index c6b7d05..2dea43f 100644 --- a/cadency_core/src/handler/command.rs +++ b/cadency_core/src/handler/command.rs @@ -38,7 +38,9 @@ impl EventHandler for Handler { if let Some(cmd) = cmd_target { info!("⚡ Execute '{}' command", cmd.name()); + let mut prepared_response = ResponseBuilder::new(ResponseTiming::Instant); if cmd.deferred() { + prepared_response.timing(ResponseTiming::Deferred); ResponseBuilder::new(ResponseTiming::DeferredInfo) .build() .expect("Failed to build response") @@ -46,32 +48,44 @@ impl EventHandler for Handler { .await .expect("Unable to submit deferred info"); } - if let Err(command_error) = cmd.execute(&ctx, &mut command).await { - error!("❌ Command execution failed: {command_error:?}"); - let mut error_res_builder = ResponseBuilder::default(); - if cmd.deferred() { - error_res_builder.timing(ResponseTiming::Deferred); - } else { - error_res_builder.timing(ResponseTiming::Instant); + match cmd + .execute(&ctx, &mut command, &mut prepared_response) + .await + { + Ok(response) => { + response + .submit(&ctx, &mut command) + .await + .expect("To submit the command response"); + info!("✅ Command '{}' was successful", cmd.name()); } - match command_error { - CadencyError::Command { message } => { - error_res_builder.message(Some(message)); - error_res_builder.build() + Err(command_error) => { + error!("❌ Command execution failed: {command_error:?}"); + let mut error_res_builder = ResponseBuilder::default(); + if cmd.deferred() { + error_res_builder.timing(ResponseTiming::Deferred); + } else { + error_res_builder.timing(ResponseTiming::Instant); + } + match command_error { + CadencyError::Command { message } => { + error_res_builder.message(Some(message)); + error_res_builder.build() + } + _ => error_res_builder + .message(Some( + "**Oops! Something went terrible wrong.**".to_string(), + )) + .build(), } - _ => error_res_builder - .message(Some("**Oops! Something went terrible wrong.**".to_string())) - .build(), + .expect("Unable to build error response") + .submit(&ctx, &mut command) + .await + .map_err(|err| { + error!("❌ Fatal error! Is discord down? {:?}", err); + }) + .expect("Unable to send error response"); } - .expect("Unable to build error response") - .submit(&ctx, &mut command) - .await - .map_err(|err| { - error!("❌ Fatal error! Is discord down? {:?}", err); - }) - .expect("Unable to send error response"); - } else { - info!("✅ Command '{}' was successful", cmd.name()) } } else { command_not_implemented(&ctx, &command) diff --git a/cadency_core/src/utils/mod.rs b/cadency_core/src/utils/mod.rs index a806046..17d202a 100644 --- a/cadency_core/src/utils/mod.rs +++ b/cadency_core/src/utils/mod.rs @@ -1,13 +1,9 @@ -use crate::{command::Commands, error::CadencyError, CadencyCommand}; +use crate::{command::Commands, CadencyCommand}; use serenity::{ - builder::CreateEmbed, client::Context, model::{ - application::interaction::{ - application_command::{ - ApplicationCommandInteraction, CommandDataOption, CommandDataOptionValue, - }, - InteractionResponseType, + application::interaction::application_command::{ + CommandDataOption, CommandDataOptionValue, }, gateway::Activity, user::OnlineStatus, @@ -40,44 +36,3 @@ pub fn get_option_value_at_position( .get(position) .and_then(|option| option.resolved.as_ref()) } - -pub async fn create_response<'a>( - ctx: &Context, - interaction: &mut ApplicationCommandInteraction, - content: &str, -) -> Result<(), CadencyError> { - interaction - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| message.content(content)) - }) - .await - .map_err(|err| { - error!("Failed to submit response: {}", err); - CadencyError::Response - }) -} - -pub async fn create_response_with_embed<'a>( - ctx: &Context, - interaction: &mut ApplicationCommandInteraction, - embeds: Vec, -) -> Result<(), CadencyError> { - interaction - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - for embed in embeds { - message.add_embed(embed); - } - message - }) - }) - .await - .map_err(|err| { - error!("Failed to submit embed response: {}", err); - CadencyError::Response - }) -} diff --git a/cadency_core/src/utils/voice.rs b/cadency_core/src/utils/voice.rs index 9c43e82..6b961f6 100644 --- a/cadency_core/src/utils/voice.rs +++ b/cadency_core/src/utils/voice.rs @@ -1,15 +1,10 @@ -use crate::error::CadencyError; -use crate::utils; +use crate::{error::CadencyError, utils}; use reqwest::Url; -use serenity::model; use serenity::{ - builder::CreateEmbed, client::Context, - model::application::interaction::{ - application_command::{ - ApplicationCommandInteraction, CommandDataOption, CommandDataOptionValue, - }, - InteractionResponseType, + model, + model::application::interaction::application_command::{ + ApplicationCommandInteraction, CommandDataOption, CommandDataOptionValue, }, }; use songbird::{input::Input, input::Restartable}; @@ -100,52 +95,3 @@ pub async fn get_songbird(ctx: &Context) -> std::sync::Arc { .await .expect("Failed to get songbird manager") } - -pub async fn create_deferred_response<'a>( - ctx: &Context, - interaction: &mut ApplicationCommandInteraction, -) -> Result<(), CadencyError> { - interaction - .create_interaction_response(&ctx.http, |response| { - response.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) - .await - .map_err(|err| { - error!("Failed to submit deferred message: {err}"); - CadencyError::Response - }) -} - -pub async fn edit_deferred_response<'a>( - ctx: &Context, - interaction: &'a mut ApplicationCommandInteraction, - content: &str, -) -> Result<(), CadencyError> { - interaction - .edit_original_interaction_response(&ctx.http, |previous_response| { - previous_response.content(content) - }) - .await - .map_err(|err| { - error!("Failed to edit deferred message: {err}"); - CadencyError::Response - })?; - Ok(()) -} - -pub async fn edit_deferred_response_with_embeded<'a>( - ctx: &Context, - interaction: &mut ApplicationCommandInteraction, - embeded_content: Vec, -) -> Result<(), CadencyError> { - interaction - .edit_original_interaction_response(&ctx.http, |previous_response| { - previous_response.add_embeds(embeded_content) - }) - .await - .map_err(|err| { - error!("Failed to edit deferred message with embeded content: {err}"); - CadencyError::Response - })?; - Ok(()) -} diff --git a/examples/custom_commands/examples/custom_commands.rs b/examples/custom_commands/examples/custom_commands.rs index b7c498e..87f6f94 100644 --- a/examples/custom_commands/examples/custom_commands.rs +++ b/examples/custom_commands/examples/custom_commands.rs @@ -5,6 +5,7 @@ extern crate cadency_codegen; use cadency_commands::Fib; use cadency_core::{ + response::{Response, ResponseBuilder}, setup_commands, utils, Cadency, CadencyCommand, CadencyCommandOption, CadencyError, }; use serenity::{ @@ -42,9 +43,10 @@ impl CadencyCommand for Hello { // The following code will get executed by the cadency command handler if the command is called async fn execute<'a>( &self, - ctx: &Context, + _ctx: &Context, command: &'a mut ApplicationCommandInteraction, - ) -> Result<(), CadencyError> { + response_builder: &'a mut ResponseBuilder, + ) -> Result { let user_arg = utils::get_option_value_at_position(command.data.options.as_ref(), 0) .and_then(|option_value| { if let CommandDataOptionValue::User(user, _) = option_value { @@ -55,8 +57,9 @@ impl CadencyCommand for Hello { } }) .expect("A user as command argument"); - utils::create_response(ctx, command, &format!("**Hello {user_arg}!**",)).await?; - Ok(()) + Ok(response_builder + .message(Some(format!("**Hello {user_arg}!**"))) + .build()?) } } From 5705d15eabe1e4434335b1c036b076ac3285211c Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sun, 5 Feb 2023 17:57:20 +0100 Subject: [PATCH 12/16] fix(urban): Remove json keys that are not present anymore --- cadency_commands/src/urban.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cadency_commands/src/urban.rs b/cadency_commands/src/urban.rs index 76e224d..d1e3ff1 100644 --- a/cadency_commands/src/urban.rs +++ b/cadency_commands/src/urban.rs @@ -19,11 +19,9 @@ struct UrbanEntry { pub definition: String, pub permalink: String, pub thumbs_up: i64, - pub sound_urls: Vec, pub author: String, pub word: String, pub defid: i64, - pub current_vote: String, pub written_on: String, pub example: String, pub thumbs_down: i64, From 49826fdb6eebb2089dcd4fd0f6ba2fc65d522392 Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Sun, 5 Feb 2023 18:11:01 +0100 Subject: [PATCH 13/16] refactor(core): Handle join errors in command handler --- cadency_core/src/handler/command.rs | 15 ++++++++------- cadency_core/src/utils/voice.rs | 4 +++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/cadency_core/src/handler/command.rs b/cadency_core/src/handler/command.rs index 2dea43f..b42a1d4 100644 --- a/cadency_core/src/handler/command.rs +++ b/cadency_core/src/handler/command.rs @@ -69,15 +69,16 @@ impl EventHandler for Handler { } match command_error { CadencyError::Command { message } => { - error_res_builder.message(Some(message)); - error_res_builder.build() + error_res_builder.message(Some(message)) } - _ => error_res_builder - .message(Some( - "**Oops! Something went terrible wrong.**".to_string(), - )) - .build(), + CadencyError::Join => error_res_builder.message(Some( + "❌ **I could not join your voice channel**".to_string(), + )), + _ => error_res_builder.message(Some( + "**Oops! Something went terrible wrong.**".to_string(), + )), } + .build() .expect("Unable to build error response") .submit(&ctx, &mut command) .await diff --git a/cadency_core/src/utils/voice.rs b/cadency_core/src/utils/voice.rs index 6b961f6..91f7e13 100644 --- a/cadency_core/src/utils/voice.rs +++ b/cadency_core/src/utils/voice.rs @@ -31,7 +31,9 @@ pub async fn join( CadencyError, > { let manager = get_songbird(ctx).await; - let guild_id = command.guild_id.ok_or(CadencyError::Join)?; + let guild_id = command.guild_id.ok_or(CadencyError::Command { + message: ":x: *To use this command, you must be on a server*".to_string(), + })?; let channel_id = ctx .cache .guild(guild_id) From 1af357cc2cff45dabb806491daaf178c7d46c9a1 Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Mon, 6 Feb 2023 00:08:19 +0100 Subject: [PATCH 14/16] refactor(codegen): Generate command options from macro attribute --- cadency_codegen/Cargo.toml | 1 + cadency_codegen/src/derive.rs | 162 ++++++++++++++++++++++++++-------- cadency_codegen/src/lib.rs | 2 +- cadency_core/src/command.rs | 2 +- 4 files changed, 126 insertions(+), 41 deletions(-) diff --git a/cadency_codegen/Cargo.toml b/cadency_codegen/Cargo.toml index bafa6b2..65ea946 100644 --- a/cadency_codegen/Cargo.toml +++ b/cadency_codegen/Cargo.toml @@ -12,5 +12,6 @@ proc_macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +proc-macro2 = "1.0.51" quote = "1.0.23" syn = "1.0.107" diff --git a/cadency_codegen/src/derive.rs b/cadency_codegen/src/derive.rs index 2211f9d..2d1822e 100644 --- a/cadency_codegen/src/derive.rs +++ b/cadency_codegen/src/derive.rs @@ -1,58 +1,142 @@ use proc_macro::TokenStream; -use syn::{DeriveInput, Lit, Meta}; +use syn::{DeriveInput, Lit, Meta, NestedMeta}; pub(crate) fn impl_command_baseline(derive_input: DeriveInput) -> TokenStream { let struct_name = derive_input.ident; let mut command_name = struct_name.to_string().to_lowercase(); let mut description = "".to_string(); let mut deferred = false; + let mut arguments: Vec<(String, String, String, bool)> = vec![]; for attr in derive_input.attrs.iter() { let attr_meta = attr.parse_meta().unwrap(); - if let Meta::NameValue(derive_attr) = attr_meta { - match derive_attr.path.get_ident().unwrap().to_string().as_str() { - "name" => { - if let Lit::Str(name_attr_value) = derive_attr.lit { - command_name = name_attr_value.value(); - } else { - return syn::Error::new( - derive_attr.lit.span(), - "'name' attribute must be a string", - ) - .to_compile_error() - .into(); + match attr_meta { + Meta::NameValue(derive_attr) => { + match derive_attr.path.get_ident().unwrap().to_string().as_str() { + "name" => { + if let Lit::Str(name_attr_value) = derive_attr.lit { + command_name = name_attr_value.value(); + } else { + return syn::Error::new( + derive_attr.lit.span(), + "'name' attribute must be a string", + ) + .to_compile_error() + .into(); + } } - } - "description" => { - if let Lit::Str(description_attr_value) = derive_attr.lit { - description = description_attr_value.value(); - } else { - return syn::Error::new( - derive_attr.lit.span(), - "'description' attribute must be a string", - ) - .to_compile_error() - .into(); + "description" => { + if let Lit::Str(description_attr_value) = derive_attr.lit { + description = description_attr_value.value(); + } else { + return syn::Error::new( + derive_attr.lit.span(), + "'description' attribute must be a string", + ) + .to_compile_error() + .into(); + } + } + "deferred" => { + if let Lit::Bool(deferred_attr_value) = derive_attr.lit { + deferred = deferred_attr_value.value(); + } else { + return syn::Error::new( + derive_attr.lit.span(), + "'deferred' attribute must be a bool", + ) + .to_compile_error() + .into(); + } } + _ => (), } - "deferred" => { - if let Lit::Bool(deferred_attr_value) = derive_attr.lit { - deferred = deferred_attr_value.value(); - } else { - return syn::Error::new( - derive_attr.lit.span(), - "'deferred' attribute must be a bool", - ) - .to_compile_error() - .into(); + } + Meta::List(derive_attr_list) => { + if derive_attr_list + .path + .get_ident() + .unwrap() + .to_string() + .as_str() + == "argument" + { + let mut name: Option = None; + let mut description: Option = None; + let mut kind: Option = None; + let mut required = true; + + for arguments_attr_meta in derive_attr_list.nested.iter() { + if let NestedMeta::Meta(Meta::NameValue(argument_item)) = + arguments_attr_meta + { + match argument_item.path.get_ident().unwrap().to_string().as_str() { + "name" => { + if let Lit::Str(argument_name) = argument_item.lit.to_owned() { + name = Some(argument_name.value()); + } else { + return syn::Error::new(argument_item.lit.span(), "Name must be a string").to_compile_error().into() + } + } + "description" => { + if let Lit::Str(argument_description) = argument_item.lit.to_owned() { + description = Some(argument_description.value()); + } else { + return syn::Error::new(argument_item.lit.span(), "Description must be a string").to_compile_error().into() + } + } + "kind" => { + if let Lit::Str(argument_kind) = argument_item.lit.to_owned() { + kind = Some(argument_kind.value()); + } else { + return syn::Error::new(argument_item.lit.span(), "Kind must be a string").to_compile_error().into() + } + } + "required" => { + if let Lit::Bool(argument_required) = argument_item.lit.to_owned() { + required = argument_required.value(); + } + } + _ => { + return syn::Error::new(argument_item.path.get_ident().unwrap().span(), "Only 'name', 'description', 'kind' and 'required' are supported") + .to_compile_error() + .into() + } + } + } + } + match (name, description, kind) { + (Some(name), Some(description), Some(kind)) => { + arguments.push((name, description, kind, required)); + } + _ => { + return syn::Error::new( + derive_attr_list.path.get_ident().unwrap().span(), + "You need to specify at least 'name', 'description' and 'kind'", + ) + .to_compile_error() + .into(); + } } } - _ => (), } + _ => (), } } + let argument_tokens = arguments.iter().map(|(name, description, kind, required)| { + let kind_token: proc_macro2::TokenStream = kind.parse().unwrap(); + quote! { + __CadencyCommandOption { + name: #name, + description: #description, + kind: __CommandOptionType::#kind_token, + required: #required + } + } + }); quote! { - use cadency_core::{self, CadencyCommandBaseline}; - impl cadency_core::CadencyCommandBaseline for #struct_name { + use cadency_core::{CadencyCommandBaseline as __CadencyCommandBaseline, CadencyCommandOption as __CadencyCommandOption}; + use serenity::model::application::command::CommandOptionType as __CommandOptionType; + impl __CadencyCommandBaseline for #struct_name { fn name(&self) -> String { String::from(#command_name) } @@ -65,8 +149,8 @@ pub(crate) fn impl_command_baseline(derive_input: DeriveInput) -> TokenStream { #deferred } - fn options(&self) -> &Vec { - self.options.as_ref() + fn options(&self) -> Vec<__CadencyCommandOption> { + vec![#(#argument_tokens),*] } } } diff --git a/cadency_codegen/src/lib.rs b/cadency_codegen/src/lib.rs index bb58608..74d87f7 100644 --- a/cadency_codegen/src/lib.rs +++ b/cadency_codegen/src/lib.rs @@ -6,7 +6,7 @@ use syn::{parse_macro_input, DeriveInput}; mod derive; -#[proc_macro_derive(CommandBaseline, attributes(name, description, deferred))] +#[proc_macro_derive(CommandBaseline, attributes(name, description, deferred, argument))] pub fn derive_command_baseline(input_item: TokenStream) -> TokenStream { // Parse token stream into derive syntax tree let tree: DeriveInput = parse_macro_input!(input_item); diff --git a/cadency_core/src/command.rs b/cadency_core/src/command.rs index c77782a..0c8186e 100644 --- a/cadency_core/src/command.rs +++ b/cadency_core/src/command.rs @@ -37,7 +37,7 @@ pub trait CadencyCommandBaseline { fn name(&self) -> String; fn description(&self) -> String; fn deferred(&self) -> bool; - fn options(&self) -> &Vec; + fn options(&self) -> Vec; } pub struct CadencyCommandOption { From 2075371ec895fbf49459282633fa948d62bce24a Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Mon, 6 Feb 2023 00:10:57 +0100 Subject: [PATCH 15/16] refactor(commands): Use argument attribute to add options --- cadency_commands/src/fib.rs | 31 ++--- cadency_commands/src/inspire.rs | 6 +- cadency_commands/src/lib.rs | 116 +++++++++++------- cadency_commands/src/now.rs | 6 +- cadency_commands/src/pause.rs | 6 +- cadency_commands/src/ping.rs | 6 +- cadency_commands/src/play.rs | 31 ++--- cadency_commands/src/resume.rs | 6 +- cadency_commands/src/skip.rs | 6 +- cadency_commands/src/slap.rs | 31 ++--- cadency_commands/src/stop.rs | 6 +- cadency_commands/src/tracks.rs | 6 +- cadency_commands/src/urban.rs | 27 +--- .../examples/custom_commands.rs | 28 +---- 14 files changed, 133 insertions(+), 179 deletions(-) diff --git a/cadency_commands/src/fib.rs b/cadency_commands/src/fib.rs index b74f5cc..083ab61 100644 --- a/cadency_commands/src/fib.rs +++ b/cadency_commands/src/fib.rs @@ -1,34 +1,23 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyCommandOption, CadencyError, + utils, CadencyCommand, CadencyError, }; use serenity::{ async_trait, client::Context, - model::application::{ - command::CommandOptionType, - interaction::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}, + model::application::interaction::application_command::{ + ApplicationCommandInteraction, CommandDataOptionValue, }, }; -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] #[description = "Calculate the nth number in the fibonacci sequence"] -pub struct Fib { - options: Vec, -} - -impl std::default::Default for Fib { - fn default() -> Self { - Self { - options: vec![CadencyCommandOption { - name: "number", - description: "The number in the fibonacci sequence", - kind: CommandOptionType::Integer, - required: true, - }], - } - } -} +#[argument( + name = "number", + description = "The number in the fibonacci sequence", + kind = "Integer" +)] +pub struct Fib {} impl Fib { fn calc(n: &i64) -> f64 { diff --git a/cadency_commands/src/inspire.rs b/cadency_commands/src/inspire.rs index 3655f5a..a7cf345 100644 --- a/cadency_commands/src/inspire.rs +++ b/cadency_commands/src/inspire.rs @@ -1,6 +1,6 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - CadencyCommand, CadencyCommandOption, CadencyError, + CadencyCommand, CadencyError, }; use serenity::{ async_trait, client::Context, @@ -9,9 +9,7 @@ use serenity::{ #[derive(CommandBaseline, Default)] #[description = "Say something really inspiring!"] -pub struct Inspire { - options: Vec, -} +pub struct Inspire {} impl Inspire { async fn request_inspire_image_url() -> Result { diff --git a/cadency_commands/src/lib.rs b/cadency_commands/src/lib.rs index 96bf0b3..d99eeb2 100644 --- a/cadency_commands/src/lib.rs +++ b/cadency_commands/src/lib.rs @@ -32,14 +32,10 @@ pub use urban::Urban; #[cfg(test)] mod test { - use cadency_core::CadencyCommandOption; - #[test] fn impl_commandbaseline_trait_with_macro() { #[derive(cadency_codegen::CommandBaseline)] - struct Test { - options: Vec, - } + struct Test {} assert!(true) } @@ -47,12 +43,8 @@ mod test { fn return_lowercase_struct_name_as_name() { #[derive(cadency_codegen::CommandBaseline)] #[description = "123"] - struct Test { - options: Vec, - } - let test = Test { - options: Vec::new(), - }; + struct Test {} + let test = Test {}; let name: String = test.name(); assert_eq!(name, "test", "Test command name to be lowercase {name}") } @@ -62,12 +54,8 @@ mod test { #[derive(cadency_codegen::CommandBaseline)] #[name = "my_test"] #[description = "123"] - struct Test { - options: Vec, - } - let test = Test { - options: Vec::new(), - }; + struct Test {} + let test = Test {}; let name: String = test.name(); assert_eq!( name, "my_test", @@ -79,12 +67,8 @@ mod test { fn not_return_uppercase_struct_name_as_name() { #[derive(cadency_codegen::CommandBaseline)] #[description = "123"] - struct Test { - options: Vec, - } - let test = Test { - options: Vec::new(), - }; + struct Test {} + let test = Test {}; let name: String = test.name(); assert_ne!( name, "Test", @@ -96,12 +80,8 @@ mod test { fn return_attribute_description() { #[derive(cadency_codegen::CommandBaseline)] #[description = "123"] - struct Test { - options: Vec, - } - let test = Test { - options: Vec::new(), - }; + struct Test {} + let test = Test {}; assert_eq!( test.description(), "123", @@ -113,12 +93,8 @@ mod test { fn return_default_deferred_config() { #[derive(cadency_codegen::CommandBaseline)] #[description = "123"] - struct Test { - options: Vec, - } - let test = Test { - options: Vec::new(), - }; + struct Test {} + let test = Test {}; assert_eq!( test.deferred(), false, @@ -131,12 +107,70 @@ mod test { #[derive(cadency_codegen::CommandBaseline)] #[description = "123"] #[deferred = true] - struct Test { - options: Vec, - } - let test = Test { - options: Vec::new(), - }; + struct Test {} + let test = Test {}; assert!(test.deferred(), "Test command should be deferred") } + + #[test] + fn return_empty_options_by_default() { + #[derive(cadency_codegen::CommandBaseline)] + struct Test {} + let test = Test {}; + assert_eq!(test.options().len(), 0); + } + + #[test] + fn return_derived_option() { + use serenity::model::application::command::CommandOptionType; + #[derive(cadency_codegen::CommandBaseline)] + #[argument( + name = "say", + description = "Word to say", + kind = "String", + required = false + )] + struct Test {} + let test = Test {}; + let arguments = test.options(); + assert_eq!(arguments.len(), 1); + let argument = arguments.get(0).unwrap(); + assert_eq!(argument.name, "say"); + assert_eq!(argument.description, "Word to say"); + assert_eq!(argument.kind, CommandOptionType::String); + assert_eq!(argument.required, false); + } + + #[test] + fn return_required_option_by_default() { + #[derive(cadency_codegen::CommandBaseline)] + #[argument(name = "say", description = "Word to say", kind = "String")] + struct Test {} + let test = Test {}; + let arguments = test.options(); + assert_eq!(arguments.len(), 1); + let argument = arguments.get(0).unwrap(); + assert!(argument.required); + } + + #[test] + fn return_multiple_options() { + use serenity::model::application::command::CommandOptionType; + + #[derive(cadency_codegen::CommandBaseline)] + #[argument(name = "say", description = "Word to say", kind = "String")] + #[argument(name = "target", description = "The target user", kind = "User")] + struct Test {} + let test = Test {}; + let arguments = test.options(); + assert_eq!(arguments.len(), 2); + let first_argument = arguments.get(0).unwrap(); + let second_argument = arguments.get(1).unwrap(); + assert_eq!(first_argument.name, "say"); + assert_eq!(first_argument.description, "Word to say"); + assert_eq!(first_argument.kind, CommandOptionType::String); + assert_eq!(second_argument.name, "target"); + assert_eq!(second_argument.description, "The target user"); + assert_eq!(second_argument.kind, CommandOptionType::User); + } } diff --git a/cadency_commands/src/now.rs b/cadency_commands/src/now.rs index 49161cc..c1f2036 100644 --- a/cadency_commands/src/now.rs +++ b/cadency_commands/src/now.rs @@ -1,6 +1,6 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyCommandOption, CadencyError, + utils, CadencyCommand, CadencyError, }; use serenity::{ async_trait, client::Context, @@ -9,9 +9,7 @@ use serenity::{ #[derive(CommandBaseline, Default)] #[description = "Shows current song"] -pub struct Now { - options: Vec, -} +pub struct Now {} #[async_trait] impl CadencyCommand for Now { diff --git a/cadency_commands/src/pause.rs b/cadency_commands/src/pause.rs index bcd7263..e979149 100644 --- a/cadency_commands/src/pause.rs +++ b/cadency_commands/src/pause.rs @@ -1,6 +1,6 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyCommandOption, CadencyError, + utils, CadencyCommand, CadencyError, }; use serenity::{ async_trait, client::Context, @@ -10,9 +10,7 @@ use serenity::{ #[derive(CommandBaseline, Default)] #[description = "Pause the current song"] #[deferred = true] -pub struct Pause { - options: Vec, -} +pub struct Pause {} #[async_trait] impl CadencyCommand for Pause { diff --git a/cadency_commands/src/ping.rs b/cadency_commands/src/ping.rs index badb91c..8a4ece1 100644 --- a/cadency_commands/src/ping.rs +++ b/cadency_commands/src/ping.rs @@ -1,6 +1,6 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - CadencyCommand, CadencyCommandOption, CadencyError, + CadencyCommand, CadencyError, }; use serenity::{ async_trait, client::Context, @@ -9,9 +9,7 @@ use serenity::{ #[derive(CommandBaseline, Default)] #[description = "Play Ping-Pong"] -pub struct Ping { - options: Vec, -} +pub struct Ping {} #[async_trait] impl CadencyCommand for Ping { diff --git a/cadency_commands/src/play.rs b/cadency_commands/src/play.rs index 0c3b67b..23fc165 100644 --- a/cadency_commands/src/play.rs +++ b/cadency_commands/src/play.rs @@ -1,38 +1,27 @@ use cadency_core::{ handler::voice::InactiveHandler, response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyCommandOption, CadencyError, + utils, CadencyCommand, CadencyError, }; use reqwest::Url; use serenity::{ async_trait, client::Context, - model::application::{ - command::CommandOptionType, - interaction::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}, + model::application::interaction::application_command::{ + ApplicationCommandInteraction, CommandDataOptionValue, }, }; use songbird::events::Event; -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] #[description = "Play a song from Youtube"] #[deferred = true] -pub struct Play { - options: Vec, -} - -impl std::default::Default for Play { - fn default() -> Self { - Self { - options: vec![CadencyCommandOption { - name: "query", - description: "URL or search query like: 'Hey Jude Beatles'", - kind: CommandOptionType::String, - required: true, - }], - } - } -} +#[argument( + name = "quiery", + description = "URL or search query like: 'Hey Jude Beatles'", + kind = "String" +)] +pub struct Play {} #[async_trait] impl CadencyCommand for Play { diff --git a/cadency_commands/src/resume.rs b/cadency_commands/src/resume.rs index b85424a..7ffa5cb 100644 --- a/cadency_commands/src/resume.rs +++ b/cadency_commands/src/resume.rs @@ -1,6 +1,6 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyCommandOption, CadencyError, + utils, CadencyCommand, CadencyError, }; use serenity::{ async_trait, client::Context, @@ -10,9 +10,7 @@ use serenity::{ #[derive(CommandBaseline, Default)] #[description = "Resume current song if paused"] #[deferred = true] -pub struct Resume { - options: Vec, -} +pub struct Resume {} #[async_trait] impl CadencyCommand for Resume { diff --git a/cadency_commands/src/skip.rs b/cadency_commands/src/skip.rs index 10f63f3..23f2adf 100644 --- a/cadency_commands/src/skip.rs +++ b/cadency_commands/src/skip.rs @@ -1,6 +1,6 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyCommandOption, CadencyError, + utils, CadencyCommand, CadencyError, }; use serenity::{ async_trait, client::Context, @@ -10,9 +10,7 @@ use serenity::{ #[derive(CommandBaseline, Default)] #[description = "Skip current song"] #[deferred = true] -pub struct Skip { - options: Vec, -} +pub struct Skip {} #[async_trait] impl CadencyCommand for Skip { diff --git a/cadency_commands/src/slap.rs b/cadency_commands/src/slap.rs index 2a7a695..2770a85 100644 --- a/cadency_commands/src/slap.rs +++ b/cadency_commands/src/slap.rs @@ -1,34 +1,23 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyCommandOption, CadencyError, + utils, CadencyCommand, CadencyError, }; use serenity::{ async_trait, client::Context, - model::application::{ - command::CommandOptionType, - interaction::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}, + model::application::interaction::application_command::{ + ApplicationCommandInteraction, CommandDataOptionValue, }, }; -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] #[description = "Slap someone with a large trout!"] -pub struct Slap { - options: Vec, -} - -impl std::default::Default for Slap { - fn default() -> Self { - Self { - options: vec![CadencyCommandOption { - name: "target", - description: "The user you want to slap", - kind: CommandOptionType::User, - required: true, - }], - } - } -} +#[argument( + name = "target", + description = "The user you want to slap", + kind = "User" +)] +pub struct Slap {} #[async_trait] impl CadencyCommand for Slap { diff --git a/cadency_commands/src/stop.rs b/cadency_commands/src/stop.rs index 65aac17..5fe44a8 100644 --- a/cadency_commands/src/stop.rs +++ b/cadency_commands/src/stop.rs @@ -1,6 +1,6 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyCommandOption, CadencyError, + utils, CadencyCommand, CadencyError, }; use serenity::{ async_trait, client::Context, @@ -10,9 +10,7 @@ use serenity::{ #[derive(CommandBaseline, Default)] #[description = "Stop music and clear the track list"] #[deferred = true] -pub struct Stop { - options: Vec, -} +pub struct Stop {} #[async_trait] impl CadencyCommand for Stop { diff --git a/cadency_commands/src/tracks.rs b/cadency_commands/src/tracks.rs index e63e2a7..bc20f3e 100644 --- a/cadency_commands/src/tracks.rs +++ b/cadency_commands/src/tracks.rs @@ -1,6 +1,6 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyCommandOption, CadencyError, + utils, CadencyCommand, CadencyError, }; use serenity::{ async_trait, builder::CreateEmbed, client::Context, @@ -11,9 +11,7 @@ use serenity::{ #[derive(CommandBaseline, Default)] #[description = "List all tracks in the queue"] #[deferred = true] -pub struct Tracks { - options: Vec, -} +pub struct Tracks {} #[async_trait] impl CadencyCommand for Tracks { diff --git a/cadency_commands/src/urban.rs b/cadency_commands/src/urban.rs index d1e3ff1..ed89130 100644 --- a/cadency_commands/src/urban.rs +++ b/cadency_commands/src/urban.rs @@ -1,14 +1,13 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyCommandOption, CadencyError, + utils, CadencyCommand, CadencyError, }; use serenity::{ async_trait, builder::CreateEmbed, client::Context, - model::application::{ - command::CommandOptionType, - interaction::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}, + model::application::interaction::application_command::{ + ApplicationCommandInteraction, CommandDataOptionValue, }, utils::Color, }; @@ -32,25 +31,11 @@ struct UrbanResult { pub list: Vec, } -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] #[description = "Searches the Urbandictionary for your query"] #[deferred = true] -pub struct Urban { - options: Vec, -} - -impl std::default::Default for Urban { - fn default() -> Self { - Self { - options: vec![CadencyCommandOption { - name: "query", - description: "Your search query", - kind: CommandOptionType::String, - required: true, - }], - } - } -} +#[argument(name = "query", description = "Your search query", kind = "String")] +pub struct Urban {} impl Urban { async fn request_urban_dictionary_entries( diff --git a/examples/custom_commands/examples/custom_commands.rs b/examples/custom_commands/examples/custom_commands.rs index 87f6f94..32e987c 100644 --- a/examples/custom_commands/examples/custom_commands.rs +++ b/examples/custom_commands/examples/custom_commands.rs @@ -6,37 +6,21 @@ extern crate cadency_codegen; use cadency_commands::Fib; use cadency_core::{ response::{Response, ResponseBuilder}, - setup_commands, utils, Cadency, CadencyCommand, CadencyCommandOption, CadencyError, + setup_commands, utils, Cadency, CadencyCommand, CadencyError, }; use serenity::{ async_trait, client::Context, - model::application::{ - command::CommandOptionType, - interaction::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}, + model::application::interaction::application_command::{ + ApplicationCommandInteraction, CommandDataOptionValue, }, }; // This is your custom command with the name "hello" -#[derive(CommandBaseline)] +#[derive(CommandBaseline, Default)] #[description = "Say Hello to a user"] -struct Hello { - // The allowed list of command arguments - options: Vec, -} - -impl std::default::Default for Hello { - fn default() -> Self { - Self { - options: vec![CadencyCommandOption { - name: "user", - description: "The user to greet", - kind: CommandOptionType::User, - required: true, - }], - } - } -} +#[argument(name = "user", description = "The user to great", kind = "User")] +struct Hello {} #[async_trait] impl CadencyCommand for Hello { From e1ed8c0e7544c9d39bb9ce3dad0d2866d625d46f Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Mon, 6 Feb 2023 22:28:17 +0100 Subject: [PATCH 16/16] chore(version): Bump version to v0.3.1 --- cadency/Cargo.toml | 6 +++--- cadency_codegen/Cargo.toml | 2 +- cadency_commands/Cargo.toml | 8 ++++---- cadency_core/Cargo.toml | 2 +- cadency_yt_playlist/Cargo.toml | 2 +- examples/custom_commands/Cargo.toml | 10 +++++----- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cadency/Cargo.toml b/cadency/Cargo.toml index 2d9b156..6ba82af 100644 --- a/cadency/Cargo.toml +++ b/cadency/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cadency" -version = "0.3.0" +version = "0.3.1" edition = "2021" description = "An extensible discord bot with support with music commands" license = "MIT" @@ -14,11 +14,11 @@ log = "0.4.17" [dependencies.cadency_core] path = "../cadency_core" -version = "0.3.0" +version = "0.3.1" [dependencies.cadency_commands] path = "../cadency_commands" -version = "0.3.0" +version = "0.3.1" [dependencies.tokio] version = "1.25.0" diff --git a/cadency_codegen/Cargo.toml b/cadency_codegen/Cargo.toml index 65ea946..8fad4d8 100644 --- a/cadency_codegen/Cargo.toml +++ b/cadency_codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cadency_codegen" -version = "0.3.0" +version = "0.3.1" edition = "2021" description = "Library with codegen macros for the cadency discord bot" license = "MIT" diff --git a/cadency_commands/Cargo.toml b/cadency_commands/Cargo.toml index 9e33ed4..9a90ffb 100644 --- a/cadency_commands/Cargo.toml +++ b/cadency_commands/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cadency_commands" -version = "0.3.0" +version = "0.3.1" edition = "2021" description = "Library with a collection of some commands for the cadency discord bot" license = "MIT" @@ -27,15 +27,15 @@ features = ["builtin-queue", "yt-dlp"] [dependencies.cadency_core] path = "../cadency_core" -version = "0.3.0" +version = "0.3.1" [dependencies.cadency_codegen] path = "../cadency_codegen" -version = "0.3.0" +version = "0.3.1" [dependencies.cadency_yt_playlist] path = "../cadency_yt_playlist" -version = "0.3.0" +version = "0.3.1" [dev-dependencies.tokio] version = "1.25.0" diff --git a/cadency_core/Cargo.toml b/cadency_core/Cargo.toml index 26f3dd6..01d8374 100644 --- a/cadency_core/Cargo.toml +++ b/cadency_core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cadency_core" -version = "0.3.0" +version = "0.3.1" edition = "2021" description = "Library with the core logic for the cadency discord bot" license = "MIT" diff --git a/cadency_yt_playlist/Cargo.toml b/cadency_yt_playlist/Cargo.toml index 055aad7..330c1ae 100644 --- a/cadency_yt_playlist/Cargo.toml +++ b/cadency_yt_playlist/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cadency_yt_playlist" -version = "0.3.0" +version = "0.3.1" edition = "2021" description = "Library of the cadency discord bot to interact with youtube playlists" license = "MIT" diff --git a/examples/custom_commands/Cargo.toml b/examples/custom_commands/Cargo.toml index 67c91c4..1e326f7 100644 --- a/examples/custom_commands/Cargo.toml +++ b/examples/custom_commands/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "custom_commands" -version = "0.3.0" +version = "0.3.1" edition = "2021" description = "An example how to integrate custom commands into cadency_rs" license = "MIT" @@ -22,16 +22,16 @@ features = ["client", "gateway", "rustls_backend", "model", "voice", "cache"] [dependencies.cadency_core] path = "../../cadency_core" -version = "0.3.0" +version = "0.3.1" [dependencies.cadency_codegen] path = "../../cadency_codegen" -version = "0.3.0" +version = "0.3.1" [dependencies.cadency_commands] path = "../../cadency_commands" -version = "0.3.0" +version = "0.3.1" [dependencies.tokio] version = "1.25.0" -features = ["macros", "rt-multi-thread"] \ No newline at end of file +features = ["macros", "rt-multi-thread"]