diff --git a/Cargo.lock b/Cargo.lock index 62ab676..d5d9c00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,8 +89,10 @@ dependencies = [ "futures-io", "futures-util", "log 0.4.14", + "native-tls", "pin-project-lite", "tokio", + "tokio-native-tls", "tokio-rustls", "tungstenite 0.13.0", "webpki-roots 0.21.1", @@ -1077,6 +1079,31 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "lavalink-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c63ca28d0378fa5e51d24a2cb6cc900a5f654d7931a53784ae01ff9a94b443e1" +dependencies = [ + "async-trait", + "async-tungstenite 0.13.1", + "dashmap", + "futures", + "http", + "regex", + "reqwest", + "serde", + "serde-aux", + "serde_json", + "songbird", + "tokio", + "tokio-native-tls", + "tracing", + "tracing-log", + "typemap_rev", + "version_check", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -2047,6 +2074,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-aux" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77eb8c83f6ebaedf5e8f970a8a44506b180b8e6268de03885c8547031ccaee00" +dependencies = [ + "chrono", + "serde", + "serde_json", +] + [[package]] name = "serde_derive" version = "1.0.125" @@ -2495,6 +2533,7 @@ dependencies = [ "dotenv", "fern", "futures", + "lavalink-rs", "lazy_static", "log 0.4.14", "minecraft-data-rs", @@ -2642,6 +2681,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log 0.4.14", + "tracing-core", +] + [[package]] name = "trigram" version = "0.4.4" @@ -2690,6 +2740,7 @@ dependencies = [ "httparse", "input_buffer 0.4.0", "log 0.4.14", + "native-tls", "rand 0.8.3", "rustls", "sha-1", diff --git a/Cargo.toml b/Cargo.toml index 998ba74..3739499 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,4 +38,5 @@ sauce-api = "0.7.1" rustc_version_runtime = "0.2.0" trigram = "0.4.4" typemap_rev = "0.1.5" -youtube-metadata = "0.1.1" \ No newline at end of file +youtube-metadata = "0.1.1" +lavalink-rs = {version="0.7.1", features=["native"]} \ No newline at end of file diff --git a/src/client.rs b/src/client.rs index 64b239a..4775e5e 100644 --- a/src/client.rs +++ b/src/client.rs @@ -12,28 +12,42 @@ use songbird::SerenityInit; use crate::commands::*; use crate::handler::Handler; +use crate::providers::music::lavalink::{Lavalink, LavalinkHandler}; use crate::utils::context_data::{get_database_from_context, DatabaseContainer, Store, StoreData}; use crate::utils::error::{BotError, BotResult}; use bot_serenityutils::menu::EventDrivenMessageContainer; +use lavalink_rs::LavalinkClient; use serenity::framework::standard::buckets::LimitedFor; +use serenity::http::Http; +use std::env; use std::sync::Arc; use std::time::SystemTime; use tokio::sync::Mutex; pub async fn get_client() -> BotResult { - let token = dotenv::var("BOT_TOKEN").map_err(|_| BotError::MissingToken)?; + let token = env::var("BOT_TOKEN").map_err(|_| BotError::MissingToken)?; let database = get_database()?; - + let http = Http::new_with_token(&token); + let current_application = http.get_current_application_info().await?; let client = Client::builder(token) .event_handler(Handler) .framework(get_framework().await) .register_songbird() .await?; + let data = client.data.clone(); + let http = client.cache_and_http.http.clone(); + let lava_client = LavalinkClient::builder(current_application.id.0) + .set_host(env::var("LAVALINK_HOST").unwrap_or("172.0.0.1".to_string())) + .set_password(env::var("LAVALINK_PORT").expect("Missing lavalink port")) + .set_password(env::var("LAVALINK_PASSWORD").expect("Missing lavalink password")) + .build(LavalinkHandler { data, http }) + .await?; { let mut data = client.data.write().await; data.insert::(StoreData::new()); data.insert::(database); data.insert::(Arc::new(Mutex::new(HashMap::new()))); + data.insert::(lava_client); } Ok(client) diff --git a/src/commands/music/current.rs b/src/commands/music/current.rs index 33f8ce4..caa3aeb 100644 --- a/src/commands/music/current.rs +++ b/src/commands/music/current.rs @@ -30,9 +30,7 @@ async fn current(ctx: &Context, msg: &Message) -> CommandResult { queue_lock.current().clone() }; - if let Some((current, _)) = current { - let metadata = current.metadata().clone(); - log::trace!("Metadata is {:?}", metadata); + if let Some(_) = current { let np_msg = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?; let mut queue_lock = queue.lock().await; diff --git a/src/commands/music/leave.rs b/src/commands/music/leave.rs index 59431c7..bd252e1 100644 --- a/src/commands/music/leave.rs +++ b/src/commands/music/leave.rs @@ -5,7 +5,7 @@ use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; use crate::commands::music::{get_voice_manager, DJ_CHECK}; -use crate::utils::context_data::Store; +use crate::providers::music::lavalink::Lavalink; use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::ephemeral_message::EphemeralMessage; @@ -21,15 +21,6 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult { log::debug!("Leave request received for guild {}", guild.id); let manager = get_voice_manager(ctx).await; - let queue = { - let mut data = ctx.data.write().await; - let store = data.get_mut::().unwrap(); - store - .music_queues - .remove(&guild.id) - .expect("No queue for guild.") - }; - let queue_lock = queue.lock().await; let handler = manager.get(guild.id); if let Some(handler) = handler { @@ -38,10 +29,10 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult { } if manager.get(guild.id).is_some() { - if let Some((current, _)) = queue_lock.current() { - current.stop()?; - } manager.remove(guild.id).await?; + let data = ctx.data.read().await; + let player = data.get::().unwrap(); + player.destroy(guild.id.0).await?; EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { m.content("👋 Left the Voice Channel") }) diff --git a/src/commands/music/lyrics.rs b/src/commands/music/lyrics.rs index 9115d69..b07fc10 100644 --- a/src/commands/music/lyrics.rs +++ b/src/commands/music/lyrics.rs @@ -23,11 +23,10 @@ async fn lyrics(ctx: &Context, msg: &Message) -> CommandResult { ); let queue_lock = queue.lock().await; - if let Some((current, _)) = queue_lock.current() { + if let Some(song) = queue_lock.current() { log::debug!("Playing music. Fetching lyrics for currently playing song..."); - let metadata = current.metadata(); - let title = metadata.title.clone().unwrap(); - let author = metadata.artist.clone().unwrap(); + let title = song.title().clone(); + let author = song.author().clone(); if let Some(lyrics) = get_lyrics(&*author, &*title).await? { log::trace!("Lyrics for '{}' are {}", title, lyrics); diff --git a/src/commands/music/mod.rs b/src/commands/music/mod.rs index 1e0ab1d..fad8b7a 100644 --- a/src/commands/music/mod.rs +++ b/src/commands/music/mod.rs @@ -35,6 +35,7 @@ use shuffle::SHUFFLE_COMMAND; use skip::SKIP_COMMAND; use crate::messages::music::now_playing::update_now_playing_msg; +use crate::providers::music::lavalink::Lavalink; use crate::providers::music::queue::{MusicQueue, Song}; use crate::providers::music::{add_youtube_song_to_database, youtube_dl}; use crate::providers::settings::{get_setting, Setting}; @@ -43,7 +44,9 @@ use crate::utils::error::{BotError, BotResult}; use bot_database::Database; use futures::future::BoxFuture; use futures::FutureExt; +use lavalink_rs::LavalinkClient; use serenity::framework::standard::{Args, CommandOptions, Reason}; +use serenity::prelude::{RwLock, TypeMap}; use youtube_metadata::get_video_information; mod clear_queue; @@ -84,16 +87,27 @@ pub struct Music; struct SongEndNotifier { channel_id: ChannelId, + guild_id: GuildId, http: Arc, queue: Arc>, - handler: Arc>, + data: Arc>, } #[async_trait] impl VoiceEventHandler for SongEndNotifier { async fn act(&self, _ctx: &EventContext<'_>) -> Option { log::debug!("Song ended in {}. Playing next one", self.channel_id); - while !play_next_in_queue(&self.http, &self.channel_id, &self.queue, &self.handler).await { + let data = self.data.read().await; + let player = data.get::().unwrap(); + while !play_next_in_queue( + &self.http, + &self.channel_id, + &self.guild_id, + &self.queue, + player, + ) + .await + { tokio::time::sleep(Duration::from_millis(100)).await; } @@ -129,9 +143,6 @@ impl VoiceEventHandler for ChannelDurationNotifier { let mut handler_lock = self.handler.lock().await; handler_lock.remove_all_global_events(); } - if let Some((current, _)) = queue_lock.current() { - let _ = current.stop(); - } let _ = self.manager.remove(self.guild_id).await; log::debug!("Left the voice channel"); } @@ -156,11 +167,17 @@ async fn join_channel(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) - .expect("Songbird Voice client placed in at initialisation.") .clone(); - let (handler, _) = manager.join(guild_id, channel_id).await; + let (handler, connection) = manager.join_gateway(guild_id, channel_id).await; + let connection = connection.expect("Failed to join gateway"); let mut data = ctx.data.write().await; + let lava_client = data.get::().unwrap(); + lava_client + .create_session(&connection) + .await + .expect("Failed to create lava session"); let store = data.get_mut::().unwrap(); log::debug!("Creating new queue"); - let queue = Arc::new(Mutex::new(MusicQueue::new())); + let queue = Arc::new(Mutex::new(MusicQueue::new(channel_id))); store.music_queues.insert(guild_id, queue.clone()); { @@ -170,10 +187,11 @@ async fn join_channel(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) - handler_lock.add_global_event( Event::Track(TrackEvent::End), SongEndNotifier { - channel_id: channel_id.clone(), + channel_id, + guild_id, http: ctx.http.clone(), queue: Arc::clone(&queue), - handler: handler.clone(), + data: ctx.data.clone(), }, ); @@ -228,11 +246,12 @@ pub(crate) async fn get_queue_for_guild( } /// Plays the next song in the queue -async fn play_next_in_queue( +pub async fn play_next_in_queue( http: &Arc, channel_id: &ChannelId, + guild_id: &GuildId, queue: &Arc>, - handler: &Arc>, + player: &LavalinkClient, ) -> bool { let mut queue_lock = queue.lock().await; @@ -247,7 +266,8 @@ async fn play_next_in_queue( } }; log::debug!("Getting source for song '{}'", url); - let source = match songbird::ytdl(&url).await { + + let query_information = match player.auto_search_tracks(url).await { Ok(s) => s, Err(e) => { let _ = channel_id @@ -259,22 +279,25 @@ async fn play_next_in_queue( return false; } }; - let mut handler_lock = handler.lock().await; - let track = handler_lock.play_only_source(source); - log::trace!("Track is {:?}", track); + if let Err(e) = player + .play(guild_id.0, query_information.tracks[0].clone()) + .start() + .await + { + log::error!("Failed to play song: {:?}", e); + } + log::trace!("Track is {:?}", query_information.tracks[0]); if queue_lock.paused() { - let _ = track.pause(); + let _ = player.pause(guild_id.0).await; } if let Some(np) = &queue_lock.now_playing_msg { - if let Err(e) = - update_now_playing_msg(http, np, track.metadata(), queue_lock.paused()).await - { + if let Err(e) = update_now_playing_msg(http, np, &mut next, queue_lock.paused()).await { log::error!("Failed to update now playing message: {:?}", e); } } - queue_lock.set_current(track, next); + queue_lock.set_current(next); } else { if let Some(np) = mem::take(&mut queue_lock.now_playing_msg) { let np = np.read().await; diff --git a/src/commands/music/pause.rs b/src/commands/music/pause.rs index 3b0de63..33deacf 100644 --- a/src/commands/music/pause.rs +++ b/src/commands/music/pause.rs @@ -6,6 +6,7 @@ use serenity::prelude::*; use crate::commands::common::handle_autodelete; use crate::commands::music::{get_queue_for_guild, DJ_CHECK}; use crate::messages::music::now_playing::update_now_playing_msg; +use crate::providers::music::lavalink::Lavalink; use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::ephemeral_message::EphemeralMessage; @@ -27,17 +28,21 @@ async fn pause(ctx: &Context, msg: &Message) -> CommandResult { let mut queue_lock = queue.lock().await; if let Some(_) = queue_lock.current() { - queue_lock.pause(); - if queue_lock.paused() { + let is_paused = { + let data = ctx.data.read().await; + let player = data.get::().unwrap(); + player.set_pause(guild.id.0, !queue_lock.paused()).await?; + !queue_lock.paused() + }; + queue_lock.set_paused(is_paused); + if is_paused { log::debug!("Paused"); EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { m.content("⏸️ Paused playback️") }) .await?; - if let (Some(menu), Some((current, _))) = - (&queue_lock.now_playing_msg, queue_lock.current()) - { - update_now_playing_msg(&ctx.http, menu, current.metadata(), true).await?; + if let (Some(menu), Some(song)) = (&queue_lock.now_playing_msg, queue_lock.current()) { + update_now_playing_msg(&ctx.http, menu, &mut song.clone(), true).await?; } } else { log::debug!("Resumed"); @@ -45,10 +50,8 @@ async fn pause(ctx: &Context, msg: &Message) -> CommandResult { m.content("▶ Resumed playback️") }) .await?; - if let (Some(menu), Some((current, _))) = - (&queue_lock.now_playing_msg, queue_lock.current()) - { - update_now_playing_msg(&ctx.http, menu, current.metadata(), true).await?; + if let (Some(menu), Some(song)) = (&queue_lock.now_playing_msg, queue_lock.current()) { + update_now_playing_msg(&ctx.http, menu, &mut song.clone(), true).await?; } } } else { diff --git a/src/commands/music/play.rs b/src/commands/music/play.rs index 00e022e..10d274e 100644 --- a/src/commands/music/play.rs +++ b/src/commands/music/play.rs @@ -1,6 +1,6 @@ use serenity::client::Context; use serenity::framework::standard::macros::command; -use serenity::framework::standard::{Args, CommandError, CommandResult}; +use serenity::framework::standard::{Args, CommandResult}; use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; @@ -9,6 +9,7 @@ use crate::commands::music::{ join_channel, play_next_in_queue, }; use crate::messages::music::now_playing::create_now_playing_msg; +use crate::providers::music::lavalink::Lavalink; use crate::providers::settings::{get_setting, Setting}; #[command] @@ -25,21 +26,15 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult { log::debug!("Play request received for guild {}", guild.id); let manager = get_voice_manager(ctx).await; - let mut handler = manager.get(guild.id); + let handler = manager.get(guild.id); if handler.is_none() { log::debug!("Not in a channel. Joining authors channel..."); msg.guild(&ctx.cache).await.unwrap(); let channel_id = get_channel_for_author(&msg.author.id, &guild)?; - handler = Some(join_channel(ctx, channel_id, guild.id).await); + join_channel(ctx, channel_id, guild.id).await; } - let handler_lock = forward_error!( - ctx, - msg.channel_id, - handler.ok_or(CommandError::from("I'm not in a voice channel")) - ); - let songs = get_songs_for_query(&ctx, msg, query).await?; let queue = get_queue_for_guild(ctx, &guild.id).await?; @@ -66,7 +61,11 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult { if play_first { log::debug!("Playing first song in queue"); - while !play_next_in_queue(&ctx.http, &msg.channel_id, &queue, &handler_lock).await {} + let data = ctx.data.read().await; + let lava_player = data.get::().unwrap(); + while !play_next_in_queue(&ctx.http, &msg.channel_id, &guild.id, &queue, &lava_player).await + { + } } if create_now_playing { let handle = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?; diff --git a/src/commands/music/play_next.rs b/src/commands/music/play_next.rs index b188bfc..d668241 100644 --- a/src/commands/music/play_next.rs +++ b/src/commands/music/play_next.rs @@ -1,6 +1,6 @@ use serenity::client::Context; use serenity::framework::standard::macros::command; -use serenity::framework::standard::{Args, CommandError, CommandResult}; +use serenity::framework::standard::{Args, CommandResult}; use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; @@ -9,6 +9,7 @@ use crate::commands::music::{ join_channel, play_next_in_queue, DJ_CHECK, }; use crate::messages::music::now_playing::create_now_playing_msg; +use crate::providers::music::lavalink::Lavalink; #[command] #[only_in(guilds)] @@ -24,21 +25,15 @@ async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let guild = msg.guild(&ctx.cache).await.unwrap(); log::debug!("Playing song as next song for guild {}", guild.id); let manager = get_voice_manager(ctx).await; - let mut handler = manager.get(guild.id); + let handler = manager.get(guild.id); if handler.is_none() { log::debug!("Not in a voice channel. Joining authors channel"); msg.guild(&ctx.cache).await.unwrap(); let channel_id = get_channel_for_author(&msg.author.id, &guild)?; - handler = Some(join_channel(ctx, channel_id, guild.id).await); + join_channel(ctx, channel_id, guild.id).await; } - let handler = forward_error!( - ctx, - msg.channel_id, - handler.ok_or(CommandError::from("I'm not in a voice channel")) - ); - let mut songs = get_songs_for_query(&ctx, msg, query).await?; let queue = get_queue_for_guild(ctx, &guild.id).await?; @@ -57,7 +52,9 @@ async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult { }; if play_first { - while !play_next_in_queue(&ctx.http, &msg.channel_id, &queue, &handler).await {} + let data = ctx.data.read().await; + let player = data.get::().unwrap(); + while !play_next_in_queue(&ctx.http, &msg.channel_id, &guild.id, &queue, &player).await {} } if create_now_playing { let handle = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?; diff --git a/src/commands/music/skip.rs b/src/commands/music/skip.rs index 596e136..987abdb 100644 --- a/src/commands/music/skip.rs +++ b/src/commands/music/skip.rs @@ -4,7 +4,8 @@ use serenity::framework::standard::CommandResult; use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; -use crate::commands::music::{get_queue_for_guild, DJ_CHECK}; +use crate::commands::music::DJ_CHECK; +use crate::providers::music::lavalink::Lavalink; use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::ephemeral_message::EphemeralMessage; @@ -18,15 +19,11 @@ use bot_serenityutils::ephemeral_message::EphemeralMessage; async fn skip(ctx: &Context, msg: &Message) -> CommandResult { let guild = msg.guild(&ctx.cache).await.unwrap(); log::debug!("Skipping song for guild {}", guild.id); - let queue = forward_error!( - ctx, - msg.channel_id, - get_queue_for_guild(ctx, &guild.id).await - ); - let queue_lock = queue.lock().await; - if let Some((current, _)) = queue_lock.current() { - current.stop()?; + { + let data = ctx.data.read().await; + let player = data.get::().unwrap(); + player.stop(guild.id.0).await?; } EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { diff --git a/src/messages/music/now_playing.rs b/src/messages/music/now_playing.rs index 2e8a7c8..67489ac 100644 --- a/src/messages/music/now_playing.rs +++ b/src/messages/music/now_playing.rs @@ -3,12 +3,12 @@ use std::sync::Arc; use serenity::builder::CreateEmbed; use serenity::http::Http; use serenity::model::prelude::ChannelId; -use songbird::input::Metadata; use crate::commands::music::{get_queue_for_guild, get_voice_manager, is_dj}; use crate::messages::add_ephemeral_handle_to_database; use crate::providers::music::add_youtube_song_to_database; -use crate::providers::music::queue::MusicQueue; +use crate::providers::music::lavalink::Lavalink; +use crate::providers::music::queue::{MusicQueue, Song}; use crate::utils::context_data::{DatabaseContainer, Store}; use crate::utils::error::*; use bot_serenityutils::core::MessageHandle; @@ -68,9 +68,12 @@ pub async fn create_now_playing_msg( log::debug!("Queue locked"); let mut page = CreateMessage::default(); - if let Some((current, _)) = queue.current() { + if let Some(mut current) = queue.current().clone() { + let mut embed = CreateEmbed::default(); + create_now_playing_embed(&mut current, &mut embed, queue.paused(), nsfw).await; page.embed(|e| { - create_now_playing_embed(current.metadata(), e, queue.paused(), nsfw) + e.0.clone_from(&embed.0); + e }); } else { page.embed(|e| e.description("Queue is empty")); @@ -94,7 +97,7 @@ pub async fn create_now_playing_msg( pub async fn update_now_playing_msg( http: &Arc, handle: &Arc>, - meta: &Metadata, + song: &mut Song, paused: bool, ) -> BotResult<()> { log::debug!("Updating now playing message"); @@ -102,9 +105,14 @@ pub async fn update_now_playing_msg( let mut message = handle.get_message(http).await?; let nsfw = http.get_channel(handle.channel_id).await?.is_nsfw(); + let mut embed = CreateEmbed::default(); + create_now_playing_embed(song, &mut embed, paused, nsfw).await; message .edit(http, |m| { - m.embed(|e| create_now_playing_embed(meta, e, paused, nsfw)) + m.embed(|e| { + e.0.clone_from(&embed.0); + e + }) }) .await?; log::debug!("Message updated."); @@ -113,19 +121,20 @@ pub async fn update_now_playing_msg( } /// Creates the embed of the now playing message -fn create_now_playing_embed<'a>( - meta: &Metadata, +async fn create_now_playing_embed<'a>( + song: &mut Song, mut embed: &'a mut CreateEmbed, paused: bool, nsfw: bool, ) -> &'a mut CreateEmbed { + let url = song.url().await.unwrap(); embed = embed .title(if paused { "Paused" } else { "Playing" }) .description(format!( "[{}]({}) by {}", - meta.title.clone().unwrap(), - meta.source_url.clone().unwrap(), - meta.artist.clone().unwrap() + song.title().clone(), + url, + song.author().clone() )) .footer(|f| { f.text(format!( @@ -135,7 +144,7 @@ fn create_now_playing_embed<'a>( }); if nsfw { - if let Some(thumb) = meta.thumbnail.clone() { + if let Some(thumb) = song.thumbnail().clone() { embed = embed.thumbnail(thumb); } } @@ -162,7 +171,16 @@ async fn play_pause_button_action( let (current, message, paused) = { log::debug!("Queue is locked"); let mut queue = queue.lock().await; - queue.pause(); + { + let pause = !queue.paused(); + let data = ctx.data.read().await; + let player = data.get::().unwrap(); + player + .set_pause(guild_id.0, pause) + .await + .map_err(BotError::from)?; + queue.set_paused(pause); + } ( queue.current().clone(), queue.now_playing_msg.clone().unwrap(), @@ -171,8 +189,8 @@ async fn play_pause_button_action( }; log::debug!("Queue is unlocked"); - if let Some((current, _)) = current { - update_now_playing_msg(&ctx.http, &message, current.metadata(), paused).await?; + if let Some(mut current) = current { + update_now_playing_msg(&ctx.http, &message, &mut current, paused).await?; } } @@ -191,16 +209,11 @@ async fn skip_button_action( if !is_dj(ctx, guild_id, &user).await? { return Ok(()); } - { - let current = { - let queue = get_queue_for_guild(ctx, &guild_id).await?; - let queue = queue.lock().await; - queue.current().clone() - }; - if let Some((current, _)) = current { - let _ = current.stop(); - } + { + let data = ctx.data.read().await; + let player = data.get::().unwrap(); + player.stop(guild_id.0).await.map_err(BotError::from)?; } Ok(()) @@ -220,8 +233,6 @@ async fn stop_button_action( } { let manager = get_voice_manager(ctx).await; - let queue = get_queue_for_guild(ctx, &guild_id).await?; - let queue = queue.lock().await; let handler = manager.get(guild_id); @@ -229,12 +240,12 @@ async fn stop_button_action( let mut handler_lock = handler.lock().await; handler_lock.remove_all_global_events(); } - if let Some(current) = queue.current() { - current.0.stop().map_err(BotError::from)?; - } if manager.get(guild_id).is_some() { manager.remove(guild_id).await.map_err(BotError::from)?; + let data = ctx.data.read().await; + let player = data.get::().unwrap(); + player.destroy(guild_id.0).await.map_err(BotError::from)?; log::debug!("Left the voice channel"); } else { log::debug!("Not in a voice channel"); @@ -260,7 +271,7 @@ async fn good_pick_action( let queue = get_queue_for_guild(ctx, &guild_id).await?; let queue = queue.lock().await; - if let Some((_, song)) = queue.current() { + if let Some(song) = queue.current() { let data = ctx.data.read().await; let store = data.get::().unwrap(); let database = data.get::().unwrap(); diff --git a/src/providers/music/lavalink.rs b/src/providers/music/lavalink.rs new file mode 100644 index 0000000..3cbf50e --- /dev/null +++ b/src/providers/music/lavalink.rs @@ -0,0 +1,56 @@ +use crate::commands::music::play_next_in_queue; +use crate::utils::context_data::Store; +use lavalink_rs::gateway::LavalinkEventHandler; +use lavalink_rs::model::{TrackFinish, TrackStart}; +use lavalink_rs::LavalinkClient; +use serenity::async_trait; +use serenity::http::Http; +use serenity::model::id::GuildId; +use serenity::prelude::TypeMapKey; +use std::sync::Arc; +use tokio::sync::RwLock; +use typemap_rev::TypeMap; + +pub struct LavalinkHandler { + pub data: Arc>, + pub http: Arc, +} + +#[async_trait] +impl LavalinkEventHandler for LavalinkHandler { + async fn track_start(&self, _client: LavalinkClient, event: TrackStart) { + log::info!("Track started!\nGuild: {}", event.guild_id); + } + async fn track_finish(&self, client: LavalinkClient, event: TrackFinish) { + log::info!("Track finished!\nGuild: {}", event.guild_id); + let queue = { + let data = self.data.read().await; + let store = data.get::().unwrap(); + + store + .music_queues + .get(&GuildId(event.guild_id)) + .unwrap() + .clone() + }; + let channel_id = { + let queue = queue.lock().await; + queue.channel_id() + }; + while !play_next_in_queue( + &self.http, + &channel_id, + &GuildId(event.guild_id), + &queue, + &client, + ) + .await + {} + } +} + +pub struct Lavalink; + +impl TypeMapKey for Lavalink { + type Value = LavalinkClient; +} diff --git a/src/providers/music/mod.rs b/src/providers/music/mod.rs index ec17eaa..00991a6 100644 --- a/src/providers/music/mod.rs +++ b/src/providers/music/mod.rs @@ -7,6 +7,7 @@ use regex::Regex; use responses::VideoInformation; use youtube_dl::search_video_information; +pub(crate) mod lavalink; pub(crate) mod lyrics; pub(crate) mod queue; pub(crate) mod responses; diff --git a/src/providers/music/queue.rs b/src/providers/music/queue.rs index 7929a95..382238c 100644 --- a/src/providers/music/queue.rs +++ b/src/providers/music/queue.rs @@ -1,7 +1,6 @@ use std::collections::VecDeque; use aspotify::Track; -use songbird::tracks::TrackHandle; use bot_coreutils::shuffle::Shuffle; @@ -9,26 +8,29 @@ use crate::providers::music::responses::{PlaylistEntry, VideoInformation}; use crate::providers::music::song_to_youtube_video; use bot_database::models::YoutubeSong; use bot_serenityutils::core::MessageHandle; +use serenity::model::id::ChannelId; use std::sync::Arc; use tokio::sync::RwLock; #[derive(Clone)] pub struct MusicQueue { inner: VecDeque, - current: Option<(TrackHandle, Song)>, + current: Option, paused: bool, pub now_playing_msg: Option>>, pub leave_flag: bool, + channel_id: ChannelId, } impl MusicQueue { - pub fn new() -> Self { + pub fn new(channel_id: ChannelId) -> Self { Self { inner: VecDeque::new(), current: None, paused: false, leave_flag: false, now_playing_msg: None, + channel_id, } } @@ -58,8 +60,8 @@ impl MusicQueue { } /// Sets the currently playing song - pub fn set_current(&mut self, handle: TrackHandle, song: Song) { - self.current = Some((handle, song)) + pub fn set_current(&mut self, song: Song) { + self.current = Some(song) } /// Clears the currently playing song @@ -68,10 +70,20 @@ impl MusicQueue { } /// Returns the reference to the currently playing song - pub fn current(&self) -> &Option<(TrackHandle, Song)> { + pub fn current(&self) -> &Option { &self.current } + /// Returns if the queue is paused + pub fn paused(&self) -> bool { + self.paused + } + + /// Sets if the queue is paused + pub fn set_paused(&mut self, paused: bool) { + self.paused = paused; + } + /// Clears the queue pub fn clear(&mut self) { self.inner.clear(); @@ -89,24 +101,9 @@ impl MusicQueue { self.inner.remove(index); } - /// Toggles pause - pub fn pause(&mut self) { - if let Some(current) = &self.current { - if self.paused { - let _ = current.0.play(); - } else { - let _ = current.0.pause(); - } - - self.paused = !self.paused; - } else { - self.paused = false; - } - } - - /// Returns if the queue is paused - pub fn paused(&self) -> bool { - self.paused + /// The channel id where the music messages should be sent to + pub fn channel_id(&self) -> ChannelId { + self.channel_id } } diff --git a/src/utils/error.rs b/src/utils/error.rs index 664d77b..ebdc370 100644 --- a/src/utils/error.rs +++ b/src/utils/error.rs @@ -1,4 +1,5 @@ use bot_serenityutils::error::SerenityUtilsError; +use lavalink_rs::error::LavalinkError; use thiserror::Error; pub type BotResult = Result; @@ -44,6 +45,9 @@ pub enum BotError { #[error("YouTube Error: {0}")] YoutubeError(#[from] youtube_metadata::error::YoutubeError), + #[error("Lavalink Error: {0}")] + LavalinkError(#[from] LavalinkError), + #[error("{0}")] Msg(String), }