use std::sync::Arc; use log::{ debug, info, warn, }; use serenity::prelude::*; use songbird::{ input::YoutubeDl, Call, Songbird, }; use crate::{ bot::HttpKey, util, PoiseContext, CONFIG, }; pub async fn songbird(ctx: PoiseContext<'_>) -> anyhow::Result<(Arc, Arc>)> { let Some(gid) = ctx.guild_id() else { return Err(anyhow::anyhow!("no guild id").into()); }; let sb = songbird::get(ctx.serenity_context()).await.expect("acquiring songbird handle"); let call = sb.get_or_insert(gid); Ok((sb, call)) } pub async fn _play(ctx: PoiseContext<'_>, url: &url::Url) -> anyhow::Result<()> { use url::Host; debug!("playing '{}'", url); if !url.scheme().starts_with("http") { warn!("got bad url argument to play: {}", url); util::reply(ctx, "bAD LiNk").await?; return Ok(()); } 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 ctx.author().id == 106160362109272064 { util::reply(ctx, "fuck you conway").await?; } else { util::reply(ctx, "IMGUR IS BAD, YOU TRASH CAN MAN").await?; } return Ok(()); } let (_sb, call) = songbird(ctx).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.serenity_context().data.read().await; data.get::().unwrap().clone() }; let input = YoutubeDl::new_ytdl_like("yt-dlp", client.clone(), url.to_string()); call.enqueue_input(input.into()).await; Ok(()) } #[poise::command(slash_command, prefix_command, guild_only, category = "playback")] pub async fn play( ctx: PoiseContext<'_>, #[description = "link to play (if absent, resumes playback)"] u: Option, ) -> anyhow::Result<()> { let Some(u) = u else { return _resume(ctx).await; }; _play(ctx, &u).await } #[poise::command(slash_command, prefix_command, guild_only, category = "playback")] pub async fn pause(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; let call = call.lock().await; call.queue().pause()?; Ok(()) } #[poise::command( slash_command, prefix_command, guild_only, aliases("continue"), category = "playback" )] pub async fn resume(ctx: PoiseContext<'_>) -> anyhow::Result<()> { _resume(ctx).await } async fn _resume(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; let call = call.lock().await; call.queue().resume()?; Ok(()) } #[poise::command(slash_command, prefix_command, guild_only, category = "playback", aliases("next"))] pub async fn skip(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; let call = call.lock().await; call.queue().skip()?; Ok(()) } #[poise::command( slash_command, prefix_command, guild_only, category = "playback", aliases("sudoku", "fuckoff", "stop") )] pub async fn die(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; let mut call = call.lock().await; call.queue().stop(); call.leave().await?; Ok(()) } #[poise::command( slash_command, prefix_command, guild_only, category = "playback", aliases("queue") )] pub async fn list(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; let call = call.lock().await; let queue = call.queue(); util::reply(ctx, "(command fix work-in-progress)").await?; for track in queue.current_queue().into_iter() { let info = track.get_info().await?; let fmt = format!("track playing for {:?}", info.play_time); util::reply(ctx, fmt).await?; } Ok(()) }