use anyhow::{Context, Result}; use poise::{ serenity_prelude::{CreateEmbed, EmbedMessageBuilding, MessageBuilder}, CreateReply, }; use songbird::input::{Input, YoutubeDl}; use tracing::{debug, log::warn}; use crate::{personality, CommandContext, Error}; #[poise::command(slash_command)] pub async fn join(ctx: CommandContext<'_>) -> Result<(), Error> { let Some((guild_id, Some(channel_id))) = ctx.guild().and_then(|g| { g.voice_states .get(&ctx.author().id) .map(|vs| (g.id, vs.channel_id)) }) else { ctx.say("You're not in a voice channel, silly.").await?; return Ok(()); }; let manager = songbird::get(ctx.serenity_context()) .await .context("Expected a songbird manager")? .clone(); let _handler = manager.join(guild_id, channel_id).await; ctx.say("Joining your channel!").await?; Ok(()) } #[poise::command(slash_command)] pub async fn leave(ctx: CommandContext<'_>) -> Result<(), Error> { let Some(guild_id) = ctx.guild().map(|g| g.id) else { ctx.say("You're not in a server, silly.").await?; return Ok(()); }; let manager = songbird::get(ctx.serenity_context()) .await .context("Expected a songbird manager")? .clone(); if manager.get(guild_id).is_none() { ctx.say("I'm not even in a voice channel!").await?; return Ok(()); } let _handler = manager.remove(guild_id).await; ctx.say("Okay bye!").await?; Ok(()) } #[poise::command(slash_command)] pub async fn play( ctx: CommandContext<'_>, #[description = "The URL of the song to play"] url: Option, ) -> Result<(), Error> { let Some(url) = url else { ctx.say("You need to give me a URL to play!").await?; return Ok(()); }; let Some(guild_id) = ctx.guild().map(|g| g.id) else { ctx.say("You're not in a server, silly.").await?; return Ok(()); }; let manager = songbird::get(ctx.serenity_context()) .await .context("Expected a songbird manager")? .clone(); if manager.get(guild_id).is_none() { let Some((guild_id, Some(channel_id))) = ctx.guild().and_then(|g| { g.voice_states .get(&ctx.author().id) .map(|vs| (g.id, vs.channel_id)) }) else { ctx.say("You're not in a voice channel, silly.").await?; return Ok(()); }; let _handler = manager.join(guild_id, channel_id).await; } if let Some(handler_lock) = manager.get(guild_id) { let reply = poise::CreateReply::default() .content(personality::get_random_loading_message()) .embed( CreateEmbed::new() .image("https://media.giphy.com/media/H1dxi6xdh4NGQCZSvz/giphy.gif"), ); let response = ctx.send(reply).await?; let mut handler = handler_lock.lock().await; debug!("Trying to play: {}", url); let mut source: Input = YoutubeDl::new(ctx.data().http_client.clone(), url.clone()).into(); let metadata = source.aux_metadata().await?; debug!("Playing: {:?}", metadata); let title = metadata.title.clone().unwrap_or(String::from("This video")); let mut msg = MessageBuilder::new(); // Optional sassy commentary! match personality::get_sassy_commentary(&title).await { Ok(commentary) => { msg.push_line(&commentary).push_line(""); } Err(e) => { warn!("Failed to get sassy commentary for \"{title}\": {e}"); } }; msg.push_bold("Now playing: ").push_named_link(title, url); response .edit(ctx, CreateReply::default().content(msg.build())) .await?; let mut queue = ctx.data().queue.lock(); if !queue.is_empty() { let _ = queue.stop(); } queue.add_next(source, &mut handler)?; queue.resume()?; } else { ctx.say("Neither of us are in a voice channel, silly.") .await?; } Ok(()) } #[poise::command(slash_command)] pub async fn queue( ctx: CommandContext<'_>, #[description = "The URL of the song to play"] url: Option, ) -> Result<(), Error> { let Some(url) = url else { ctx.say("You need to give me a URL to play!").await?; return Ok(()); }; let Some(guild_id) = ctx.guild().map(|g| g.id) else { ctx.say("You're not in a server, silly.").await?; return Ok(()); }; let manager = songbird::get(ctx.serenity_context()) .await .context("Expected a songbird manager")? .clone(); if manager.get(guild_id).is_none() { let Some((guild_id, Some(channel_id))) = ctx.guild().and_then(|g| { g.voice_states .get(&ctx.author().id) .map(|vs| (g.id, vs.channel_id)) }) else { ctx.say("You're not in a voice channel, silly.").await?; return Ok(()); }; let _handler = manager.join(guild_id, channel_id).await; } if let Some(handler_lock) = manager.get(guild_id) { let reply = poise::CreateReply::default() .content(personality::get_random_loading_message()) .embed( CreateEmbed::new() .image("https://media.giphy.com/media/H1dxi6xdh4NGQCZSvz/giphy.gif"), ); let response = ctx.send(reply).await?; let mut handler = handler_lock.lock().await; debug!("Trying to play: {}", url); let mut source: Input = YoutubeDl::new(ctx.data().http_client.clone(), url.clone()).into(); let metadata = source.aux_metadata().await?; debug!("Playing: {:?}", metadata); let title = metadata.title.clone().unwrap_or(String::from("This video")); let mut msg = MessageBuilder::new(); // Optional sassy commentary! match personality::get_sassy_commentary(&title).await { Ok(commentary) => { msg.push_line(&commentary).push_line(""); } Err(e) => { warn!("Failed to get sassy commentary for \"{title}\": {e}"); } }; msg.push_bold("Queued: ").push_named_link(title, url); response .edit(ctx, CreateReply::default().content(msg.build())) .await?; let mut queue = ctx.data().queue.lock(); queue.add_to_end(source, &mut handler)?; } else { ctx.say("Neither of us are in a voice channel, silly.") .await?; } Ok(()) } #[poise::command(slash_command)] pub async fn stop(ctx: CommandContext<'_>) -> Result<(), Error> { let Some(guild_id) = ctx.guild().map(|g| g.id) else { ctx.say("You're not in a server, silly.").await?; return Ok(()); }; let manager = songbird::get(ctx.serenity_context()) .await .context("Expected a songbird manager")? .clone(); if manager.get(guild_id).is_some() { { let mut queue = ctx.data().queue.lock(); queue.stop()?; } ctx.say("Alright, I guess I'll stop.").await?; } else { ctx.say("I'm not even in a channel to begin with. Silly.") .await?; } Ok(()) } #[poise::command(slash_command)] pub async fn skip(ctx: CommandContext<'_>) -> Result<(), Error> { let Some(guild_id) = ctx.guild().map(|g| g.id) else { ctx.say("You're not in a server, silly.").await?; return Ok(()); }; let manager = songbird::get(ctx.serenity_context()) .await .context("Expected a songbird manager")? .clone(); if manager.get(guild_id).is_some() { { let mut queue = ctx.data().queue.lock(); let _ = queue.stop(); queue.resume()?; } ctx.say("Skipping!").await?; } else { ctx.say("I'm not even in a channel to begin with. Silly.") .await?; } Ok(()) } #[poise::command(slash_command)] pub async fn pause(ctx: CommandContext<'_>) -> Result<(), Error> { let Some(guild_id) = ctx.guild().map(|g| g.id) else { ctx.say("You're not in a server, silly.").await?; return Ok(()); }; let manager = songbird::get(ctx.serenity_context()) .await .context("Expected a songbird manager")? .clone(); if manager.get(guild_id).is_some() { { let mut queue = ctx.data().queue.lock(); queue.pause()?; } ctx.say("Pausing!").await?; } else { ctx.say("I'm not even in a channel to begin with. Silly.") .await?; } Ok(()) } #[poise::command(slash_command)] pub async fn resume(ctx: CommandContext<'_>) -> Result<(), Error> { let Some(guild_id) = ctx.guild().map(|g| g.id) else { ctx.say("You're not in a server, silly.").await?; return Ok(()); }; let manager = songbird::get(ctx.serenity_context()) .await .context("Expected a songbird manager")? .clone(); if manager.get(guild_id).is_some() { { let mut queue = ctx.data().queue.lock(); queue.resume()?; } ctx.say("Resuming!").await?; } else { ctx.say("I'm not even in a channel to begin with. Silly.") .await?; } Ok(()) } #[poise::command(slash_command)] pub async fn clear(ctx: CommandContext<'_>) -> Result<(), Error> { let Some(guild_id) = ctx.guild().map(|g| g.id) else { ctx.say("You're not in a server, silly.").await?; return Ok(()); }; let manager = songbird::get(ctx.serenity_context()) .await .context("Expected a songbird manager")? .clone(); if manager.get(guild_id).is_some() { { let mut queue = ctx.data().queue.lock(); queue.clear(); } ctx.say("Cleared the queue!").await?; } else { ctx.say("I'm not even in a channel to begin with. Silly.") .await?; } Ok(()) }