From d18e79696a0dc80de2aa56e005c991eaacdb63f4 Mon Sep 17 00:00:00 2001 From: Ladislav Hano Date: Thu, 9 Jan 2025 18:57:42 +0100 Subject: [PATCH] feat: implemented general player using yt-dlp --- Cargo.toml | 2 +- src/commands/moover/say.rs | 4 +- src/commands/voice/general/general_player.rs | 63 +++++++++++++++++++ src/commands/voice/general/mod.rs | 3 + src/commands/voice/mod.rs | 4 +- .../voice/{player.rs => player_common.rs} | 19 ++---- src/commands/voice/spotify/mod.rs | 4 +- .../spotify_player.rs} | 0 src/main.rs | 21 ++++--- src/types.rs | 25 +++++++- 10 files changed, 115 insertions(+), 30 deletions(-) create mode 100644 src/commands/voice/general/general_player.rs create mode 100644 src/commands/voice/general/mod.rs rename src/commands/voice/{player.rs => player_common.rs} (88%) rename src/commands/voice/{soundcloud/souncloud.rs => spotify/spotify_player.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index 999bd3f..b6ca761 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "moover_rust" -version = "0.3.0" +version = "3.3.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/commands/moover/say.rs b/src/commands/moover/say.rs index c097539..e4df757 100644 --- a/src/commands/moover/say.rs +++ b/src/commands/moover/say.rs @@ -1,8 +1,6 @@ use poise; -// use super::super::types::Data; -type Error = Box; -type Context<'a> = poise::Context<'a, (), Error>; +use crate::types::{Context, Error}; #[poise::command( slash_command, diff --git a/src/commands/voice/general/general_player.rs b/src/commands/voice/general/general_player.rs new file mode 100644 index 0000000..c3cd0d6 --- /dev/null +++ b/src/commands/voice/general/general_player.rs @@ -0,0 +1,63 @@ +use std::vec; + +use songbird::input::YoutubeDl; + +use crate::commands::util::connect; +use crate::util::poise_context_extension::ContextExt; +use crate::types::{Context, Error}; +use crate::commands::voice::util::autocomplete_channel; + +// TODO: search, queue +#[poise::command( + slash_command, + description_localized("en-US", "Plays music from supported URL") +)] +pub async fn play(ctx: Context<'_>, + #[autocomplete = "autocomplete_channel"] + #[description = "Voice channel name: "] + channel: Option, + #[description = "Source URL: "] + url: String, +) -> Result<(), Error> { + + if ctx.guild().is_none() { + ctx.reply_ephemeral("Can't use this outside of guild").await?; + return Ok(()); + } + + let manager = songbird::get(ctx.serenity_context()) + .await + .expect("Songbird Voice client placed in at initialisation.") + .clone(); + + let Some(guild_id) = ctx.guild_id() else { + ctx.reply_ephemeral("Guild id not found").await?; + return Ok(()) + }; + + let http_client = &ctx.data().http_client; + + if manager.get(guild_id).is_none() { + match connect(&ctx, guild_id, channel).await { + Ok(_) => (), + Err(e) => { + ctx.reply_ephemeral(&e.to_string()).await?; + return Ok(()) + } + } + } + + if let Some(handler_lock) = manager.get(guild_id) { + let mut handler = handler_lock.lock().await; + + let src = YoutubeDl::new(http_client.clone(), url); + handler.enqueue_input(src.into()).await; + } + else { + ctx.reply_ephemeral("Not in a voice channel").await?; + return Ok(()) + } + + ctx.reply_ephemeral("Done!").await?; + Ok(()) +} diff --git a/src/commands/voice/general/mod.rs b/src/commands/voice/general/mod.rs new file mode 100644 index 0000000..8b6dfd4 --- /dev/null +++ b/src/commands/voice/general/mod.rs @@ -0,0 +1,3 @@ +pub use general_player::*; + +pub mod general_player; \ No newline at end of file diff --git a/src/commands/voice/mod.rs b/src/commands/voice/mod.rs index 0df6866..284b48d 100644 --- a/src/commands/voice/mod.rs +++ b/src/commands/voice/mod.rs @@ -1,5 +1,5 @@ pub mod util; -pub mod player; +pub mod player_common; pub mod radio; // pub mod spotify; -// pub mod yt; \ No newline at end of file +pub mod general; \ No newline at end of file diff --git a/src/commands/voice/player.rs b/src/commands/voice/player_common.rs similarity index 88% rename from src/commands/voice/player.rs rename to src/commands/voice/player_common.rs index 5444b86..38ac50a 100644 --- a/src/commands/voice/player.rs +++ b/src/commands/voice/player_common.rs @@ -9,18 +9,9 @@ use crate::types::{Context, Error}; use super::util::{connect, autocomplete_channel}; -// #[poise::command( -// slash_command, -// description_localized("en-US", "Play song") -// )] -// pub async fn play(ctx: Context<'_>, -// #[description = "Song: "] -// _url: String -// ) -> Result<(), Error> { - -// ctx.reply("Done!").await?; -// Ok(()) -// } +/** + * Common commands that are the same for every implementation + */ #[poise::command( slash_command, @@ -31,7 +22,7 @@ pub async fn disconnect( ) -> Result<(), Error> { let Some(guild_id) = ctx.guild_id() else { - ctx.reply("Can't use this outside of guild").await?; + ctx.reply_ephemeral("Can't use this outside of guild").await?; return Ok(()); }; @@ -49,7 +40,7 @@ pub async fn disconnect( match manager.remove(guild_id).await { Ok(()) => { - ctx.reply("Disconnected").await?; + ctx.reply_ephemeral("Disconnected").await?; return Ok(()) } Err(e) => { diff --git a/src/commands/voice/spotify/mod.rs b/src/commands/voice/spotify/mod.rs index d44230b..f73c81c 100644 --- a/src/commands/voice/spotify/mod.rs +++ b/src/commands/voice/spotify/mod.rs @@ -1 +1,3 @@ -pub mod player; \ No newline at end of file +pub use spotify_player::*; + +pub mod spotify_player; diff --git a/src/commands/voice/soundcloud/souncloud.rs b/src/commands/voice/spotify/spotify_player.rs similarity index 100% rename from src/commands/voice/soundcloud/souncloud.rs rename to src/commands/voice/spotify/spotify_player.rs diff --git a/src/main.rs b/src/main.rs index c85ed95..44bd0d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,8 @@ use serenity::model::gateway::Ready; use serenity::all::{EventHandler, Message}; use serenity::Client; +use reqwest::Client as HttpClient; + use dotenv::dotenv; use songbird::SerenityInit; @@ -27,11 +29,11 @@ mod other; use other::notice; mod types; -use types::Error; +use types::{Data, Error}; struct Handler; -async fn on_error(error: poise::FrameworkError<'_, (), Error>) { +async fn on_error(error: poise::FrameworkError<'_, Data, Error>) { match error { poise::FrameworkError::Setup { error, .. } => panic!("Failed to start bot: {:?}", error), poise::FrameworkError::Command { error, ctx, .. } => { @@ -52,7 +54,7 @@ impl EventHandler for Handler { } async fn ready(&self, ctx: Context, ready: Ready) { - println!("{} v3.2.0 is connected!", ready.user.name); + println!("{} v3.3.0 is connected!", ready.user.name); #[cfg(feature="RELEASE")] { use util::debug::hello; @@ -80,13 +82,14 @@ async fn main() -> anyhow::Result<()> { dotenv().ok(); // create poise framework for registering commands - let options: poise::FrameworkOptions<(), Box> = poise::FrameworkOptions { + let options: poise::FrameworkOptions> = poise::FrameworkOptions { commands: vec![ commands::say(), commands::hug(), - commands::player::play_local(), - commands::player::disconnect(), - commands::radio::radio() + // commands::player::play_local(), + commands::player_common::disconnect(), + commands::radio::radio(), + commands::general::play() ], prefix_options: poise::PrefixFrameworkOptions { prefix: Some("/".into()), @@ -125,7 +128,9 @@ async fn main() -> anyhow::Result<()> { register_globally(ctx, &framework.options().commands).await?; } - Ok(()) + Ok(Data { + http_client: HttpClient::new() + }) }) }) .options(options) diff --git a/src/types.rs b/src/types.rs index b5ebced..1103f1c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,5 +1,28 @@ // pub struct Data {} +use std::{ffi::OsString, path::PathBuf}; + +use serenity::prelude::TypeMapKey; + +use reqwest::Client as HttpClient; + +// TODO: this should be probably expanded also to be used by multiple servers and commands +// radio and spotify commands always create new HttpClient +pub struct HttpKey; + +impl TypeMapKey for HttpKey { + type Value = HttpClient; +} + +pub struct Data { + pub http_client: HttpClient, +} + pub type Error = Box; // replace () with Data if you ever need to store some additional data -pub type Context<'a> = poise::Context<'a, (), Error>; +pub type Context<'a> = poise::Context<'a, Data, Error>; + +pub struct Track { + pub path: PathBuf, + pub name: OsString, +}