diff --git a/src/bot/commands/audio/mod.rs b/src/bot/commands/audio/mod.rs index 1de1f6a..d91e16b 100644 --- a/src/bot/commands/audio/mod.rs +++ b/src/bot/commands/audio/mod.rs @@ -17,101 +17,68 @@ pub mod skip; pub mod stop; pub mod volume; +pub async fn get_songbird(ctx: &Context) -> Arc { + songbird::get(ctx) + .await + .expect("Songbird Voice client placed in at initialization") +} + /** * Finds a voice channel that the user is currently in, and attempts to join it. */ -pub async fn join_by_user( +pub async fn join_voice_channel( cache: &Arc, manager: &Arc, guild_id: &GuildId, user: &User, ) -> SirenResult { - let channel_id = match find_voice_channel(cache, guild_id, user) { - Ok(channel) => channel, - Err(err) => return Err(SirenError::new(500, err.to_string())) - }; - - join_voice_channel(manager, guild_id, &channel_id).await?; - Ok(channel_id) -} - -/** - * Joins a voice channel. - */ -async fn join_voice_channel( - manager: &Arc, - guild_id: &GuildId, - channel_id: &ChannelId, -) -> SirenResult<()> { + let channel_id = find_voice_channel(cache, guild_id, user)?; log::debug!("<{}> Joining channel {}", guild_id.get(), channel_id.get()); manager.join(guild_id.to_owned(), channel_id.to_owned()).await?; - Ok(()) + Ok(channel_id) } /** * Leaves a voice channel. */ pub async fn leave_voice_channel( - manager: Arc, - guild_id_option: &Option, -) -> Result<(), String> { - let guild_id = match guild_id_option { - Some(g) => g, - None => { - return Err(format!("{}", "No guild ID set")); - } - }; - - if manager.get(*guild_id).is_some() { + manager: &Arc, + guild_id: &GuildId, +) -> SirenResult<()> { + if manager.get(guild_id.to_owned()).is_some() { log::debug!("<{}> Disconnecting from channel", guild_id.get()); - if let Err(e) = manager.remove(*guild_id).await { - return Err(format!("{}", e)); - } + manager.remove(*guild_id).await?; } Ok(()) } -/** - * Finds a voice channel that the user is currently in. - */ -fn find_voice_channel( - cache: &Arc, - guild_id: &GuildId, - user: &User, -) -> Result { - let guild = match guild_id.to_guild_cached(cache) { - Some(g) => g, - None => return Err(format!("Guild not found")), - }; - - match guild - .voice_states - .get(&user.id) - .and_then(|voice_state| voice_state.channel_id) - { - Some(channel) => Ok(channel), - None => return Err(format!("User is not in a voice channel")), - } -} - pub async fn create_response( ctx: &Context, command: &CommandInteraction, content: String, -) -> Result<(), SerenityError> { - let data = CreateInteractionResponseMessage::new().content(content); +) { + let data = CreateInteractionResponseMessage::new().content(content.to_owned()); let builder = CreateInteractionResponse::Message(data); - command.create_response(&ctx.http, builder).await?; - Ok(()) + match command.create_response(&ctx.http, builder).await { + Ok(_) => {} + Err(err) => { + log::error!("Failed to create response for {content}\n{err}"); + } + }; } pub async fn edit_response( ctx: &Context, command: &CommandInteraction, content: String, -) -> Result { - let builder = EditInteractionResponse::new().content(content); - command.edit_response(&ctx.http, builder).await +) { + let builder = EditInteractionResponse::new().content(content.to_owned()); + match command.edit_response(&ctx.http, builder).await { + Ok(_) => {} + Err(err) => { + log::error!("Failed to create response for {content}\n{err}"); + } + } } /** @@ -129,8 +96,25 @@ fn is_valid_url(url: &str) -> (bool, bool) { }) } -pub async fn get_songbird(ctx: &Context) -> Arc { - songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialization") +/** + * Finds a voice channel that the user is currently in. + */ +fn find_voice_channel( + cache: &Arc, + guild_id: &GuildId, + user: &User, +) -> SirenResult { + let guild = match guild_id.to_guild_cached(cache) { + Some(g) => g, + None => return Err(SirenError::new(404, "Guild not found".to_string())), + }; + + match guild + .voice_states + .get(&user.id) + .and_then(|voice_state| voice_state.channel_id) + { + Some(channel) => Ok(channel), + None => return Err(SirenError::new(401, "User is not in a voice channel".to_string())), + } } diff --git a/src/bot/commands/audio/pause.rs b/src/bot/commands/audio/pause.rs index d646c83..1671041 100644 --- a/src/bot/commands/audio/pause.rs +++ b/src/bot/commands/audio/pause.rs @@ -1,38 +1,33 @@ -use log::{debug, error}; - use serenity::{all::{CommandInteraction, CreateCommand}, prelude::*}; use super::{get_songbird, create_response, edit_response}; pub async fn run(ctx: &Context, command: &CommandInteraction) { // Create the initial response - if let Err(why) = create_response(&ctx, &command, "Processing command...".to_string()).await { - error!("Failed to create response message: {}", why); - return; - } + create_response(&ctx, &command, format!(".....")).await; - let guild_id = match command.guild_id { - Some(g) => g, + // Get the songbird manager + let manager = get_songbird(ctx).await; + + // Extract the guild ID + let guild_id = match &command.guild_id { + Some(guild_id) => guild_id, None => { - if let Err(why) = - edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await - { - error!("Failed to edit response message: {}", why); - } + edit_response(&ctx, &command, "Unable to find the current server ID".to_string()).await; return; } }; - let manager = get_songbird(ctx).await; - if let Some(handler_lock) = manager.get(guild_id) { + + // Pause the track + if let Some(handler_lock) = manager.get(guild_id.to_owned()) { let handler = handler_lock.lock().await; - if let Err(err) = handler.queue().pause() { - if let Err(why) = edit_response(&ctx, &command, format!("Failed to pause: {}", err)).await { - error!("Failed to edit response message: {}", why); + match handler.queue().pause() { + Ok(_) => { + log::debug!("Paused the track"); + edit_response(&ctx, &command, format!("Pausing the track")).await; } - } else { - debug!("Paused the track"); - if let Err(why) = edit_response(&ctx, &command, format!("Pausing the track")).await { - error!("Failed to edit response message: {}", why); + Err(err) => { + edit_response(&ctx, &command, format!("Failed to pause: {}", err)).await; } } } diff --git a/src/bot/commands/audio/play.rs b/src/bot/commands/audio/play.rs index c900985..b1b907f 100644 --- a/src/bot/commands/audio/play.rs +++ b/src/bot/commands/audio/play.rs @@ -5,63 +5,45 @@ use serenity::model::prelude::GuildId; use serenity::{prelude::*, async_trait}; use songbird::input::{AuxMetadata, Input, YoutubeDl}; use songbird::tracks::TrackHandle; -use songbird::{Call, EventHandler, Songbird}; +use songbird::{Call, Event, EventHandler, Songbird, TrackEvent}; use crate::bot::guilds::GuildCache; use crate::bot::ytdlp::{PlaylistItem, YtDlp}; -use crate::bot::commands::audio::{create_response, edit_response, leave_voice_channel}; use crate::error::{SirenResult, Error as SirenError}; use crate::HttpKey; -use super::{get_songbird, is_valid_url, join_by_user}; +use super::{create_response, edit_response, get_songbird, is_valid_url, join_voice_channel, leave_voice_channel}; pub async fn run(ctx: &Context, command: &CommandInteraction) { - // Get the track url - let track_url = match command.data.options.get(0) { - Some(o) => match &o.value.as_str() { - Some(s) => s.to_owned(), - None => { - log::warn!("Missing track option"); - if let Err(why) = - create_response(&ctx, &command, format!("Track option is missing")).await - { - log::error!("Failed to create response message: {}", why); - } - return; - } - }, + // Process the command options + let track_url = match command.data.options.first() { + Some(o) => &o.value.as_str().unwrap(), None => { - log::warn!("Missing track option"); - if let Err(why) = create_response(&ctx, &command, format!("Track option is missing")).await { - log::error!("Failed to create response message: {}", why); - } + log::warn!("{} attempted to play a track without a track option", command.user.id.get()); + create_response(&ctx, &command, format!("Track option is missing")).await; return; } }; // Create the initial response - if let Err(why) = create_response(&ctx, &command, format!("Processing command...")).await { - log::error!("Failed to create response message: {}", why); - return; - } + create_response(&ctx, &command, format!(".....")).await; + // Get the songbird manager let manager = get_songbird(ctx).await; + // Extract the guild ID let guild_id = match &command.guild_id { Some(guild_id) => guild_id, None => { - if let Err(why) = - edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await - { - log::error!("Failed to edit response message: {}", why); - } + edit_response(&ctx, &command, "Unable to find the current server ID".to_string()).await; return; } }; + // Join the user's voice channel - match join_by_user(&ctx.cache, &manager, guild_id, &command.user).await { - Ok(_) => { - log::debug!("Play command executed with track: {:?}", track_url); + match join_voice_channel(&ctx.cache, &manager, guild_id, &command.user).await { + Ok(channel_id) => { + log::debug!("<{guild_id}> Play command executed on {channel_id} with track: {track_url:?}"); // Handle the track url match play_track(ctx, manager, guild_id.to_owned(), track_url).await { Ok(count) => { @@ -71,25 +53,17 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) { } else if count == 1 { message = "Playing 1 track".to_string(); } - if let Err(why) = edit_response(&ctx, &command, message).await { - log::error!("Failed to edit response message: {}", why); - } + edit_response(&ctx, &command, message).await; } Err(err) => { log::warn!("Failed to play track: {}", err); - if let Err(why) = - edit_response(&ctx, &command, format!("Failed to play track: {}", err)).await - { - log::error!("Failed to edit response message: {}", why); - } + edit_response(&ctx, &command, format!("Failed to play track: {}", err)).await; } }; } Err(err) => { log::warn!("{}", err); - if let Err(why) = edit_response(&ctx, &command, format!("{}", err)).await { - log::error!("Failed to edit response message: {}", why); - } + edit_response(&ctx, &command, format!("{}", err)).await; } } } @@ -140,17 +114,17 @@ pub async fn play_track( handler_lock.clone(), &item.url, is_queue_empty, - Some(guild.volume as f32 / 100.0), + guild.volume as f32 / 100.0, ) .await { - Ok(added_song) => { - let track_title = added_song.title.unwrap(); + Ok(metadata) => { + let track_title = metadata.title.unwrap(); log::debug!("Added track: {}", track_title); let mut handler = handler_lock.lock().await; - handler.remove_all_global_events(); + // handler.remove_all_global_events(); handler.add_global_event( - songbird::Event::Track(songbird::TrackEvent::End), + Event::Track(TrackEvent::End), TrackEndNotifier { guild_id, call: manager.clone(), @@ -160,7 +134,7 @@ pub async fn play_track( } Err(err) => { log::warn!("Failed to add song: {}", err); - if let Err(why) = leave_voice_channel(manager, &Some(guild_id)).await { + if let Err(why) = leave_voice_channel(&manager, &guild_id).await { log::error!("Failed to leave voice channel: {}", why); } return Err(SirenError::new(422, err.to_string())); @@ -176,9 +150,8 @@ async fn add_song( call: Arc>, url: &str, lazy: bool, - volume: Option, + volume: f32, ) -> SirenResult { - // let source = Restartable::ytdl(url.to_owned(), lazy).await?; let http_client = { let data = ctx.data.read().await; data.get::() @@ -187,17 +160,16 @@ async fn add_song( }; let source = YoutubeDl::new(http_client, url.to_owned()); let mut handler = call.lock().await; - let mut track: Input = source.into(); - let metadata = track.aux_metadata().await.unwrap(); + let mut input: Input = source.into(); + let metadata = input.aux_metadata().await.unwrap(); let track_handle: TrackHandle; if lazy { - track_handle = handler.play_input(track); + track_handle = handler.play_input(input); } else { - track_handle = handler.enqueue_input(track).await; - } - if let Some(volume) = volume { - let _ = track_handle.set_volume(volume); + track_handle = handler.enqueue_input(input).await; } + // Set the volume + let _ = track_handle.set_volume(volume); Ok(metadata) } @@ -236,8 +208,8 @@ pub fn register() -> CreateCommand { } struct TrackEndNotifier { - pub call: std::sync::Arc, - pub guild_id: serenity::model::id::GuildId, + pub call: Arc, + pub guild_id: GuildId, } #[async_trait] diff --git a/src/bot/commands/audio/resume.rs b/src/bot/commands/audio/resume.rs index 722f2bc..a357db8 100644 --- a/src/bot/commands/audio/resume.rs +++ b/src/bot/commands/audio/resume.rs @@ -1,39 +1,34 @@ -use log::{debug, error}; - use serenity::{all::{CommandInteraction, CreateCommand}, prelude::*}; use super::{get_songbird, create_response, edit_response}; pub async fn run(ctx: &Context, command: &CommandInteraction) { // Create the initial response - if let Err(why) = create_response(&ctx, &command, "Processing command...".to_string()).await { - error!("Failed to create response message: {}", why); - return; - } + create_response(&ctx, &command, format!(".....")).await; - let guild_id = match command.guild_id { - Some(g) => g, + // Get the songbird manager + let manager = get_songbird(ctx).await; + + // Extract the guild ID + let guild_id = match &command.guild_id { + Some(guild_id) => guild_id, None => { - if let Err(why) = - edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await - { - error!("Failed to edit response message: {}", why); - } + edit_response(&ctx, &command, "Unable to find the current server ID".to_string()).await; return; } }; - let manager = get_songbird(ctx).await; - if let Some(handler_lock) = manager.get(guild_id) { + + // Resume the track + if let Some(handler_lock) = manager.get(guild_id.to_owned()) { let handler = handler_lock.lock().await; - if let Err(err) = handler.queue().resume() { - if let Err(why) = edit_response(&ctx, &command, format!("Failed to resume: {}", err)).await { - error!("Failed to edit response message: {}", why); - } - } else { - debug!("Resumed the track"); - if let Err(why) = edit_response(&ctx, &command, format!("Resuming the track")).await { - error!("Failed to edit response message: {}", why); - } + match handler.queue().resume() { + Ok(_) => { + log::debug!("Resumed the track"); + edit_response(&ctx, &command, format!("Resuming the track")).await; + }, + Err(err) => { + edit_response(&ctx, &command, format!("Failed to resume: {}", err)).await; + } } } } diff --git a/src/bot/commands/audio/skip.rs b/src/bot/commands/audio/skip.rs index ef5ff8c..9d38193 100644 --- a/src/bot/commands/audio/skip.rs +++ b/src/bot/commands/audio/skip.rs @@ -1,39 +1,34 @@ -use log::{debug, error}; - use serenity::{all::{CommandInteraction, CreateCommand}, prelude::*}; use super::{get_songbird, create_response, edit_response}; pub async fn run(ctx: &Context, command: &CommandInteraction) { // Create the initial response - if let Err(why) = create_response(&ctx, &command, "Processing command...".to_string()).await { - error!("Failed to create response message: {}", why); - return; - } + create_response(&ctx, &command, format!(".....")).await; - let guild_id = match command.guild_id { - Some(g) => g, + // Get the songbird manager + let manager = get_songbird(ctx).await; + + // Extract the guild ID + let guild_id = match &command.guild_id { + Some(guild_id) => guild_id, None => { - if let Err(why) = - edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await - { - error!("Failed to edit response message: {}", why); - } + edit_response(&ctx, &command, "Unable to find the current server ID".to_string()).await; return; } }; - let manager = get_songbird(ctx).await; - if let Some(handler_lock) = manager.get(guild_id) { + + // Skip the track + if let Some(handler_lock) = manager.get(guild_id.to_owned()) { let handler = handler_lock.lock().await; - if let Err(err) = handler.queue().skip() { - if let Err(why) = edit_response(&ctx, &command, format!("Failed to skip: {}", err)).await { - error!("Failed to edit response message: {}", why); - } - } else { - debug!("Skipped the track"); - if let Err(why) = edit_response(&ctx, &command, format!("Skipping the track")).await { - error!("Failed to edit response message: {}", why); - } + match handler.queue().skip() { + Ok(_) => { + log::debug!("Skipped the track"); + edit_response(&ctx, &command, format!("Skipping the track")).await; + }, + Err(err) => { + edit_response(&ctx, &command, format!("Failed to skip: {}", err)).await; + } } } } diff --git a/src/bot/commands/audio/stop.rs b/src/bot/commands/audio/stop.rs index 1bee71c..6c98350 100644 --- a/src/bot/commands/audio/stop.rs +++ b/src/bot/commands/audio/stop.rs @@ -1,35 +1,29 @@ -use log::{debug, error}; - use serenity::{all::{CommandInteraction, CreateCommand}, prelude::*}; use super::{get_songbird, create_response, edit_response}; pub async fn run(ctx: &Context, command: &CommandInteraction) { // Create the initial response - if let Err(why) = create_response(&ctx, &command, "Processing command...".to_string()).await { - error!("Failed to create response message: {}", why); - return; - } + create_response(&ctx, &command, format!(".....")).await; + // Get the songbird manager + let manager = get_songbird(ctx).await; + + // Extract the guild ID let guild_id = match command.guild_id { Some(g) => g, None => { - if let Err(why) = - edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await - { - error!("Failed to edit response message: {}", why); - } + edit_response(&ctx, &command, "Unable to find the current server ID".to_string()).await; return; } }; - let manager = get_songbird(ctx).await; + + // Stop the track and clear the queue if let Some(handler_lock) = manager.get(guild_id) { let handler = handler_lock.lock().await; handler.queue().stop(); - debug!("Stopped the track"); - if let Err(why) = edit_response(&ctx, &command, format!("Stopping the tracks")).await { - error!("Failed to edit response message: {}", why); - } + log::debug!("Stopped the track"); + edit_response(&ctx, &command, format!("Stopping the tracks")).await; } } diff --git a/src/bot/commands/audio/volume.rs b/src/bot/commands/audio/volume.rs index d31563d..5a01f96 100644 --- a/src/bot/commands/audio/volume.rs +++ b/src/bot/commands/audio/volume.rs @@ -1,7 +1,5 @@ use std::sync::Arc; -use log::{error, warn}; - use serenity::{all::{CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}, model::prelude::GuildId, prelude::*}; use songbird::Songbird; @@ -10,66 +8,53 @@ use crate::bot::guilds::GuildCache; use super::{get_songbird, create_response, edit_response}; pub async fn run(ctx: &Context, command: &CommandInteraction) { - // Get the volume - let volume = match command.data.options.get(0) { - Some(o) => match o.value.as_i64() { - Some(p) => p as i32, - None => { - warn!("Unable to get volume option as a string"); - if let Err(why) = - create_response(&ctx, &command, format!("Volume option is missing")).await - { - error!("Failed to create response message: {}", why); - } - return; - } - }, + // Process the command options + let volume = match command.data.options.first() { + Some(o) => o.value.as_i64().unwrap() as i32, None => { - warn!("Missing volume option"); - if let Err(why) = create_response(&ctx, &command, format!("Volume option is missing")).await { - error!("Failed to create response message: {}", why); - } + log::warn!("Unable to get volume option as a string"); + create_response(&ctx, &command, format!("Volume option is missing")).await; return; } }; // Create the initial response - if let Err(why) = create_response(&ctx, &command, "Processing command...".to_string()).await { - error!("Failed to create response message: {}", why); - return; - } + create_response(&ctx, &command, ".....".to_string()).await; - let guild_id = match command.guild_id { - Some(g) => g, + // Get the songbird manager + let manager = get_songbird(ctx).await; + + // Extract the guild ID + let guild_id = match &command.guild_id { + Some(guild_id) => guild_id, None => { - if let Err(why) = - edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await - { - error!("Failed to edit response message: {}", why); - } + edit_response(&ctx, &command, "Unable to find the current server ID".to_string()).await; return; } }; - let manager = get_songbird(ctx).await; - set_volume(manager, guild_id, volume).await; - if let Err(why) = edit_response(&ctx, &command, format!("Setting the volume to {}", volume)).await - { - error!("Failed to set the volume: {}", why); - } + + // Set the volume + set_volume(&manager, guild_id, volume).await; + edit_response(&ctx, &command, format!("Setting the volume to {}", volume)).await; } -pub async fn set_volume(manager: Arc, guild_id: GuildId, volume: i32) { +pub async fn set_volume(manager: &Arc, guild_id: &GuildId, volume: i32) { // Format volume to f32 bound between 0.0 and 1.0 let volume = std::cmp::min(100, std::cmp::max(0, volume)); let bound_volume = volume as f32 / 100.0; + + // Update the guild cache let mut guild_cache = GuildCache::get_by_id(guild_id.get() as i64).await.unwrap().unwrap(); guild_cache.volume = volume; guild_cache.update().await.unwrap(); - if let Some(handler_lock) = manager.get(guild_id) { + // Update the volume of the songbird handler + if let Some(handler_lock) = manager.get(guild_id.to_owned()) { let handler = handler_lock.lock().await; for (_, track_handle) in handler.queue().current_queue().iter().enumerate() { - let _ = track_handle.set_volume(bound_volume); + if let Err(err) = track_handle.set_volume(bound_volume) { + log::error!("Unable to set volume: {err}"); + } } } } diff --git a/src/bot/commands/roll.rs b/src/bot/commands/roll.rs index d5f10dd..44126d4 100644 --- a/src/bot/commands/roll.rs +++ b/src/bot/commands/roll.rs @@ -1,4 +1,3 @@ -use log::{error, warn}; use rand::Rng; use serenity::all::{CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption}; @@ -7,28 +6,21 @@ use crate::bot::commands::audio::edit_response; use super::audio::create_response; pub async fn run(ctx: &Context, command: &CommandInteraction) { - if let Err(why) = create_response(&ctx, &command, format!("Processing command...")).await { - error!("Failed to create response message: {}", why); - return; - } + create_response(&ctx, &command, format!("Processing command...")).await; let dice_string = match command.data.options.get(0) { Some(o) => { match o.value.as_str() { Some(s) => s.split_whitespace().collect::(), None => { - warn!("Missing dice option"); - if let Err(why) = edit_response(&ctx, &command, format!("Dice option is missing")).await { - error!("Failed to create response message: {}", why); - } + log::warn!("Missing dice option"); + edit_response(&ctx, &command, format!("Dice option is missing")).await; return; } } }, None => { - warn!("Missing dice option"); - if let Err(why) = edit_response(&ctx, &command, format!("Dice option is missing")).await { - error!("Failed to create response message: {}", why); - } + log::warn!("Missing dice option"); + edit_response(&ctx, &command, format!("Dice option is missing")).await; return; } }; @@ -55,15 +47,10 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) { }, total + (modifier as u32) ); - if let Err(why) = edit_response(&ctx, &command, response).await { - error!("Failed to create response message: {}", why); - } + edit_response(&ctx, &command, response).await; } Err(why) => { - if let Err(why) = edit_response(&ctx, &command, format!("Invalid dice string: {}", why)).await - { - error!("Failed to create response message: {}", why); - } + edit_response(&ctx, &command, format!("Invalid dice string: {}", why)).await; } } } diff --git a/src/bot/handler.rs b/src/bot/handler.rs index ff5650f..1c4c9f8 100644 --- a/src/bot/handler.rs +++ b/src/bot/handler.rs @@ -66,10 +66,7 @@ impl EventHandler for Handler { "ping" => commands::ping::run(&command.data.options), _ => "Unknown command".to_string(), }; - - if let Err(why) = create_response(&ctx, &command, content).await { - warn!("Cannot respond to slash command: {}", why); - } + create_response(&ctx, &command, content).await; } } }