From 55ef4f344269a7279a3de4659aef913809852df4 Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Fri, 8 Dec 2023 21:17:01 +0100 Subject: [PATCH 1/2] refactor(codegen): Generate getter fn for command args --- cadency_codegen/src/argument.rs | 87 +++++++- cadency_codegen/src/command.rs | 2 +- cadency_codegen/src/derive.rs | 339 +++++++++++++++-------------- cadency_commands/src/fib.rs | 26 +-- cadency_commands/src/play.rs | 42 ++-- cadency_commands/src/slap.rs | 24 +- cadency_commands/src/track_loop.rs | 33 +-- cadency_commands/src/urban.rs | 22 +- 8 files changed, 294 insertions(+), 281 deletions(-) diff --git a/cadency_codegen/src/argument.rs b/cadency_codegen/src/argument.rs index dc0b98b..6bccf4d 100644 --- a/cadency_codegen/src/argument.rs +++ b/cadency_codegen/src/argument.rs @@ -1,4 +1,7 @@ -pub struct Argument { +use proc_macro2::Ident; +use syn::spanned::Spanned; + +pub(crate) struct Argument { pub name: String, pub description: String, pub kind: String, @@ -15,7 +18,89 @@ impl Argument { } } + fn arg_name(&self) -> String { + self.name.trim().to_lowercase() + } + + fn kind_token(&self) -> proc_macro2::TokenStream { + self.kind.parse().unwrap() + } + + fn kind_ident(&self) -> Ident { + Ident::new(&self.kind, self.name.span()) + } + + fn rust_type(&self) -> proc_macro2::TokenStream { + match self.kind.as_str() { + "Boolean" => quote! { bool }, + "Integer" => quote! { i64 }, + "Number" => quote! { f64 }, + "String" => quote! { String }, + "SubCommand" | "SubCommandGroup" => { + quote! { Vec } + } + "Attachment" => quote! { serenity::model::id::AttachmentId }, + "Channel" => quote! { serenity::model::id::ChannelId }, + "Mentionable" => quote! { serenity::model::id::GenericId }, + "Role" => quote! { serenity::model::id::RoleId }, + "User" => quote! { serenity::model::id::UserId }, + "Unknown" => quote! { u8 }, + _ => panic!("Unknown argument kind: {}", self.kind), + } + } + pub fn is_optional(&mut self) { self.required = false; } + + pub fn to_cadency_command_option(&self) -> proc_macro2::TokenStream { + let name = self.arg_name(); + let description = &self.description; + let kind_token = self.kind_token(); + let required = self.required; + quote! { + __CadencyCommandOption { + name: #name, + description: #description, + kind: __CommandOptionType::#kind_token, + required: #required + } + } + } + + pub fn to_getter_fn(&self) -> proc_macro2::TokenStream { + let arg_kind_ident = self.kind_ident(); + let arg_rust_type = self.rust_type(); + + let arg_name = &self.name; + let fn_name_ident = Ident::new(&format!("arg_{arg_name}"), self.name.span()); + + let (fn_return_type, value_unwrap) = if self.required { + (quote! { #arg_rust_type }, quote! {.unwrap()}) + } else { + (quote! { Option<#arg_rust_type> }, quote! {}) + }; + + // Create a function to extract the argument from the command + quote! { + fn #fn_name_ident( + &self, + command: &serenity::model::application::CommandInteraction + ) -> #fn_return_type { + command + .data + .options + .iter() + .find(|option| option.name == #arg_name) + .map(|option| option.value.to_owned()) + .map(|value| { + match value { + serenity::model::application::CommandDataOptionValue::#arg_kind_ident(value) => value, + _ => unreachable!("Incorrect Type"), + } + }) + #value_unwrap + } + } + } } diff --git a/cadency_codegen/src/command.rs b/cadency_codegen/src/command.rs index 7a476a3..ab544a7 100644 --- a/cadency_codegen/src/command.rs +++ b/cadency_codegen/src/command.rs @@ -1,6 +1,6 @@ use crate::argument::Argument; -pub struct Command { +pub(crate) struct Command { pub name: String, pub description: String, pub deferred: bool, diff --git a/cadency_codegen/src/derive.rs b/cadency_codegen/src/derive.rs index 19a2292..c47c68c 100644 --- a/cadency_codegen/src/derive.rs +++ b/cadency_codegen/src/derive.rs @@ -1,192 +1,201 @@ use proc_macro::TokenStream; -use syn::{punctuated::Punctuated, spanned::Spanned, DeriveInput, Expr, Lit, Meta, Token}; +use syn::{ + punctuated::Punctuated, spanned::Spanned, Attribute, DeriveInput, Expr, Lit, Meta, MetaList, + MetaNameValue, Token, +}; use crate::{argument::Argument, command::Command}; -pub(crate) fn impl_command_baseline(derive_input: DeriveInput) -> TokenStream { - let struct_name = derive_input.ident; - let mut command = Command::new(struct_name.to_string().to_lowercase()); - for attr in derive_input.attrs.iter() { - match attr.meta.to_owned() { - Meta::NameValue(derive_attr) => { - match derive_attr.path.get_ident().unwrap().to_string().as_str() { - // #[name = "name"] - "name" => { - if let Expr::Lit(name_lit) = derive_attr.value { - if let Lit::Str(name_lit) = name_lit.lit { - command.name(name_lit.value()); - } else { - return syn::Error::new( - name_lit.lit.span(), - "'name' attribute must be a string", - ) - .to_compile_error() - .into(); +fn parse_command_args(command: &mut Command, derive_attr: MetaNameValue) -> Result<(), syn::Error> { + match derive_attr.path.get_ident().unwrap().to_string().as_str() { + // #[name = "name"] + "name" => { + if let Expr::Lit(name_lit) = derive_attr.value { + if let Lit::Str(name_lit) = name_lit.lit { + command.name(name_lit.value()); + } else { + return Err(syn::Error::new( + name_lit.lit.span(), + "'name' attribute must be a string", + )); + } + } + } + // #[description = "description"] + "description" => { + if let Expr::Lit(description_lit) = derive_attr.value { + if let Lit::Str(description_lit) = description_lit.lit { + command.description(description_lit.value()); + } else { + return Err(syn::Error::new( + description_lit.lit.span(), + "'description' attribute must be a string", + )); + } + } + } + // #[deferred = true] + "deferred" => { + if let Expr::Lit(deferred_lit) = derive_attr.value { + if let Lit::Bool(deferred_lit) = deferred_lit.lit { + if deferred_lit.value { + command.is_deferred(); + } + } else { + return Err(syn::Error::new( + deferred_lit.lit.span(), + "'deferred' attribute must be a bool", + )); + } + } + } + _ => (), + }; + Ok(()) +} + +fn parse_arguments( + command: &mut Command, + derive_attr_list: &MetaList, + attr: &Attribute, +) -> Result<(), syn::Error> { + // #[argument(..., ...)] + if derive_attr_list.path.is_ident("argument") { + let mut name: Option = None; + let mut description: Option = None; + let mut kind: Option = None; + let mut required = true; + + let nested = attr + .parse_args_with(Punctuated::::parse_terminated) + .unwrap(); + + for meta in nested { + match meta { + Meta::NameValue(name_value_arg) => { + match name_value_arg + .path + .get_ident() + .unwrap() + .to_string() + .as_str() + { + // #[argument(name = "name")] + "name" => { + if let Expr::Lit(argument_name_lit) = name_value_arg.value { + if let Lit::Str(argument_name_lit) = argument_name_lit.lit { + name = Some(argument_name_lit.value()); + } else { + return Err(syn::Error::new( + argument_name_lit.lit.span(), + "Name must be a string", + )); + } } } - } - // #[description = "description"] - "description" => { - if let Expr::Lit(description_lit) = derive_attr.value { - if let Lit::Str(description_lit) = description_lit.lit { - command.description(description_lit.value()); - } else { - return syn::Error::new( - description_lit.lit.span(), - "'description' attribute must be a string", - ) - .to_compile_error() - .into(); + // #[argument(description = "description")] + "description" => { + if let Expr::Lit(argument_description_lit) = name_value_arg.value { + if let Lit::Str(argument_description_lit) = + argument_description_lit.lit + { + description = Some(argument_description_lit.value()); + } else { + return Err(syn::Error::new( + argument_description_lit.lit.span(), + "Description must be a string", + )); + } } } - } - // #[deferred = true] - "deferred" => { - if let Expr::Lit(deferred_lit) = derive_attr.value { - if let Lit::Bool(deferred_lit) = deferred_lit.lit { - if deferred_lit.value { - command.is_deferred(); + // #[argument(kind = "kind")] + "kind" => { + if let Expr::Lit(argument_kind_lit) = name_value_arg.value { + if let Lit::Str(argument_kind_lit) = argument_kind_lit.lit { + kind = Some(argument_kind_lit.value()); + } else { + return Err(syn::Error::new( + argument_kind_lit.lit.span(), + "Kind must be a string", + )); } - } else { - return syn::Error::new( - deferred_lit.lit.span(), - "'deferred' attribute must be a bool", - ) - .to_compile_error() - .into(); } } - } - _ => (), - } - } - Meta::List(derive_attr_list) => { - // #[argument(..., ...)] - if derive_attr_list.path.is_ident("argument") { - let mut name: Option = None; - let mut description: Option = None; - let mut kind: Option = None; - let mut required = true; - - let nested = attr - .parse_args_with(Punctuated::::parse_terminated) - .unwrap(); - - for meta in nested { - match meta { - Meta::NameValue(name_value_arg) => { - match name_value_arg.path.get_ident().unwrap().to_string().as_str() { - // #[argument(name = "name")] - "name" => { - if let Expr::Lit(argument_name_lit) = name_value_arg.value { - if let Lit::Str(argument_name_lit) = argument_name_lit.lit { - name = Some(argument_name_lit.value()); - } else { - return syn::Error::new( - argument_name_lit.lit.span(), - "Name must be a string", - ) - .to_compile_error() - .into(); - } - } - } - // #[argument(description = "description")] - "description" => { - if let Expr::Lit(argument_description_lit) = name_value_arg.value { - if let Lit::Str(argument_description_lit) = argument_description_lit.lit { - description = Some(argument_description_lit.value()); - } else { - return syn::Error::new( - argument_description_lit.lit.span(), - "Description must be a string", - ) - .to_compile_error() - .into(); - } - } - } - // #[argument(kind = "kind")] - "kind" => { - if let Expr::Lit(argument_kind_lit) = name_value_arg.value { - if let Lit::Str(argument_kind_lit) = argument_kind_lit.lit { - kind = Some(argument_kind_lit.value()); - } else { - return syn::Error::new( - argument_kind_lit.lit.span(), - "Kind must be a string", - ) - .to_compile_error() - .into(); - } - } - } - // #[argument(required = true)] - "required" => { - if let Expr::Lit(argument_required_lit) = name_value_arg.value { - if let Lit::Bool(argument_required_lit) = argument_required_lit.lit { - required = argument_required_lit.value; - } else { - return syn::Error::new( - argument_required_lit.lit.span(), - "Required must be a bool", - ) - .to_compile_error() - .into(); - } - } - } - _ => { - return syn::Error::new(name_value_arg.path.get_ident().unwrap().span(), "Only 'name', 'description', 'kind' and 'required' are supported") - .to_compile_error() - .into() - } + // #[argument(required = true)] + "required" => { + if let Expr::Lit(argument_required_lit) = name_value_arg.value { + if let Lit::Bool(argument_required_lit) = argument_required_lit.lit + { + required = argument_required_lit.value; + } else { + return Err(syn::Error::new( + argument_required_lit.lit.span(), + "Required must be a bool", + )); } } - Meta::List(_) => {} - Meta::Path(_) => {} } - } - if let (Some(name), Some(description), Some(kind)) = (name, description, kind) { - let mut argument = Argument::new(name, description, kind); - if !required { - argument.is_optional(); + _ => { + return Err(syn::Error::new( + name_value_arg.path.get_ident().unwrap().span(), + "Only 'name', 'description', 'kind' and 'required' are supported", + )); } - command.add_argument(argument); - } else { - return syn::Error::new( - derive_attr_list.path.get_ident().span(), - "All arguments must have a name, description and kind", - ) - .to_compile_error() - .into(); } } + Meta::List(_) | Meta::Path(_) => {} } - _ => (), } - } - let argument_tokens = command.arguments.iter().map(|arg| { - let name = &arg.name; - let description = &arg.description; - let kind_token: proc_macro2::TokenStream = arg.kind.parse().unwrap(); - let required = arg.required; - quote! { - __CadencyCommandOption { - name: #name, - description: #description, - kind: __CommandOptionType::#kind_token, - required: #required + if let (Some(name), Some(description), Some(kind)) = (name, description, kind) { + let mut argument = Argument::new(name, description, kind); + if !required { + argument.is_optional(); } + command.add_argument(argument); + } else { + return Err(syn::Error::new( + derive_attr_list.path.get_ident().span(), + "All arguments must have a name, description and kind", + )); + } + } + Ok(()) +} + +pub(crate) fn impl_command_baseline(derive_input: DeriveInput) -> TokenStream { + let struct_name = derive_input.ident; + let mut command = Command::new(struct_name.to_string().to_lowercase()); + for attr in &derive_input.attrs { + if let Err(err) = match attr.meta.clone() { + Meta::NameValue(derive_attr) => parse_command_args(&mut command, derive_attr), + Meta::List(derive_attr_list) => parse_arguments(&mut command, &derive_attr_list, attr), + Meta::Path(_) => Ok(()), + } { + // If there are any parsing errors, throw them back to the compiler + return err.to_compile_error().into(); } - }); + } + let cadency_command_option_tokens: Vec = command + .arguments + .iter() + .map(Argument::to_cadency_command_option) + .collect(); + + let argument_functions: Vec = command + .arguments + .iter() + .map(Argument::to_getter_fn) + .collect(); let command_name = &command.name; let description = &command.description; let deferred = command.deferred; + + // Implement the CadencyCommandBaseline trait for the struct quote! { use cadency_core::{CadencyCommandBaseline as __CadencyCommandBaseline, CadencyCommandOption as __CadencyCommandOption}; use serenity::model::application::CommandOptionType as __CommandOptionType; + impl __CadencyCommandBaseline for #struct_name { fn name(&self) -> String { String::from(#command_name) @@ -201,9 +210,15 @@ pub(crate) fn impl_command_baseline(derive_input: DeriveInput) -> TokenStream { } fn options(&self) -> Vec<__CadencyCommandOption> { - vec![#(#argument_tokens),*] + vec![#(#cadency_command_option_tokens),*] } } + + impl #struct_name { + #(#argument_functions)* + } + + } .into() } diff --git a/cadency_commands/src/fib.rs b/cadency_commands/src/fib.rs index 751e10c..f4042ea 100644 --- a/cadency_commands/src/fib.rs +++ b/cadency_commands/src/fib.rs @@ -1,12 +1,8 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyError, -}; -use serenity::{ - async_trait, - client::Context, - model::application::{CommandDataOptionValue, CommandInteraction}, + CadencyCommand, CadencyError, }; +use serenity::{async_trait, client::Context, model::application::CommandInteraction}; #[derive(CommandBaseline, Default)] #[description = "Calculate the nth number in the fibonacci sequence"] @@ -35,23 +31,7 @@ impl CadencyCommand for Fib { command: &'a mut CommandInteraction, 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 { - Some(fib_value) - } else { - error!( - "{} command option not a integer: {:?}", - self.name(), - option_value - ); - None - } - }) - .ok_or(CadencyError::Command { - message: "Invalid number input".to_string(), - })?; - let fib_msg = Self::calc(number).to_string(); + let fib_msg = Self::calc(&self.arg_number(command)).to_string(); Ok(response_builder.message(Some(fib_msg)).build()?) } } diff --git a/cadency_commands/src/play.rs b/cadency_commands/src/play.rs index d2a02c8..857e71b 100644 --- a/cadency_commands/src/play.rs +++ b/cadency_commands/src/play.rs @@ -4,11 +4,7 @@ use cadency_core::{ utils, CadencyCommand, CadencyError, }; use reqwest::Url; -use serenity::{ - async_trait, - client::Context, - model::application::{CommandDataOptionValue, CommandInteraction}, -}; +use serenity::{async_trait, client::Context, model::application::CommandInteraction}; use songbird::events::Event; #[derive(CommandBaseline)] @@ -43,27 +39,19 @@ impl CadencyCommand for Play { command: &'a mut CommandInteraction, 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| { - 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 - } - }) - .ok_or(CadencyError::Command { - message: ":x: **No search string provided**".to_string(), - })?; + let (search_payload, is_url, is_playlist) = { + let query = self.arg_query(command); + let (is_valid_url, is_playlist): (bool, bool) = + Url::parse(&query).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) + }); + (query, is_valid_url, is_playlist) + }; + let (manager, call, guild_id) = utils::voice::join(ctx, command).await?; let response_builder = if is_playlist { @@ -133,7 +121,7 @@ impl CadencyCommand for Play { added_song_meta .source_url .as_ref() - .map_or("unknown url", |url| url) + .map_or("unknown url".to_string(), |url| url.to_owned()) }; response_builder.message(Some(format!( ":white_check_mark: **Added song to the queue and started playing:** \n:notes: `{}` \n:link: `{}`", diff --git a/cadency_commands/src/slap.rs b/cadency_commands/src/slap.rs index d8881c2..fe35702 100644 --- a/cadency_commands/src/slap.rs +++ b/cadency_commands/src/slap.rs @@ -1,12 +1,9 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyError, + CadencyCommand, CadencyError, }; use serenity::{ - all::Mentionable, - async_trait, - client::Context, - model::application::{CommandDataOptionValue, CommandInteraction}, + all::Mentionable, async_trait, client::Context, model::application::CommandInteraction, }; use std::num::NonZeroU64; @@ -27,25 +24,14 @@ impl CadencyCommand for Slap { command: &'a mut CommandInteraction, response_builder: &'a mut ResponseBuilder, ) -> Result { - let user_id = utils::get_option_value_at_position(command.data.options.as_ref(), 0) - .and_then(|option_value| { - if let CommandDataOptionValue::User(user_id) = option_value { - Some(user_id) - } else { - error!("Command option is not a user"); - None - } - }) - .ok_or(CadencyError::Command { - message: ":x: *Invalid user provided*".to_string(), - })?; + let user_id = self.arg_target(command); - let response_builder = if user_id == &command.user.id { + let response_builder = if user_id == command.user.id { response_builder.message(Some(format!( "**Why do you want to slap yourself, {}?**", command.user.mention() ))) - } else if NonZeroU64::from(*user_id) == NonZeroU64::from(command.application_id) { + } else if NonZeroU64::from(user_id) == NonZeroU64::from(command.application_id) { response_builder.message(Some(format!( "**Nope!\n{} slaps {} around a bit with a large trout!**", user_id.mention(), diff --git a/cadency_commands/src/track_loop.rs b/cadency_commands/src/track_loop.rs index 6cd85a1..b12c832 100644 --- a/cadency_commands/src/track_loop.rs +++ b/cadency_commands/src/track_loop.rs @@ -2,10 +2,7 @@ use cadency_core::{ response::{Response, ResponseBuilder}, utils, CadencyCommand, CadencyError, }; -use serenity::{ - all::CommandDataOptionValue, async_trait, client::Context, - model::application::CommandInteraction, -}; +use serenity::{async_trait, client::Context, model::application::CommandInteraction}; #[derive(Default, CommandBaseline)] #[name = "loop"] @@ -46,32 +43,8 @@ impl CadencyCommand for TrackLoop { })?; // Extract the loop amount and stop argument from the command - let loop_amount = command - .data - .options - .iter() - .find(|option| option.name == "amount") - .and_then(|option_amount| { - if let CommandDataOptionValue::Integer(amount) = option_amount.value { - Some(amount) - } else { - error!("Command option 'amount' is not a integer"); - None - } - }); - let stop_argument = command - .data - .options - .iter() - .find(|option| option.name == "stop") - .and_then(|option_stop| { - if let CommandDataOptionValue::Boolean(stop) = option_stop.value { - Some(stop) - } else { - error!("Command option 'stop' is not a boolean"); - None - } - }); + let loop_amount = self.arg_amount(command); + let stop_argument = self.arg_stop(command); // Cancel looping if the stop argument is true if let Some(stop) = stop_argument { diff --git a/cadency_commands/src/urban.rs b/cadency_commands/src/urban.rs index 177e426..21dcc35 100644 --- a/cadency_commands/src/urban.rs +++ b/cadency_commands/src/urban.rs @@ -1,15 +1,12 @@ use cadency_core::{ response::{Response, ResponseBuilder}, - utils, CadencyCommand, CadencyError, + CadencyCommand, CadencyError, }; use serenity::{ async_trait, builder::CreateEmbed, client::Context, - model::{ - application::{CommandDataOptionValue, CommandInteraction}, - Color, - }, + model::{application::CommandInteraction, Color}, }; #[derive(Deserialize, Debug)] @@ -84,19 +81,8 @@ impl CadencyCommand for Urban { command: &'a mut CommandInteraction, 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 { - Some(query) - } else { - error!("Urban command option empty"); - None - } - }) - .ok_or(CadencyError::Command { - message: ":x: *Empty or invalid query*".to_string(), - })?; - let urbans = Self::request_urban_dictionary_entries(query) + let query = self.arg_query(command); + let urbans = Self::request_urban_dictionary_entries(&query) .await .map_err(|err| { error!("Failed to request urban dictionary entries : {:?}", err); From 416d53a1b5fb91ae1ca8db9fdbdf1e78e9c17cf9 Mon Sep 17 00:00:00 2001 From: Jontze <42588836+jontze@users.noreply.github.com> Date: Fri, 8 Dec 2023 21:17:47 +0100 Subject: [PATCH 2/2] refactor: Fix some pendantic clippy issues --- cadency_core/src/client.rs | 1 + cadency_core/src/command.rs | 2 +- cadency_core/src/handler/command.rs | 2 +- cadency_yt_playlist/src/ytdlp.rs | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cadency_core/src/client.rs b/cadency_core/src/client.rs index c50b648..197aa33 100644 --- a/cadency_core/src/client.rs +++ b/cadency_core/src/client.rs @@ -16,6 +16,7 @@ pub struct Cadency { } impl Cadency { + #[must_use] pub fn builder() -> CadencyBuilder { CadencyBuilder::default() } diff --git a/cadency_core/src/command.rs b/cadency_core/src/command.rs index cdb4cb9..ebab784 100644 --- a/cadency_core/src/command.rs +++ b/cadency_core/src/command.rs @@ -80,7 +80,7 @@ impl TypeMapKey for Commands { pub(crate) async fn setup_commands(ctx: &Context) -> Result<(), serenity::Error> { let commands = utils::get_commands(ctx).await; // No need to run this in parallel as serenity will enforce one-by-one execution - for command in commands.iter() { + for command in &commands { command.register(ctx).await?; } Ok(()) diff --git a/cadency_core/src/handler/command.rs b/cadency_core/src/handler/command.rs index 7a18d8d..d936b15 100644 --- a/cadency_core/src/handler/command.rs +++ b/cadency_core/src/handler/command.rs @@ -22,7 +22,7 @@ impl EventHandler for Handler { info!("⏳ Started to submit commands, please wait..."); match setup_commands(&ctx).await { - Ok(_) => info!("✅ Application commands submitted"), + Ok(()) => info!("✅ Application commands submitted"), Err(err) => error!("❌ Failed to submit application commands: {:?}", err), }; } diff --git a/cadency_yt_playlist/src/ytdlp.rs b/cadency_yt_playlist/src/ytdlp.rs index 6f00fa0..c3701ea 100644 --- a/cadency_yt_playlist/src/ytdlp.rs +++ b/cadency_yt_playlist/src/ytdlp.rs @@ -33,7 +33,7 @@ impl YtDlp { self.command .args(self.args.clone()) .spawn() - .and_then(|child| child.wait_with_output()) + .and_then(std::process::Child::wait_with_output) } }