use log::{ debug, error, info, warn, }; use serenity::{ framework::standard::{ macros::{ command, group, }, Args, CommandError, CommandResult, }, model::channel::Message, prelude::*, }; use songbird::{ input::YoutubeDl, Call, Songbird, }; use std::sync::Arc; use tap::Conv; use crate::{ bot::HttpKey, commands::sound_levels::*, util, CONFIG, }; #[group] #[commands(skip, pause, resume, list, die, mute, unmute, play)] #[only_in(guild)] struct Playback; pub async fn songbird( ctx: &Context, msg: &Message, ) -> Result<(Arc, Arc>), CommandError> { let Some(gid) = msg.guild_id else { return Err(anyhow::anyhow!("no guild id").into()); }; let sb = songbird::get(ctx).await.expect("acquiring songbird handle"); let call = sb.get_or_insert(gid); Ok((sb, call)) } pub async fn _play(ctx: &Context, msg: &Message, url: &str) -> CommandResult { use url::{ Host, Url, }; debug!("playing '{}'", url); if !url.starts_with("http") { warn!("got bad url argument to play: {}", url); util::send(ctx, msg.channel_id, "bAD LiNk", msg.tts).await?; return Ok(()); } let url = match Url::parse(url) { Err(e) => { error!("bad url: {}", e); util::send(ctx, msg.channel_id, "INVALID URL", msg.tts).await?; return Ok(()); }, Ok(u) => u, }; let host = url.host().and_then(|u| match u { Host::Domain(h) => Some(h.to_owned()), _ => None, }); if host.map(|h| h.to_lowercase().contains("imgur")).unwrap_or(false) { info!("detected imgur link"); if msg.author.id.get() == 106160362109272064 { util::send(ctx, msg.channel_id, "fuck you conway", true).await?; } else { util::send(ctx, msg.channel_id, "IMGUR IS BAD, YOU TRASH CAN MAN", msg.tts).await?; } return Ok(()); } let (_sb, call) = songbird(ctx, msg).await?; let mut call = call.lock().await; if call.current_channel().is_none() { call.join(CONFIG.discord.voice_channel()).await?; } let client = { let data = ctx.data.read().await; data.get::().unwrap().clone() }; let input = YoutubeDl::new_ytdl_like("yt-dlp", client.clone(), url.conv::()); call.enqueue_input(input.into()).await; Ok(()) } #[command] pub async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { if args.len() == 0 { return _resume(ctx, msg).await; } let url = match args.single::() { Ok(url) => url, Err(e) => { error!("unable to parse url from args: {}", e); return util::send(ctx, msg.channel_id, "BAD LINK", msg.tts) .await .map_err(CommandError::from); }, }; _play(ctx, msg, &url).await } #[command] pub async fn pause(ctx: &Context, msg: &Message, _: Args) -> CommandResult { let (_sb, call) = songbird(ctx, msg).await?; let call = call.lock().await; call.queue().pause()?; Ok(()) } #[command] #[aliases("continue")] pub async fn resume(ctx: &Context, msg: &Message, _: Args) -> CommandResult { _resume(ctx, msg).await } async fn _resume(ctx: &Context, msg: &Message) -> CommandResult { let (_sb, call) = songbird(ctx, msg).await?; let call = call.lock().await; call.queue().resume()?; Ok(()) } #[command] #[aliases("next")] pub async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let (_sb, call) = songbird(ctx, msg).await?; let call = call.lock().await; call.queue().skip()?; Ok(()) } #[command] #[aliases("sudoku", "fuckoff", "stop")] pub async fn die(ctx: &Context, msg: &Message, _: Args) -> CommandResult { let (_sb, call) = songbird(ctx, msg).await?; let call = call.lock().await; call.queue().stop(); Ok(()) } #[command] #[aliases("queue")] pub async fn list(ctx: &Context, msg: &Message, _: Args) -> CommandResult { let (_sb, call) = songbird(ctx, msg).await?; let call = call.lock().await; let queue = call.queue(); util::send(ctx, msg.channel_id, "(command fix work-in-progress)", msg.tts).await?; for track in queue.current_queue().into_iter() { let info = track.get_info().await?; util::send(ctx, msg.channel_id, format!("track playing for {:?}", info.play_time), msg.tts) .await?; } Ok(()) }