From 880e81646d70c600a62b4d7f48b8fda490b09467 Mon Sep 17 00:00:00 2001 From: moonleay Date: Mon, 20 May 2024 04:07:10 +0200 Subject: [PATCH] WIP: code compiles now, connects to VC, does not play audio yet --- Cargo.lock | 176 ++++++++++++++++++++++----- Cargo.toml | 2 +- src/commands/now_playing.rs | 64 ++++------ src/events/lavalink_event_handler.rs | 8 +- src/main.rs | 32 ++--- src/music/music_manager.rs | 130 ++++++++++++-------- src/util/config.rs | 6 +- 7 files changed, 280 insertions(+), 138 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5e1094..becb948 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,20 +78,10 @@ dependencies = [ ] [[package]] -name = "async-tungstenite" -version = "0.24.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3609af4bbf701ddaf1f6bb4e6257dff4ff8932327d0e685d3f653724c258b1ac" -dependencies = [ - "futures-io", - "futures-util", - "log", - "native-tls", - "pin-project-lite", - "tokio", - "tokio-native-tls", - "tungstenite 0.21.0", -] +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "audiopus" @@ -719,6 +709,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -764,6 +773,29 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.8.0" @@ -786,9 +818,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", - "http-body", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -800,6 +832,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -808,7 +860,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper", + "hyper 0.14.28", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", @@ -821,12 +873,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.28", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.3.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -902,25 +990,29 @@ dependencies = [ [[package]] name = "lavalink-rs" -version = "0.10.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87676c01f7c63f255899cc2672f11a1d7d6848787cf0e6abbc6b79c839cf9f24" +checksum = "4ba892eecbfb64bfa0e4fca87462a33fa8616915a6532d38026267147dd62cdb" dependencies = [ "arc-swap", - "async-tungstenite", + "bytes", "dashmap", "futures", "http 1.1.0", + "http-body-util", + "hyper 1.3.1", + "hyper-tls 0.6.0", + "hyper-util", "oneshot", - "reqwest", "serde", "serde_json", "serde_qs", "serenity", "songbird", "tokio", + "tokio-tungstenite 0.21.0", "tracing", - "url", + "urlencoding", "version_check", ] @@ -1515,12 +1607,12 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", - "http-body", - "hyper", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-rustls", - "hyper-tls", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -1874,9 +1966,9 @@ dependencies = [ [[package]] name = "serde_qs" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" dependencies = [ "percent-encoding", "serde", @@ -2530,6 +2622,28 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -2813,6 +2927,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" diff --git a/Cargo.toml b/Cargo.toml index 8d55e2e..f3d5fa5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" confy = "0.6.0" songbird = { version = "0.4", features = ["gateway", "serenity", "native", "driver"], default-features = false } -lavalink-rs = { version = "0.10", features = ["songbird", "serenity-native", "native-tls"], default-features = false } +lavalink-rs = { version = "0.12", features = ["songbird", "serenity", "native-tls"], default-features = false } chrono = "0.4" reqwest = "0.11" symphonia = "0.5" diff --git a/src/commands/now_playing.rs b/src/commands/now_playing.rs index f124a7a..becadda 100644 --- a/src/commands/now_playing.rs +++ b/src/commands/now_playing.rs @@ -1,6 +1,9 @@ +use std::time::Duration; + +use crate::music::preview::Preview; use crate::util::embed::Embed; use lavalink_rs::client::LavalinkClient; -use serenity::all::{CommandInteraction, Context}; +use serenity::all::{CommandInteraction, Context, GuildId}; use serenity::builder::{CreateCommand, CreateEmbed}; pub async fn run(ctx: &Context, command: &CommandInteraction, llc: &LavalinkClient) -> CreateEmbed { @@ -17,47 +20,27 @@ pub async fn run(ctx: &Context, command: &CommandInteraction, llc: &LavalinkClie } }; - let now_plaing = match music_queue::get_now_playing(&guild_id).await { - Some(ytdl) => ytdl, - None => { - return Embed::create_error_respose( - username, - "Not playing", - "I'm not playing anything!", - ); - } + let Some(player_context) = llc.get_player_context(guild_id.get()) else { + return Embed::create_error_respose(username, "Not playing", "There is no player context and therefore there is nothing playing."); }; - let now_handle = match music_queue::get_now_playing_track_handle(&guild_id).await { - Some(handle) => handle, - None => { - return Embed::create_error_respose( - username, - "Cannot get TrackHandle", - "The TrackHandle is empty.", - ); - } + let Ok(player) = player_context.get_player().await else { + return Embed::create_error_respose(username, "Can't get player", "Cannot get player from player context."); }; - let manager = songbird::get(ctx) - .await - .expect("Cannot get Songbird") - .clone(); - let _ = match manager.get(*guild_id) { - Some(handler) => handler, - None => { - return Embed::create_error_respose( - username, - "Error", - "Error while getting the audio handler.", - ); - } - }; - - let position = now_handle.get_info().await.unwrap().position; - - Embed::create_playing(now_plaing, username, "Currently playing") + if let Some(npt) = player.track { + let npti = npt.info; + let preview_data = Preview { + title: npti.title, + artist: Some(npti.author.to_string()), + duration: Some(npti.length), + thumbnail: npti.artwork_url, + link: npti.uri + }; + let position = Duration::from_millis(player_context.get_player().await.expect("Can't get player").state.position); + + return Embed::create_playing(preview_data, username, "Now playing") .await .field( "Position", @@ -66,8 +49,11 @@ pub async fn run(ctx: &Context, command: &CommandInteraction, llc: &LavalinkClie position.as_secs() / 60, position.as_secs() % 60 ), - true, - ) + true + ); + } + + return Embed::create_error_respose(username, "Not playing", "I'm currently not playing anything."); } pub fn register() -> CreateCommand { diff --git a/src/events/lavalink_event_handler.rs b/src/events/lavalink_event_handler.rs index 6b6f734..f994a93 100644 --- a/src/events/lavalink_event_handler.rs +++ b/src/events/lavalink_event_handler.rs @@ -8,22 +8,22 @@ pub async fn ready_event(client: LavalinkClient, _session_id: String, event: &ev } #[hook] -pub async fn track_start(client: LavalinkClient, _session_id: String, event: &events::TrackStart) { +pub async fn track_start(_client: LavalinkClient, _session_id: String, _event: &events::TrackStart) { } #[hook] -pub async fn track_end_event(client: LavalinkClient, _session_id: String, event: &events::TrackEnd) { +pub async fn track_end_event(_client: LavalinkClient, _session_id: String, _event: &events::TrackEnd) { } #[hook] -pub async fn track_exception(client: LavalinkClient, _session_id: String, event: &events::TrackException) { +pub async fn track_exception(_client: LavalinkClient, _session_id: String, _event: &events::TrackException) { } #[hook] -pub async fn track_stuck(client: LavalinkClient, _session_id: String, event: &events::TrackStuck) { +pub async fn track_stuck(_client: LavalinkClient, _session_id: String, _event: &events::TrackStuck) { } diff --git a/src/main.rs b/src/main.rs index 5dfc403..c2d1814 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,12 @@ mod music; mod util; mod events; +use std::ops::Deref; + use lavalink_rs::client::LavalinkClient; +use lavalink_rs::model::client::NodeDistributionStrategy; use lavalink_rs::model::events::Events; +use lavalink_rs::model::UserId; use lavalink_rs::node::NodeBuilder; use serenity::all::{ CommandInteraction, CreateInteractionResponseFollowup, OnlineStatus, VoiceState, @@ -29,7 +33,7 @@ use crate::events::lavalink_event_handler; extern crate lazy_static; struct Handler { - llc: LavalinkClient + pub llc: LavalinkClient } #[async_trait] @@ -120,26 +124,26 @@ async fn main() { let status = OnlineStatus::DoNotDisturb; let activity = ActivityData::streaming("music", "https://twitch.tv/moonleaytv").unwrap(); + let event = Events { + ready: Some(lavalink_event_handler::ready_event), + // track_start: Some(lavalink_event_handler::track_start), + // track_end: Some(lavalink_event_handler::track_end_event), + // track_exception: Some(lavalink_event_handler::track_exception), + // track_stuck: Some(lavalink_event_handler::track_stuck), + ..Default::default() + }; + let node_builder = NodeBuilder { hostname: config.lavalink_address, password: config.lavalink_password, + user_id: UserId(config.user_id), is_ssl: false, - ..Default::default() + events: event.clone(), + session_id: None }; - let event = Events { - ready: Some(lavalink_event_handler::ready_event), - track_start: Some(lavalink_event_handler::track_start), - track_end: Some(lavalink_event_handler::track_end_event), - track_exception: Some(lavalink_event_handler::track_exception), - track_stuck: Some(lavalink_event_handler::track_stuck), - ..Default::default() - }; - let lavalink_client = LavalinkClient::new(event, vec![node_builder]); - tokio::spawn(async move { - lavalink_client.start().await; - }); + let lavalink_client = LavalinkClient::new(event, vec![node_builder], NodeDistributionStrategy::round_robin()).await; // Build the client let mut client = Client::builder(config.discord_token, GatewayIntents::empty()) diff --git a/src/music/music_manager.rs b/src/music/music_manager.rs index ab400eb..50af08d 100644 --- a/src/music/music_manager.rs +++ b/src/music/music_manager.rs @@ -2,11 +2,12 @@ use crate::util::embed::Embed; use crate::util::user_util; use crate::util::user_util::get_vc_id; use crate::music::preview::Preview; -use lavalink_rs::client::LavalinkClient; +use lavalink_rs::{client::LavalinkClient, player_context::QueueMessage}; use lavalink_rs::model::search::SearchEngines; use lavalink_rs::model::track::TrackLoadData; use lavalink_rs::player_context::TrackInQueue; use serenity::all::{Context, CreateEmbed, GuildId, UserId}; +use songbird::error::JoinError; use std::sync::Arc; /// Either queues the song, or start playing it instantly, depending on if there is already a song playing @@ -56,7 +57,7 @@ pub async fn attempt_to_queue_song( .unwrap(); } - Err(why) => { + Err(_) => { return Embed::create_error_respose(username, "Cannot join", "Could not join the channel."); } } @@ -79,7 +80,7 @@ pub async fn attempt_to_queue_song( let search_query = if do_search { match SearchEngines::YouTube.to_query(&query) { Ok(x) => x, - Err(why) => { + Err(_) => { return Embed::create_error_respose(username, "Cannot generate query", "Could not generate a seach query.."); } } @@ -139,13 +140,17 @@ pub async fn attempt_to_queue_song( } }; - let mut q = match player.get_queue().await { - Ok(q) => q, - Err(why) => { - return Embed::create_error_respose(username, "Cannont get queue", "Could not get queue."); + + let q = player.get_queue(); + q.append(tracks.into()); + + if let Ok(player_data) = player.get_player().await { + if player_data.track.is_none() && q.get_track(0).await.is_ok_and(|x| x.is_some()) { + player.skip(); } - }; - q.append(&mut tracks.into()); + } + + Embed::create_playing(preview_data.unwrap(), username, &response_title).await } @@ -166,54 +171,76 @@ pub async fn attempt_to_skip_current_song( ); } - let connect_to = match get_vc_id(ctx, guild_id, user_id).await { + let user_channel = match get_vc_id(ctx, guild_id, user_id).await { Some(channel_id) => channel_id, None => { return Embed::create_error_respose(username, "Error", "Cannot find channel_id."); } }; - let manager: &Arc = &songbird::get(ctx) // TODO match - .await - .expect("Cannot get Songbird.") - .clone(); - let self_channel = user_util::get_self_vc_id(ctx, guild_id).await; if !user_util::is_self_connected_to_vc(ctx, guild_id).await { - // self is connected to vc, check if user is in same vc + // Self is not connected to vc, cannot skip. - if self_channel.is_none() { - // Connect to VC - manager // TODO match - .join(*guild_id, connect_to) - .await - .expect("Cannot connect>..."); - } + return Embed::create_error_respose(username, "Not connected", "I am not connected and I am not playing anything, therefore you cannot skip."); } else { - let self_channel = self_channel.expect("Cannot get self channel"); // TODO: match + let self_channel = self_channel.expect("Cannot get self channel"); // Check if user is in the same VC as the bot - if self_channel != connect_to { + if self_channel != user_channel { return Embed::create_error_respose( username, "You are not in my VC", - "You have to be in my VC in order to controll the music.", + "You have to be in my VC in order to control the music.", ); } } - let head = music_queue::next(guild_id).await; // TODO match - if head.is_none() { - return Embed::create_error_respose( - username, - "Cannot find a song to play", - "The queue is empty.", - ); - } - let head = head.unwrap(); - play_song(ctx, guild_id, llc, &head).await; + let Some(player_context) = llc.get_player_context(guild_id.get()) else { + return Embed::create_error_respose(username, "Not playing", "There is no player context and therefore there is nothing playing."); + }; + + let Ok(player) = player_context.get_player().await else { + return Embed::create_error_respose(username, "Can't get player", "Cannot get player from player context."); + }; + + + if let Some(_) = player.track { + let Ok(_) = player_context.skip() else { + return Embed::create_error_respose(username, "Cannot skip", "Could not skip track."); + }; + let ct = match player_context.get_player().await.expect("Can't get player").track { + Some(data) => data.info, + None => { // Disconnect + let songbird = songbird::get(ctx).await.unwrap().clone(); + + let Ok(_) = llc.delete_player(guild_id.get()).await else { + return Embed::create_error_respose(username, "Cannot delete player", "Could not delete player."); + }; + if songbird.get(GuildId::new(guild_id.get())).is_some() { + let Ok(_) = songbird.remove(GuildId::new(guild_id.get())).await else { + return Embed::create_error_respose(username, "Cannot remove ref", "Cannot remove songbird ref."); + }; + } + + return Embed::create_error_respose(username, "No track to skip to", "Therefore I am leaving the channel.") + } + }; + + let preview_data = Preview { + title: ct.title, + artist: Some(ct.author.to_string()), + duration: Some(ct.length), + thumbnail: ct.artwork_url, + link: ct.uri + }; + + return Embed::create_playing(preview_data, username, "Skipped; Now playing").await; + } + + return Embed::create_error_respose(username, "Not playing", "I'm currently not playing anything."); + - Embed::create_playing(head, username, "Song skipped; Now playing").await } /// Try to clear the queue and stop playing. Also leave the vc @@ -248,6 +275,9 @@ pub async fn attempt_to_stop( ); } + let player = llc.get_player_context(guild_id.get()).expect("Can't get player context."); + player.get_queue().clear(); + let stopped = match leave(ctx, llc, guild_id).await { Ok(stopped) => stopped, Err(e) => { @@ -267,26 +297,26 @@ pub async fn attempt_to_stop( "I am not connected.\nI cant stop doing something, when I'm not doing it.", ) } else { - music_queue::delete_queue(guild_id).await; // Clear queue - Embed::create_success_response(username, "I stopped and left", "Just like you girlfriend.") } } /// Make the bot leave the voice channel. Returns Ok(true) if bot was connected, returns Ok(false) if bot was not connected. Returns Err if something went wrong. pub async fn leave(ctx: &Context, llc: &LavalinkClient, guild_id: &GuildId) -> Result { - let manager = songbird::get(ctx) - .await - .expect("Cannot get Songbird") - .clone(); + let Some(songbird) = songbird::get(ctx) + .await.clone() else { + return Err(JoinError::NoSender); + }; - let handler = manager.get(*guild_id); - let has_handler = handler.is_some(); + let Ok(_) = llc.delete_player(guild_id.get()).await else { + return Err(JoinError::Dropped); + }; - if has_handler { - handler.unwrap().lock().await.stop(); - manager.remove(*guild_id).await?; - return Ok(true); // Handler removed + if songbird.get(GuildId::new(guild_id.get())).is_some() { + let Ok(_) = songbird.remove(GuildId::new(guild_id.get())).await else { + return Err(JoinError::Dropped); + }; } - Ok(false) // No handler, so it's already stopped + + return Ok(true) } diff --git a/src/util/config.rs b/src/util/config.rs index acd5500..a22568d 100644 --- a/src/util/config.rs +++ b/src/util/config.rs @@ -7,7 +7,8 @@ use std::error::Error; pub struct Config { pub discord_token: String, pub lavalink_address: String, - pub lavalink_password: String + pub lavalink_password: String, + pub user_id: u64 } const CONFIG_FILE: &str = "./data/config.json"; @@ -26,7 +27,8 @@ fn create_empty() -> fs::File { let example_config = Config { discord_token: "paste_your_token".to_string(), lavalink_address: "paste_your_lavalink_address".to_string(), - lavalink_password: "paste_your_lavalink_password".to_string() + lavalink_password: "paste_your_lavalink_password".to_string(), + user_id: 976119987330777168 }; let mut config_file = fs::File::create(CONFIG_FILE).unwrap();