From b2ba31a9e94eb7dcebfa221f07de3d1d6ebf53c4 Mon Sep 17 00:00:00 2001 From: trivernis Date: Tue, 20 Apr 2021 15:03:22 +0200 Subject: [PATCH] Reimplement all music related functionality in MusicPlayer struct Signed-off-by: trivernis --- src/client.rs | 11 +- src/commands/misc/stats.rs | 26 +-- src/commands/music/clear_queue.rs | 21 ++- src/commands/music/current.rs | 36 ++-- src/commands/music/join.rs | 5 +- src/commands/music/leave.rs | 40 ++--- src/commands/music/lyrics.rs | 63 +++---- src/commands/music/mod.rs | 235 ++------------------------ src/commands/music/move_song.rs | 21 ++- src/commands/music/pause.rs | 41 ++--- src/commands/music/play.rs | 42 ++--- src/commands/music/play_next.rs | 39 +++-- src/commands/music/queue.rs | 22 ++- src/commands/music/remove_song.rs | 21 ++- src/commands/music/shuffle.rs | 21 ++- src/commands/music/skip.rs | 18 +- src/handler.rs | 8 +- src/messages/music/mod.rs | 1 + src/messages/music/no_voicechannel.rs | 19 +++ src/messages/music/now_playing.rs | 64 +++---- src/providers/music/lavalink.rs | 42 ++--- src/providers/music/mod.rs | 13 +- src/providers/music/player.rs | 181 ++++++++++++++++++++ src/providers/music/queue.rs | 32 +--- src/utils/context_data.rs | 11 +- 25 files changed, 480 insertions(+), 553 deletions(-) create mode 100644 src/messages/music/no_voicechannel.rs create mode 100644 src/providers/music/player.rs diff --git a/src/client.rs b/src/client.rs index 4775e5e..a81ad0a 100644 --- a/src/client.rs +++ b/src/client.rs @@ -13,7 +13,9 @@ 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::context_data::{ + get_database_from_context, DatabaseContainer, MusicPlayers, Store, StoreData, +}; use crate::utils::error::{BotError, BotResult}; use bot_serenityutils::menu::EventDrivenMessageContainer; use lavalink_rs::LavalinkClient; @@ -35,19 +37,20 @@ pub async fn get_client() -> BotResult { .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 }) + .build(LavalinkHandler { data }) .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); + data.insert::(HashMap::new()); + data.insert::(Arc::new(lava_client)); } Ok(client) diff --git a/src/commands/misc/stats.rs b/src/commands/misc/stats.rs index 9575ba4..259c11f 100644 --- a/src/commands/misc/stats.rs +++ b/src/commands/misc/stats.rs @@ -9,9 +9,7 @@ use serenity::prelude::*; use sysinfo::{ProcessExt, SystemExt}; use crate::commands::common::handle_autodelete; -use crate::providers::music::queue::MusicQueue; -use crate::utils::context_data::{get_database_from_context, Store}; -use std::sync::Arc; +use crate::utils::context_data::{get_database_from_context, MusicPlayers}; #[command] #[description("Shows some statistics about the bot")] @@ -94,23 +92,7 @@ async fn stats(ctx: &Context, msg: &Message) -> CommandResult { /// Returns the total number of queues that are not /// flagged to leave async fn get_queue_count(ctx: &Context) -> usize { - let queues: Vec>> = { - let data = ctx.data.read().await; - let store = data.get::().unwrap(); - - store - .music_queues - .iter() - .map(|(_, q)| Arc::clone(q)) - .collect() - }; - let mut count = 0; - for queue in queues { - let queue = queue.lock().await; - if !queue.leave_flag { - count += 1; - } - } - - count + let data = ctx.data.read().await; + let players = data.get::().unwrap(); + players.len() } diff --git a/src/commands/music/clear_queue.rs b/src/commands/music/clear_queue.rs index cf315c0..bd1db27 100644 --- a/src/commands/music/clear_queue.rs +++ b/src/commands/music/clear_queue.rs @@ -1,12 +1,13 @@ use serenity::client::Context; use serenity::framework::standard::macros::command; -use serenity::framework::standard::CommandResult; +use serenity::framework::standard::{CommandResult, CommandError}; 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::{get_music_player_for_guild, DJ_CHECK}; use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::ephemeral_message::EphemeralMessage; +use crate::messages::music::no_voicechannel::create_no_voicechannel_message; #[command] #[only_in(guilds)] @@ -19,14 +20,16 @@ async fn clear_queue(ctx: &Context, msg: &Message) -> CommandResult { let guild = msg.guild(&ctx.cache).await.unwrap(); log::debug!("Clearing queue for guild {}", guild.id); - let queue = forward_error!( - ctx, - msg.channel_id, - get_queue_for_guild(ctx, &guild.id).await - ); + let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await { + player + } else { + return create_no_voicechannel_message(&ctx.http, msg.channel_id) + .await + .map_err(CommandError::from); + }; { - let mut queue_lock = queue.lock().await; - queue_lock.clear(); + let mut player = player.lock().await; + player.queue().clear(); } EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { diff --git a/src/commands/music/current.rs b/src/commands/music/current.rs index caa3aeb..8a75007 100644 --- a/src/commands/music/current.rs +++ b/src/commands/music/current.rs @@ -1,12 +1,11 @@ -use std::mem; - use serenity::client::Context; use serenity::framework::standard::macros::command; -use serenity::framework::standard::CommandResult; +use serenity::framework::standard::{CommandError, CommandResult}; use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; -use crate::commands::music::get_queue_for_guild; +use crate::commands::music::get_music_player_for_guild; +use crate::messages::music::no_voicechannel::create_no_voicechannel_message; use crate::messages::music::now_playing::create_now_playing_msg; #[command] @@ -19,27 +18,22 @@ async fn current(ctx: &Context, msg: &Message) -> CommandResult { let guild = msg.guild(&ctx.cache).await.unwrap(); log::debug!("Displaying current song for queue in {}", guild.id); - let queue = forward_error!( - ctx, - msg.channel_id, - get_queue_for_guild(ctx, &guild.id).await - ); - + let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await { + player + } else { + return create_no_voicechannel_message(&ctx.http, msg.channel_id) + .await + .map_err(CommandError::from); + }; let current = { - let queue_lock = queue.lock().await; - queue_lock.current().clone() + let mut player = player.lock().await; + player.queue().current().clone() }; 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; - if let Some(old_np) = mem::replace(&mut queue_lock.now_playing_msg, Some(np_msg)) { - let old_np = old_np.read().await; - if let Ok(message) = old_np.get_message(&ctx.http).await { - let _ = message.delete(ctx).await; - } - } + let np_msg = create_now_playing_msg(ctx, player.clone(), msg.channel_id).await?; + let mut player = player.lock().await; + player.set_now_playing(np_msg).await; } handle_autodelete(ctx, msg).await?; diff --git a/src/commands/music/join.rs b/src/commands/music/join.rs index 22b9d37..717acac 100644 --- a/src/commands/music/join.rs +++ b/src/commands/music/join.rs @@ -4,7 +4,8 @@ use serenity::framework::standard::{Args, CommandResult}; use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; -use crate::commands::music::{get_channel_for_author, is_dj, join_channel}; +use crate::commands::music::{get_channel_for_author, is_dj}; +use crate::providers::music::player::MusicPlayer; use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::ephemeral_message::EphemeralMessage; use serenity::model::id::ChannelId; @@ -34,7 +35,7 @@ async fn join(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { ) }; log::debug!("Joining channel {} for guild {}", channel_id, guild.id); - join_channel(ctx, channel_id, guild.id).await; + MusicPlayer::join(ctx, guild.id, channel_id).await?; EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { m.content("🎤 Joined the Voice Channel") }) diff --git a/src/commands/music/leave.rs b/src/commands/music/leave.rs index bd252e1..ba601c2 100644 --- a/src/commands/music/leave.rs +++ b/src/commands/music/leave.rs @@ -4,8 +4,8 @@ use serenity::framework::standard::CommandResult; use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; -use crate::commands::music::{get_voice_manager, DJ_CHECK}; -use crate::providers::music::lavalink::Lavalink; +use crate::commands::music::DJ_CHECK; +use crate::utils::context_data::MusicPlayers; use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::ephemeral_message::EphemeralMessage; @@ -20,31 +20,21 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult { let guild = msg.guild(&ctx.cache).await.unwrap(); log::debug!("Leave request received for guild {}", guild.id); - let manager = get_voice_manager(ctx).await; - let handler = manager.get(guild.id); - - if let Some(handler) = handler { - let mut handler_lock = handler.lock().await; - handler_lock.remove_all_global_events(); + let mut data = ctx.data.write().await; + let players = data.get_mut::().unwrap(); + match players.remove(&guild.id.0) { + None => { + EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { + m.content("‼️ I'm not in a Voice Channel") + }) + .await?; + } + Some(player) => { + let mut player = player.lock().await; + player.stop().await?; + } } - if manager.get(guild.id).is_some() { - 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") - }) - .await?; - log::debug!("Left the voice channel"); - } else { - EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { - m.content("‼️ I'm not in a Voice Channel") - }) - .await?; - log::debug!("Not in a voice channel"); - } handle_autodelete(ctx, msg).await?; Ok(()) diff --git a/src/commands/music/lyrics.rs b/src/commands/music/lyrics.rs index b07fc10..98e63d7 100644 --- a/src/commands/music/lyrics.rs +++ b/src/commands/music/lyrics.rs @@ -1,11 +1,11 @@ use serenity::client::Context; use serenity::framework::standard::macros::command; -use serenity::framework::standard::CommandResult; +use serenity::framework::standard::{CommandError, CommandResult}; use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; -use crate::commands::music::get_queue_for_guild; -use crate::providers::music::lyrics::get_lyrics; +use crate::commands::music::get_music_player_for_guild; +use crate::messages::music::no_voicechannel::create_no_voicechannel_message; #[command] #[only_in(guilds)] @@ -16,39 +16,40 @@ async fn lyrics(ctx: &Context, msg: &Message) -> CommandResult { let guild = msg.guild(&ctx.cache).await.unwrap(); log::debug!("Fetching lyrics for song playing in {}", 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(song) = queue_lock.current() { - log::debug!("Playing music. Fetching lyrics for currently playing song..."); - let title = song.title().clone(); - let author = song.author().clone(); + let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await { + player + } else { + return create_no_voicechannel_message(&ctx.http, msg.channel_id) + .await + .map_err(CommandError::from); + }; - if let Some(lyrics) = get_lyrics(&*author, &*title).await? { - log::trace!("Lyrics for '{}' are {}", title, lyrics); + let (lyrics, current) = { + let mut player = player.lock().await; + let current = player.queue().current().clone(); + (player.lyrics().await?, current) + }; - msg.channel_id - .send_message(ctx, |m| { - m.embed(|e| { - e.title(format!("Lyrics for {} by {}", title, author)) - .description(lyrics) - .footer(|f| f.text("Powered by lyricsovh")) - }) - }) - .await?; - } else { - log::debug!("No lyrics found"); - msg.channel_id.say(ctx, "No lyrics found").await?; - } - } else { + if let Some(lyrics) = lyrics { + let current = current.unwrap(); msg.channel_id - .say(ctx, "I'm not playing music right now") + .send_message(ctx, |m| { + m.embed(|e| { + e.title(format!( + "Lyrics for {} by {}", + current.title(), + current.author() + )) + .description(lyrics) + .footer(|f| f.text("Powered by lyricsovh")) + }) + }) .await?; + } else { + log::debug!("No lyrics found"); + msg.channel_id.say(ctx, "No lyrics found").await?; } + handle_autodelete(ctx, msg).await?; Ok(()) diff --git a/src/commands/music/mod.rs b/src/commands/music/mod.rs index fad8b7a..3ff6b3e 100644 --- a/src/commands/music/mod.rs +++ b/src/commands/music/mod.rs @@ -1,21 +1,14 @@ -use std::mem; -use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering}; use std::sync::Arc; -use std::time::Duration; use aspotify::Track; use regex::Regex; -use serenity::async_trait; use serenity::client::Context; use serenity::framework::standard::macros::{check, group}; -use serenity::http::Http; use serenity::model::channel::Message; use serenity::model::guild::Guild; use serenity::model::id::{ChannelId, GuildId, UserId}; use serenity::model::user::User; -use songbird::{ - Call, Event, EventContext, EventHandler as VoiceEventHandler, Songbird, TrackEvent, -}; +use songbird::Songbird; use tokio::sync::Mutex; use clear_queue::CLEAR_QUEUE_COMMAND; @@ -34,19 +27,16 @@ use save_playlist::SAVE_PLAYLIST_COMMAND; 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::player::MusicPlayer; +use crate::providers::music::queue::Song; use crate::providers::music::{add_youtube_song_to_database, youtube_dl}; use crate::providers::settings::{get_setting, Setting}; -use crate::utils::context_data::{DatabaseContainer, Store}; +use crate::utils::context_data::{DatabaseContainer, MusicPlayers, Store}; 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; @@ -85,131 +75,12 @@ mod skip; )] pub struct Music; -struct SongEndNotifier { - channel_id: ChannelId, - guild_id: GuildId, - http: Arc, - queue: 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); - 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; - } - - None - } -} - -struct ChannelDurationNotifier { - channel_id: ChannelId, - guild_id: GuildId, - count: Arc, - queue: Arc>, - leave_in: Arc, - handler: Arc>, - manager: Arc, -} - -#[async_trait] -impl VoiceEventHandler for ChannelDurationNotifier { - async fn act(&self, _ctx: &EventContext<'_>) -> Option { - let count_before = self.count.fetch_add(1, Ordering::Relaxed); - log::debug!( - "Playing in channel {} for {} minutes", - self.channel_id, - count_before - ); - let queue_lock = self.queue.lock().await; - if queue_lock.leave_flag { - log::debug!("Waiting to leave"); - if self.leave_in.fetch_sub(1, Ordering::Relaxed) <= 0 { - log::debug!("Leaving voice channel"); - { - let mut handler_lock = self.handler.lock().await; - handler_lock.remove_all_global_events(); - } - let _ = self.manager.remove(self.guild_id).await; - log::debug!("Left the voice channel"); - } - } else { - log::debug!("Resetting leave value"); - self.leave_in.store(5, Ordering::Relaxed) - } - - None - } -} - -/// Joins a voice channel -async fn join_channel(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) -> Arc> { - log::debug!( - "Attempting to join channel {} in guild {}", - channel_id, - guild_id - ); - let manager = songbird::get(ctx) +/// Returns the voice manager from the context +pub async fn get_voice_manager(ctx: &Context) -> Arc { + songbird::get(ctx) .await .expect("Songbird Voice client placed in at initialisation.") - .clone(); - - 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(channel_id))); - - store.music_queues.insert(guild_id, queue.clone()); - { - let mut handler_lock = handler.lock().await; - - log::debug!("Registering track end handler"); - handler_lock.add_global_event( - Event::Track(TrackEvent::End), - SongEndNotifier { - channel_id, - guild_id, - http: ctx.http.clone(), - queue: Arc::clone(&queue), - data: ctx.data.clone(), - }, - ); - - handler_lock.add_global_event( - Event::Periodic(Duration::from_secs(60), None), - ChannelDurationNotifier { - channel_id, - guild_id, - count: Default::default(), - queue: Arc::clone(&queue), - handler: handler.clone(), - leave_in: Arc::new(AtomicIsize::new(5)), - manager: manager.clone(), - }, - ); - } - - handler + .clone() } /// Returns the voice channel the author is in @@ -221,93 +92,15 @@ fn get_channel_for_author(author_id: &UserId, guild: &Guild) -> BotResult Arc { - songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone() -} - -/// Returns a reference to a guilds music queue -pub(crate) async fn get_queue_for_guild( +/// Returns the music player for a given guild +pub async fn get_music_player_for_guild( ctx: &Context, - guild_id: &GuildId, -) -> BotResult>> { + guild_id: GuildId, +) -> Option>> { let data = ctx.data.read().await; - let store = data.get::().unwrap(); - - let queue = store - .music_queues - .get(guild_id) - .ok_or(BotError::from("I'm not in a Voice Channel"))? - .clone(); - Ok(queue) -} + let players = data.get::().unwrap(); -/// Plays the next song in the queue -pub async fn play_next_in_queue( - http: &Arc, - channel_id: &ChannelId, - guild_id: &GuildId, - queue: &Arc>, - player: &LavalinkClient, -) -> bool { - let mut queue_lock = queue.lock().await; - - if let Some(mut next) = queue_lock.next() { - let url = match next.url().await { - Some(url) => url, - None => { - let _ = channel_id - .say(&http, format!("'{}' not found", next.title())) - .await; - return false; - } - }; - log::debug!("Getting source for song '{}'", url); - - let query_information = match player.auto_search_tracks(url).await { - Ok(s) => s, - Err(e) => { - let _ = channel_id - .say( - &http, - format!("Failed to enqueue {}: {:?}", next.title(), e), - ) - .await; - return false; - } - }; - 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 _ = 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, &mut next, queue_lock.paused()).await { - log::error!("Failed to update now playing message: {:?}", e); - } - } - queue_lock.set_current(next); - } else { - if let Some(np) = mem::take(&mut queue_lock.now_playing_msg) { - let np = np.read().await; - if let Ok(message) = np.get_message(http).await { - let _ = message.delete(http).await; - } - } - queue_lock.clear_current(); - } - true + players.get(&guild_id.0).cloned() } /// Returns the list of songs for a given url diff --git a/src/commands/music/move_song.rs b/src/commands/music/move_song.rs index 7472ec8..434ec50 100644 --- a/src/commands/music/move_song.rs +++ b/src/commands/music/move_song.rs @@ -1,10 +1,11 @@ use crate::commands::common::handle_autodelete; -use crate::commands::music::{get_queue_for_guild, DJ_CHECK}; +use crate::commands::music::{get_music_player_for_guild, DJ_CHECK}; +use crate::messages::music::no_voicechannel::create_no_voicechannel_message; use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::ephemeral_message::EphemeralMessage; use serenity::client::Context; use serenity::framework::standard::macros::command; -use serenity::framework::standard::{Args, CommandResult}; +use serenity::framework::standard::{Args, CommandError, CommandResult}; use serenity::model::channel::Message; #[command] @@ -22,15 +23,17 @@ async fn move_song(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul let pos1 = args.single::()?; let pos2 = args.single::()?; + let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await { + player + } else { + return create_no_voicechannel_message(&ctx.http, msg.channel_id) + .await + .map_err(CommandError::from); + }; { - let queue = forward_error!( - ctx, - msg.channel_id, - get_queue_for_guild(ctx, &guild.id).await - ); - let mut queue_lock = queue.lock().await; - queue_lock.move_position(pos1, pos2); + let mut player = player.lock().await; + player.queue().move_position(pos1, pos2); } EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { m.content(format!( diff --git a/src/commands/music/pause.rs b/src/commands/music/pause.rs index 33deacf..99862c4 100644 --- a/src/commands/music/pause.rs +++ b/src/commands/music/pause.rs @@ -1,12 +1,11 @@ use serenity::framework::standard::macros::command; -use serenity::framework::standard::CommandResult; +use serenity::framework::standard::{CommandError, CommandResult}; use serenity::model::channel::Message; 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 crate::commands::music::{get_music_player_for_guild, DJ_CHECK}; +use crate::messages::music::no_voicechannel::create_no_voicechannel_message; use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::ephemeral_message::EphemeralMessage; @@ -20,39 +19,33 @@ async fn pause(ctx: &Context, msg: &Message) -> CommandResult { let guild = msg.guild(&ctx.cache).await.unwrap(); log::debug!("Pausing playback for guild {}", guild.id); - let queue = forward_error!( - ctx, - msg.channel_id, - get_queue_for_guild(ctx, &guild.id).await - ); - let mut queue_lock = queue.lock().await; + let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await { + player + } else { + return create_no_voicechannel_message(&ctx.http, msg.channel_id) + .await + .map_err(CommandError::from); + }; + let mut player = player.lock().await; + + if let Some(_) = player.queue().current() { + player.toggle_paused().await?; + let is_paused = player.is_paused(); - if let Some(_) = queue_lock.current() { - 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(song)) = (&queue_lock.now_playing_msg, queue_lock.current()) { - update_now_playing_msg(&ctx.http, menu, &mut song.clone(), true).await?; - } + player.update_now_playing().await?; } else { log::debug!("Resumed"); EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { m.content("▶ Resumed playback️") }) .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?; - } + player.update_now_playing().await?; } } else { msg.channel_id.say(ctx, "Nothing to pause").await?; diff --git a/src/commands/music/play.rs b/src/commands/music/play.rs index 10d274e..5f2ec75 100644 --- a/src/commands/music/play.rs +++ b/src/commands/music/play.rs @@ -5,12 +5,12 @@ use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; use crate::commands::music::{ - get_channel_for_author, get_queue_for_guild, get_songs_for_query, get_voice_manager, - join_channel, play_next_in_queue, + get_channel_for_author, get_music_player_for_guild, get_songs_for_query, }; use crate::messages::music::now_playing::create_now_playing_msg; -use crate::providers::music::lavalink::Lavalink; +use crate::providers::music::player::MusicPlayer; use crate::providers::settings::{get_setting, Setting}; +use std::sync::Arc; #[command] #[only_in(guilds)] @@ -25,25 +25,22 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let guild = msg.guild(&ctx.cache).await.unwrap(); log::debug!("Play request received for guild {}", guild.id); - let manager = get_voice_manager(ctx).await; - let handler = manager.get(guild.id); + let mut player = get_music_player_for_guild(ctx, guild.id).await; - if handler.is_none() { + if player.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)?; - join_channel(ctx, channel_id, guild.id).await; + let music_player = MusicPlayer::join(ctx, guild.id, channel_id).await?; + player = Some(music_player); } - + let player = player.unwrap(); let songs = get_songs_for_query(&ctx, msg, query).await?; - let queue = get_queue_for_guild(ctx, &guild.id).await?; - let (play_first, create_now_playing) = { log::debug!("Adding song to queue"); - let mut queue_lock = queue.lock().await; + let mut player_lock = player.lock().await; for song in songs { - queue_lock.add(song); + player_lock.queue().add(song); } let autoshuffle = get_setting(ctx, guild.id, Setting::MusicAutoShuffle) .await? @@ -51,26 +48,23 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult { if autoshuffle { log::debug!("Autoshuffeling"); - queue_lock.shuffle(); + player_lock.queue().shuffle(); } ( - queue_lock.current().is_none(), - queue_lock.now_playing_msg.is_none(), + player_lock.queue().current().is_none(), + player_lock.now_playing_message().is_none(), ) }; if play_first { log::debug!("Playing first song in queue"); - 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 - { - } + let mut player_lock = player.lock().await; + player_lock.play_next().await?; } if create_now_playing { - let handle = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?; - let mut queue_lock = queue.lock().await; - queue_lock.now_playing_msg = Some(handle); + let handle = create_now_playing_msg(ctx, Arc::clone(&player), msg.channel_id).await?; + let mut player_lock = player.lock().await; + player_lock.set_now_playing(handle).await; } handle_autodelete(ctx, msg).await?; diff --git a/src/commands/music/play_next.rs b/src/commands/music/play_next.rs index d668241..e738dd3 100644 --- a/src/commands/music/play_next.rs +++ b/src/commands/music/play_next.rs @@ -5,11 +5,11 @@ use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; use crate::commands::music::{ - get_channel_for_author, get_queue_for_guild, get_songs_for_query, get_voice_manager, - join_channel, play_next_in_queue, DJ_CHECK, + get_channel_for_author, get_music_player_for_guild, get_songs_for_query, DJ_CHECK, }; use crate::messages::music::now_playing::create_now_playing_msg; -use crate::providers::music::lavalink::Lavalink; +use crate::providers::music::player::MusicPlayer; +use std::sync::Arc; #[command] #[only_in(guilds)] @@ -24,42 +24,41 @@ 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 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 mut player = get_music_player_for_guild(ctx, guild.id).await; + + if player.is_none() { + log::debug!("Not in a channel. Joining authors channel..."); let channel_id = get_channel_for_author(&msg.author.id, &guild)?; - join_channel(ctx, channel_id, guild.id).await; + let music_player = MusicPlayer::join(ctx, guild.id, channel_id).await?; + player = Some(music_player); } + let player = player.unwrap(); let mut songs = get_songs_for_query(&ctx, msg, query).await?; - let queue = get_queue_for_guild(ctx, &guild.id).await?; let (play_first, create_now_playing) = { - let mut queue_lock = queue.lock().await; + let mut player_lock = player.lock().await; songs.reverse(); log::debug!("Enqueueing songs as next songs in the queue"); for song in songs { - queue_lock.add_next(song); + player_lock.queue().add_next(song); } ( - queue_lock.current().is_none(), - queue_lock.now_playing_msg.is_none(), + player_lock.queue().current().is_none(), + player_lock.now_playing_message().is_none(), ) }; if play_first { - 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 {} + let mut player_lock = player.lock().await; + player_lock.play_next().await?; } if create_now_playing { - let handle = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?; - let mut queue_lock = queue.lock().await; - queue_lock.now_playing_msg = Some(handle); + let handle = create_now_playing_msg(ctx, Arc::clone(&player), msg.channel_id).await?; + let mut player_lock = player.lock().await; + player_lock.set_now_playing(handle).await; } handle_autodelete(ctx, msg).await?; diff --git a/src/commands/music/queue.rs b/src/commands/music/queue.rs index cb7ba95..2e611a2 100644 --- a/src/commands/music/queue.rs +++ b/src/commands/music/queue.rs @@ -1,10 +1,11 @@ use serenity::client::Context; use serenity::framework::standard::macros::command; -use serenity::framework::standard::{Args, CommandResult}; +use serenity::framework::standard::{Args, CommandError, CommandResult}; use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; -use crate::commands::music::get_queue_for_guild; +use crate::commands::music::get_music_player_for_guild; +use crate::messages::music::no_voicechannel::create_no_voicechannel_message; use crate::messages::music::queue::create_queue_menu; use crate::providers::music::queue::Song; @@ -23,13 +24,16 @@ async fn queue(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { .map(|s| s.unwrap().to_lowercase()) .collect::>(); - let queue = forward_error!( - ctx, - msg.channel_id, - get_queue_for_guild(ctx, &guild.id).await - ); - let queue_lock = queue.lock().await; - let songs: Vec<(usize, Song)> = queue_lock + let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await { + player + } else { + return create_no_voicechannel_message(&ctx.http, msg.channel_id) + .await + .map_err(CommandError::from); + }; + let mut player = player.lock().await; + let songs: Vec<(usize, Song)> = player + .queue() .entries() .into_iter() .enumerate() diff --git a/src/commands/music/remove_song.rs b/src/commands/music/remove_song.rs index a244921..7537fc5 100644 --- a/src/commands/music/remove_song.rs +++ b/src/commands/music/remove_song.rs @@ -1,10 +1,11 @@ use crate::commands::common::handle_autodelete; -use crate::commands::music::{get_queue_for_guild, DJ_CHECK}; +use crate::commands::music::{get_music_player_for_guild, DJ_CHECK}; +use crate::messages::music::no_voicechannel::create_no_voicechannel_message; use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::ephemeral_message::EphemeralMessage; use serenity::client::Context; use serenity::framework::standard::macros::command; -use serenity::framework::standard::{Args, CommandResult}; +use serenity::framework::standard::{Args, CommandError, CommandResult}; use serenity::model::channel::Message; #[command] @@ -21,15 +22,17 @@ async fn remove_song(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes log::debug!("Moving song for guild {}", guild.id); let pos = args.single::()?; + let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await { + player + } else { + return create_no_voicechannel_message(&ctx.http, msg.channel_id) + .await + .map_err(CommandError::from); + }; { - let queue = forward_error!( - ctx, - msg.channel_id, - get_queue_for_guild(ctx, &guild.id).await - ); - let mut queue_lock = queue.lock().await; - queue_lock.remove(pos); + let mut player = player.lock().await; + player.queue().remove(pos); } EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { diff --git a/src/commands/music/shuffle.rs b/src/commands/music/shuffle.rs index 115e3ea..5980cca 100644 --- a/src/commands/music/shuffle.rs +++ b/src/commands/music/shuffle.rs @@ -1,10 +1,11 @@ use serenity::client::Context; use serenity::framework::standard::macros::command; -use serenity::framework::standard::CommandResult; +use serenity::framework::standard::{CommandError, 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::{get_music_player_for_guild, DJ_CHECK}; +use crate::messages::music::no_voicechannel::create_no_voicechannel_message; use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::ephemeral_message::EphemeralMessage; @@ -19,14 +20,16 @@ async fn shuffle(ctx: &Context, msg: &Message) -> CommandResult { let guild = msg.guild(&ctx.cache).await.unwrap(); log::debug!("Shuffling queue for guild {}", guild.id); - let queue = forward_error!( - ctx, - msg.channel_id, - get_queue_for_guild(ctx, &guild.id).await - ); + let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await { + player + } else { + return create_no_voicechannel_message(&ctx.http, msg.channel_id) + .await + .map_err(CommandError::from); + }; { - let mut queue_lock = queue.lock().await; - queue_lock.shuffle(); + let mut player = player.lock().await; + player.queue().shuffle(); } EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { diff --git a/src/commands/music/skip.rs b/src/commands/music/skip.rs index 987abdb..f16333d 100644 --- a/src/commands/music/skip.rs +++ b/src/commands/music/skip.rs @@ -1,11 +1,11 @@ use serenity::client::Context; use serenity::framework::standard::macros::command; -use serenity::framework::standard::CommandResult; +use serenity::framework::standard::{CommandError, CommandResult}; use serenity::model::channel::Message; use crate::commands::common::handle_autodelete; -use crate::commands::music::DJ_CHECK; -use crate::providers::music::lavalink::Lavalink; +use crate::commands::music::{get_music_player_for_guild, DJ_CHECK}; +use crate::messages::music::no_voicechannel::create_no_voicechannel_message; use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::ephemeral_message::EphemeralMessage; @@ -20,10 +20,16 @@ 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 player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await { + player + } else { + return create_no_voicechannel_message(&ctx.http, msg.channel_id) + .await + .map_err(CommandError::from); + }; { - let data = ctx.data.read().await; - let player = data.get::().unwrap(); - player.stop(guild.id.0).await?; + let mut player = player.lock().await; + player.skip().await?; } EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { diff --git a/src/handler.rs b/src/handler.rs index 5bd3da4..99fa0e7 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -8,7 +8,7 @@ use serenity::model::id::{ChannelId, GuildId, MessageId}; use serenity::model::voice::VoiceState; use serenity::prelude::*; -use crate::commands::music::get_queue_for_guild; +use crate::commands::music::get_music_player_for_guild; use crate::utils::delete_messages_from_database; use bot_serenityutils::menu::{ handle_message_delete, handle_message_delete_bulk, handle_reaction_add, handle_reaction_remove, @@ -120,10 +120,10 @@ impl EventHandler for Handler { if let Some(count) = member_count { log::debug!("{} Members in channel", count); - if let Ok(queue) = get_queue_for_guild(&ctx, &guild_id).await { - let mut queue_lock = queue.lock().await; + if let Some(player) = get_music_player_for_guild(&ctx, guild_id).await { + let mut player = player.lock().await; log::debug!("Setting leave flag to {}", count == 0); - queue_lock.leave_flag = count == 0; + player.set_leave_flag(count == 0); } } } diff --git a/src/messages/music/mod.rs b/src/messages/music/mod.rs index 10bc688..0a2d747 100644 --- a/src/messages/music/mod.rs +++ b/src/messages/music/mod.rs @@ -1,2 +1,3 @@ +pub mod no_voicechannel; pub mod now_playing; pub mod queue; diff --git a/src/messages/music/no_voicechannel.rs b/src/messages/music/no_voicechannel.rs new file mode 100644 index 0000000..9616cd9 --- /dev/null +++ b/src/messages/music/no_voicechannel.rs @@ -0,0 +1,19 @@ +use crate::utils::error::BotResult; +use bot_serenityutils::core::SHORT_TIMEOUT; +use bot_serenityutils::ephemeral_message::EphemeralMessage; +use serenity::http::Http; +use serenity::model::prelude::ChannelId; +use std::sync::Arc; + +/// Creates a not in a voicechannel message +pub async fn create_no_voicechannel_message( + http: &Arc, + channel_id: ChannelId, +) -> BotResult<()> { + EphemeralMessage::create(http, channel_id, SHORT_TIMEOUT, |m| { + m.content("‼️ I'm not in a Voice Channel") + }) + .await?; + + Ok(()) +} diff --git a/src/messages/music/now_playing.rs b/src/messages/music/now_playing.rs index 67489ac..546c895 100644 --- a/src/messages/music/now_playing.rs +++ b/src/messages/music/now_playing.rs @@ -4,11 +4,11 @@ use serenity::builder::CreateEmbed; use serenity::http::Http; use serenity::model::prelude::ChannelId; -use crate::commands::music::{get_queue_for_guild, get_voice_manager, is_dj}; +use crate::commands::music::{get_music_player_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::lavalink::Lavalink; -use crate::providers::music::queue::{MusicQueue, Song}; +use crate::providers::music::player::MusicPlayer; +use crate::providers::music::queue::Song; use crate::utils::context_data::{DatabaseContainer, Store}; use crate::utils::error::*; use bot_serenityutils::core::MessageHandle; @@ -30,7 +30,7 @@ static GOOD_PICK_BUTTON: &str = "👍"; /// Creates a new now playing message and returns the embed for that message pub async fn create_now_playing_msg( ctx: &Context, - queue: Arc>, + player: Arc>, channel_id: ChannelId, ) -> BotResult>> { log::debug!("Creating now playing menu"); @@ -61,16 +61,17 @@ pub async fn create_now_playing_msg( ) .show_help() .add_page(Page::new_builder(move || { - let queue = Arc::clone(&queue); + let player = Arc::clone(&player); Box::pin(async move { log::debug!("Creating now playing embed for page"); - let queue = queue.lock().await; - log::debug!("Queue locked"); + let mut player = player.lock().await; + log::debug!("player locked"); let mut page = CreateMessage::default(); - if let Some(mut current) = queue.current().clone() { + if let Some(mut current) = player.queue().current().clone() { let mut embed = CreateEmbed::default(); - create_now_playing_embed(&mut current, &mut embed, queue.paused(), nsfw).await; + create_now_playing_embed(&mut current, &mut embed, player.is_paused(), nsfw) + .await; page.embed(|e| { e.0.clone_from(&embed.0); e @@ -166,25 +167,16 @@ async fn play_pause_button_action( return Ok(()); } { - let queue = get_queue_for_guild(ctx, &guild_id).await?; + let player = get_music_player_for_guild(ctx, guild_id).await.unwrap(); let (current, message, paused) = { log::debug!("Queue is locked"); - let mut queue = queue.lock().await; - { - 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); - } + let mut player = player.lock().await; + player.toggle_paused().await?; ( - queue.current().clone(), - queue.now_playing_msg.clone().unwrap(), - queue.paused(), + player.queue().current().clone(), + player.now_playing_message().clone().unwrap(), + player.is_paused(), ) }; log::debug!("Queue is unlocked"); @@ -211,9 +203,9 @@ async fn skip_button_action( } { - let data = ctx.data.read().await; - let player = data.get::().unwrap(); - player.stop(guild_id.0).await.map_err(BotError::from)?; + let player = get_music_player_for_guild(ctx, guild_id).await.unwrap(); + let mut player = player.lock().await; + player.skip().await?; } Ok(()) @@ -243,9 +235,9 @@ async fn stop_button_action( 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)?; + let player = get_music_player_for_guild(ctx, guild_id).await.unwrap(); + let mut player = player.lock().await; + player.stop().await?; log::debug!("Left the voice channel"); } else { log::debug!("Not in a voice channel"); @@ -268,10 +260,10 @@ async fn good_pick_action( reaction: Reaction, ) -> SerenityUtilsResult<()> { let guild_id = reaction.guild_id.unwrap(); - let queue = get_queue_for_guild(ctx, &guild_id).await?; - let queue = queue.lock().await; + let player = get_music_player_for_guild(ctx, guild_id).await.unwrap(); + let mut player = player.lock().await; - if let Some(song) = queue.current() { + if let Some(song) = player.queue().current() { let data = ctx.data.read().await; let store = data.get::().unwrap(); let database = data.get::().unwrap(); @@ -292,9 +284,9 @@ async fn delete_action( handle.clone() }; { - let queue = get_queue_for_guild(ctx, &guild_id).await?; - let mut queue = queue.lock().await; - queue.now_playing_msg = None; + let player = get_music_player_for_guild(ctx, guild_id).await.unwrap(); + let mut player = player.lock().await; + player.clear_now_playing(); } ctx.http .delete_message(handle.channel_id, handle.message_id) diff --git a/src/providers/music/lavalink.rs b/src/providers/music/lavalink.rs index 3cbf50e..ea10954 100644 --- a/src/providers/music/lavalink.rs +++ b/src/providers/music/lavalink.rs @@ -1,11 +1,8 @@ -use crate::commands::music::play_next_in_queue; -use crate::utils::context_data::Store; +use crate::utils::context_data::MusicPlayers; 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; @@ -13,7 +10,6 @@ use typemap_rev::TypeMap; pub struct LavalinkHandler { pub data: Arc>, - pub http: Arc, } #[async_trait] @@ -21,36 +17,28 @@ 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) { + async fn track_finish(&self, _: LavalinkClient, event: TrackFinish) { log::info!("Track finished!\nGuild: {}", event.guild_id); - let queue = { + let player = { let data = self.data.read().await; - let store = data.get::().unwrap(); + let players = data.get::().unwrap(); - store - .music_queues - .get(&GuildId(event.guild_id)) - .unwrap() - .clone() + players.get(&event.guild_id).cloned() }; - 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 - {} + if let Some(player) = player { + let mut player = player.lock().await; + if let Err(e) = player.play_next().await { + log::error!("Failed to play next song: {:?}", e); + } + if let Err(e) = player.update_now_playing().await { + log::error!("Failed to update now playing embed: {:?}", e); + } + } } } pub struct Lavalink; impl TypeMapKey for Lavalink { - type Value = LavalinkClient; + type Value = Arc; } diff --git a/src/providers/music/mod.rs b/src/providers/music/mod.rs index 00991a6..13d07d6 100644 --- a/src/providers/music/mod.rs +++ b/src/providers/music/mod.rs @@ -7,12 +7,13 @@ 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; -pub(crate) mod spotify; -pub(crate) mod youtube_dl; +pub mod lavalink; +pub mod lyrics; +pub mod player; +pub mod queue; +pub mod responses; +pub mod spotify; +pub mod youtube_dl; /// Searches for a youtube video for the specified song pub(crate) async fn song_to_youtube_video(song: &Song) -> BotResult> { diff --git a/src/providers/music/player.rs b/src/providers/music/player.rs new file mode 100644 index 0000000..a68b5fc --- /dev/null +++ b/src/providers/music/player.rs @@ -0,0 +1,181 @@ +use crate::messages::music::now_playing::update_now_playing_msg; +use crate::providers::music::lavalink::Lavalink; +use crate::providers::music::lyrics::get_lyrics; +use crate::providers::music::queue::MusicQueue; +use crate::utils::context_data::MusicPlayers; +use crate::utils::error::BotResult; +use bot_serenityutils::core::MessageHandle; +use lavalink_rs::LavalinkClient; +use serenity::client::Context; +use serenity::http::Http; +use serenity::model::id::{ChannelId, GuildId}; +use std::mem; +use std::sync::Arc; +use tokio::sync::{Mutex, RwLock}; + +pub struct MusicPlayer { + client: Arc, + http: Arc, + queue: MusicQueue, + guild_id: GuildId, + now_playing_msg: Option>>, + leave_flag: bool, + paused: bool, +} + +impl MusicPlayer { + /// Creates a new music player + pub fn new(client: Arc, http: Arc, guild_id: GuildId) -> Self { + Self { + client, + http, + guild_id, + queue: MusicQueue::new(), + now_playing_msg: None, + leave_flag: false, + paused: false, + } + } + + /// Joins a given voice channel + pub async fn join( + ctx: &Context, + guild_id: GuildId, + voice_channel_id: ChannelId, + ) -> BotResult>> { + let manager = songbird::get(ctx).await.unwrap(); + let (_, connection) = manager.join_gateway(guild_id, voice_channel_id).await; + let connection = connection?; + + let player = { + let mut data = ctx.data.write().await; + let client = data.get::().unwrap(); + client.create_session(&connection).await?; + let player = MusicPlayer::new(Arc::clone(client), Arc::clone(&ctx.http), guild_id); + let player = Arc::new(Mutex::new(player)); + let players = data.get_mut::().unwrap(); + players.insert(guild_id.0, Arc::clone(&player)); + player + }; + + Ok(player) + } + + /// Returns a mutable reference to the inner queue + pub fn queue(&mut self) -> &mut MusicQueue { + &mut self.queue + } + + /// Skips to the next song + pub async fn skip(&mut self) -> BotResult<()> { + self.client.stop(self.guild_id.0).await?; + + Ok(()) + } + + /// Stops playback and leaves the channel + pub async fn stop(&mut self) -> BotResult<()> { + self.queue.clear(); + self.client.stop(self.guild_id.0).await?; + Ok(()) + } + + /// Returns the lyrics for the currently playing song + pub async fn lyrics(&self) -> BotResult> { + if let Some(current) = self.queue.current() { + let title = current.title(); + let artist = current.author(); + get_lyrics(artist, title).await + } else { + Ok(None) + } + } + + /// Plays the next song in the queue + pub async fn play_next(&mut self) -> BotResult<()> { + while !self.try_play_next().await? {} + + Ok(()) + } + + /// Tries to play the next song + pub async fn try_play_next(&mut self) -> BotResult { + let mut next = if let Some(n) = self.queue.next() { + n + } else { + return Ok(true); + }; + let url = if let Some(url) = next.url().await { + url + } else { + return Ok(false); + }; + let query_information = match self.client.auto_search_tracks(url).await { + Ok(i) => i, + Err(e) => { + log::error!("Failed to search for song: {}", e); + return Ok(false); + } + }; + + let track = query_information.tracks[0].clone(); + self.client.play(self.guild_id.0, track).start().await?; + self.queue.set_current(next); + + Ok(true) + } + + /// Sets the new now playing message of the queue + pub async fn set_now_playing(&mut self, message: Arc>) { + let _ = self.delete_now_playing().await; + self.now_playing_msg = Some(message) + } + + /// Updates the now playing message + pub async fn update_now_playing(&self) -> BotResult<()> { + if let (Some(current), Some(np)) = (self.queue.current(), &self.now_playing_msg) { + update_now_playing_msg(&self.http, np, &mut current.clone(), self.is_paused()).await?; + } + + Ok(()) + } + + /// Deletes the now playing message + pub async fn delete_now_playing(&mut self) -> BotResult<()> { + if let Some(np) = mem::take(&mut self.now_playing_msg) { + let np = np.read().await; + let msg = np.get_message(&self.http).await?; + msg.delete(&self.http).await?; + } + + Ok(()) + } + + /// Pauses playback + pub async fn toggle_paused(&mut self) -> BotResult<()> { + self.paused = !self.paused; + self.client.set_pause(self.guild_id.0, self.paused).await?; + + Ok(()) + } + + /// Returns if playback is paused + pub fn is_paused(&self) -> bool { + self.paused + } + + /// Returns the now playing message of the player + pub fn now_playing_message(&self) -> &Option>> { + &self.now_playing_msg + } + + /// Deletes the now playing message from the player + pub fn clear_now_playing(&mut self) { + self.now_playing_msg = None; + } + + /// Sets the leave flag to the given value + pub fn set_leave_flag(&mut self, flag: bool) { + self.leave_flag = flag; + } +} diff --git a/src/providers/music/queue.rs b/src/providers/music/queue.rs index 382238c..cd9f375 100644 --- a/src/providers/music/queue.rs +++ b/src/providers/music/queue.rs @@ -7,30 +7,20 @@ use bot_coreutils::shuffle::Shuffle; 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, - paused: bool, - pub now_playing_msg: Option>>, pub leave_flag: bool, - channel_id: ChannelId, } impl MusicQueue { - pub fn new(channel_id: ChannelId) -> Self { + pub fn new() -> Self { Self { inner: VecDeque::new(), current: None, - paused: false, leave_flag: false, - now_playing_msg: None, - channel_id, } } @@ -64,26 +54,11 @@ impl MusicQueue { self.current = Some(song) } - /// Clears the currently playing song - pub fn clear_current(&mut self) { - self.current = None; - } - /// Returns the reference to the currently playing 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(); @@ -100,11 +75,6 @@ impl MusicQueue { pub fn remove(&mut self, index: usize) { self.inner.remove(index); } - - /// The channel id where the music messages should be sent to - pub fn channel_id(&self) -> ChannelId { - self.channel_id - } } #[derive(Clone, Debug)] diff --git a/src/utils/context_data.rs b/src/utils/context_data.rs index c2c4751..381304b 100644 --- a/src/utils/context_data.rs +++ b/src/utils/context_data.rs @@ -5,18 +5,16 @@ use std::sync::Arc; use bot_database::Database; use sauce_api::prelude::SauceNao; use serenity::client::Context; -use serenity::model::id::GuildId; use serenity::prelude::TypeMapKey; use tokio::sync::Mutex; -use crate::providers::music::queue::MusicQueue; +use crate::providers::music::player::MusicPlayer; use crate::providers::music::spotify::SpotifyApi; pub struct Store; pub struct StoreData { pub minecraft_data_api: minecraft_data_rs::api::Api, - pub music_queues: HashMap>>, pub spotify_api: SpotifyApi, pub sauce_nao: SauceNao, } @@ -31,7 +29,6 @@ impl StoreData { minecraft_data_api: minecraft_data_rs::api::Api::new( minecraft_data_rs::api::versions::latest_stable().unwrap(), ), - music_queues: HashMap::new(), spotify_api: SpotifyApi::new(), sauce_nao, } @@ -57,3 +54,9 @@ pub async fn get_database_from_context(ctx: &Context) -> Database { database.clone() } + +pub struct MusicPlayers; + +impl TypeMapKey for MusicPlayers { + type Value = HashMap>>; +}