Reimplement all music related functionality in MusicPlayer struct

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/25/head
trivernis 3 years ago
parent 6876a1bb1a
commit b2ba31a9e9
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -13,7 +13,9 @@ use songbird::SerenityInit;
use crate::commands::*; use crate::commands::*;
use crate::handler::Handler; use crate::handler::Handler;
use crate::providers::music::lavalink::{Lavalink, LavalinkHandler}; 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 crate::utils::error::{BotError, BotResult};
use bot_serenityutils::menu::EventDrivenMessageContainer; use bot_serenityutils::menu::EventDrivenMessageContainer;
use lavalink_rs::LavalinkClient; use lavalink_rs::LavalinkClient;
@ -35,19 +37,20 @@ pub async fn get_client() -> BotResult<Client> {
.register_songbird() .register_songbird()
.await?; .await?;
let data = client.data.clone(); let data = client.data.clone();
let http = client.cache_and_http.http.clone();
let lava_client = LavalinkClient::builder(current_application.id.0) let lava_client = LavalinkClient::builder(current_application.id.0)
.set_host(env::var("LAVALINK_HOST").unwrap_or("172.0.0.1".to_string())) .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_PORT").expect("Missing lavalink port"))
.set_password(env::var("LAVALINK_PASSWORD").expect("Missing lavalink password")) .set_password(env::var("LAVALINK_PASSWORD").expect("Missing lavalink password"))
.build(LavalinkHandler { data, http }) .build(LavalinkHandler { data })
.await?; .await?;
{ {
let mut data = client.data.write().await; let mut data = client.data.write().await;
data.insert::<Store>(StoreData::new()); data.insert::<Store>(StoreData::new());
data.insert::<DatabaseContainer>(database); data.insert::<DatabaseContainer>(database);
data.insert::<EventDrivenMessageContainer>(Arc::new(Mutex::new(HashMap::new()))); data.insert::<EventDrivenMessageContainer>(Arc::new(Mutex::new(HashMap::new())));
data.insert::<Lavalink>(lava_client); data.insert::<MusicPlayers>(HashMap::new());
data.insert::<Lavalink>(Arc::new(lava_client));
} }
Ok(client) Ok(client)

@ -9,9 +9,7 @@ use serenity::prelude::*;
use sysinfo::{ProcessExt, SystemExt}; use sysinfo::{ProcessExt, SystemExt};
use crate::commands::common::handle_autodelete; use crate::commands::common::handle_autodelete;
use crate::providers::music::queue::MusicQueue; use crate::utils::context_data::{get_database_from_context, MusicPlayers};
use crate::utils::context_data::{get_database_from_context, Store};
use std::sync::Arc;
#[command] #[command]
#[description("Shows some statistics about the bot")] #[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 /// Returns the total number of queues that are not
/// flagged to leave /// flagged to leave
async fn get_queue_count(ctx: &Context) -> usize { async fn get_queue_count(ctx: &Context) -> usize {
let queues: Vec<Arc<Mutex<MusicQueue>>> = { let data = ctx.data.read().await;
let data = ctx.data.read().await; let players = data.get::<MusicPlayers>().unwrap();
let store = data.get::<Store>().unwrap(); players.len()
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
} }

@ -1,12 +1,13 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult; use serenity::framework::standard::{CommandResult, CommandError};
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete; 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::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage; use bot_serenityutils::ephemeral_message::EphemeralMessage;
use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
#[command] #[command]
#[only_in(guilds)] #[only_in(guilds)]
@ -19,14 +20,16 @@ async fn clear_queue(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Clearing queue for guild {}", guild.id); log::debug!("Clearing queue for guild {}", guild.id);
let queue = forward_error!( let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {
ctx, player
msg.channel_id, } else {
get_queue_for_guild(ctx, &guild.id).await return create_no_voicechannel_message(&ctx.http, msg.channel_id)
); .await
.map_err(CommandError::from);
};
{ {
let mut queue_lock = queue.lock().await; let mut player = player.lock().await;
queue_lock.clear(); player.queue().clear();
} }
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {

@ -1,12 +1,11 @@
use std::mem;
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult; use serenity::framework::standard::{CommandError, CommandResult};
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete; 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; use crate::messages::music::now_playing::create_now_playing_msg;
#[command] #[command]
@ -19,27 +18,22 @@ async fn current(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Displaying current song for queue in {}", guild.id); log::debug!("Displaying current song for queue in {}", guild.id);
let queue = forward_error!( let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {
ctx, player
msg.channel_id, } else {
get_queue_for_guild(ctx, &guild.id).await return create_no_voicechannel_message(&ctx.http, msg.channel_id)
); .await
.map_err(CommandError::from);
};
let current = { let current = {
let queue_lock = queue.lock().await; let mut player = player.lock().await;
queue_lock.current().clone() player.queue().current().clone()
}; };
if let Some(_) = current { if let Some(_) = current {
let np_msg = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?; let np_msg = create_now_playing_msg(ctx, player.clone(), msg.channel_id).await?;
let mut player = player.lock().await;
let mut queue_lock = queue.lock().await; player.set_now_playing(np_msg).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;
}
}
} }
handle_autodelete(ctx, msg).await?; handle_autodelete(ctx, msg).await?;

@ -4,7 +4,8 @@ use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete; 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::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage; use bot_serenityutils::ephemeral_message::EphemeralMessage;
use serenity::model::id::ChannelId; 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); 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| { EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("🎤 Joined the Voice Channel") m.content("🎤 Joined the Voice Channel")
}) })

@ -4,8 +4,8 @@ use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete; use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_voice_manager, DJ_CHECK}; use crate::commands::music::DJ_CHECK;
use crate::providers::music::lavalink::Lavalink; use crate::utils::context_data::MusicPlayers;
use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage; 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(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Leave request received for guild {}", guild.id); log::debug!("Leave request received for guild {}", guild.id);
let manager = get_voice_manager(ctx).await; let mut data = ctx.data.write().await;
let handler = manager.get(guild.id); let players = data.get_mut::<MusicPlayers>().unwrap();
match players.remove(&guild.id.0) {
if let Some(handler) = handler { None => {
let mut handler_lock = handler.lock().await; EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
handler_lock.remove_all_global_events(); 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::<Lavalink>().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?; handle_autodelete(ctx, msg).await?;
Ok(()) Ok(())

@ -1,11 +1,11 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult; use serenity::framework::standard::{CommandError, CommandResult};
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete; 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::providers::music::lyrics::get_lyrics; use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
#[command] #[command]
#[only_in(guilds)] #[only_in(guilds)]
@ -16,39 +16,40 @@ async fn lyrics(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Fetching lyrics for song playing in {}", guild.id); log::debug!("Fetching lyrics for song playing in {}", guild.id);
let queue = forward_error!( let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {
ctx, player
msg.channel_id, } else {
get_queue_for_guild(ctx, &guild.id).await return create_no_voicechannel_message(&ctx.http, msg.channel_id)
); .await
let queue_lock = queue.lock().await; .map_err(CommandError::from);
};
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();
if let Some(lyrics) = get_lyrics(&*author, &*title).await? { let (lyrics, current) = {
log::trace!("Lyrics for '{}' are {}", title, lyrics); let mut player = player.lock().await;
let current = player.queue().current().clone();
(player.lyrics().await?, current)
};
msg.channel_id if let Some(lyrics) = lyrics {
.send_message(ctx, |m| { let current = current.unwrap();
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 {
msg.channel_id 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?; .await?;
} else {
log::debug!("No lyrics found");
msg.channel_id.say(ctx, "No lyrics found").await?;
} }
handle_autodelete(ctx, msg).await?; handle_autodelete(ctx, msg).await?;
Ok(()) Ok(())

@ -1,21 +1,14 @@
use std::mem;
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration;
use aspotify::Track; use aspotify::Track;
use regex::Regex; use regex::Regex;
use serenity::async_trait;
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::macros::{check, group}; use serenity::framework::standard::macros::{check, group};
use serenity::http::Http;
use serenity::model::channel::Message; use serenity::model::channel::Message;
use serenity::model::guild::Guild; use serenity::model::guild::Guild;
use serenity::model::id::{ChannelId, GuildId, UserId}; use serenity::model::id::{ChannelId, GuildId, UserId};
use serenity::model::user::User; use serenity::model::user::User;
use songbird::{ use songbird::Songbird;
Call, Event, EventContext, EventHandler as VoiceEventHandler, Songbird, TrackEvent,
};
use tokio::sync::Mutex; use tokio::sync::Mutex;
use clear_queue::CLEAR_QUEUE_COMMAND; use clear_queue::CLEAR_QUEUE_COMMAND;
@ -34,19 +27,16 @@ use save_playlist::SAVE_PLAYLIST_COMMAND;
use shuffle::SHUFFLE_COMMAND; use shuffle::SHUFFLE_COMMAND;
use skip::SKIP_COMMAND; use skip::SKIP_COMMAND;
use crate::messages::music::now_playing::update_now_playing_msg; use crate::providers::music::player::MusicPlayer;
use crate::providers::music::lavalink::Lavalink; use crate::providers::music::queue::Song;
use crate::providers::music::queue::{MusicQueue, Song};
use crate::providers::music::{add_youtube_song_to_database, youtube_dl}; use crate::providers::music::{add_youtube_song_to_database, youtube_dl};
use crate::providers::settings::{get_setting, Setting}; 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 crate::utils::error::{BotError, BotResult};
use bot_database::Database; use bot_database::Database;
use futures::future::BoxFuture; use futures::future::BoxFuture;
use futures::FutureExt; use futures::FutureExt;
use lavalink_rs::LavalinkClient;
use serenity::framework::standard::{Args, CommandOptions, Reason}; use serenity::framework::standard::{Args, CommandOptions, Reason};
use serenity::prelude::{RwLock, TypeMap};
use youtube_metadata::get_video_information; use youtube_metadata::get_video_information;
mod clear_queue; mod clear_queue;
@ -85,131 +75,12 @@ mod skip;
)] )]
pub struct Music; pub struct Music;
struct SongEndNotifier { /// Returns the voice manager from the context
channel_id: ChannelId, pub async fn get_voice_manager(ctx: &Context) -> Arc<Songbird> {
guild_id: GuildId, songbird::get(ctx)
http: Arc<Http>,
queue: Arc<Mutex<MusicQueue>>,
data: Arc<RwLock<TypeMap>>,
}
#[async_trait]
impl VoiceEventHandler for SongEndNotifier {
async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> {
log::debug!("Song ended in {}. Playing next one", self.channel_id);
let data = self.data.read().await;
let player = data.get::<Lavalink>().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<AtomicUsize>,
queue: Arc<Mutex<MusicQueue>>,
leave_in: Arc<AtomicIsize>,
handler: Arc<Mutex<Call>>,
manager: Arc<Songbird>,
}
#[async_trait]
impl VoiceEventHandler for ChannelDurationNotifier {
async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> {
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<Mutex<Call>> {
log::debug!(
"Attempting to join channel {} in guild {}",
channel_id,
guild_id
);
let manager = songbird::get(ctx)
.await .await
.expect("Songbird Voice client placed in at initialisation.") .expect("Songbird Voice client placed in at initialisation.")
.clone(); .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::<Lavalink>().unwrap();
lava_client
.create_session(&connection)
.await
.expect("Failed to create lava session");
let store = data.get_mut::<Store>().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
} }
/// Returns the voice channel the author is in /// Returns the voice channel the author is in
@ -221,93 +92,15 @@ fn get_channel_for_author(author_id: &UserId, guild: &Guild) -> BotResult<Channe
.ok_or(BotError::from("You're not in a Voice Channel")) .ok_or(BotError::from("You're not in a Voice Channel"))
} }
/// Returns the voice manager from the context /// Returns the music player for a given guild
pub async fn get_voice_manager(ctx: &Context) -> Arc<Songbird> { pub async fn get_music_player_for_guild(
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(
ctx: &Context, ctx: &Context,
guild_id: &GuildId, guild_id: GuildId,
) -> BotResult<Arc<Mutex<MusicQueue>>> { ) -> Option<Arc<Mutex<MusicPlayer>>> {
let data = ctx.data.read().await; let data = ctx.data.read().await;
let store = data.get::<Store>().unwrap(); let players = data.get::<MusicPlayers>().unwrap();
let queue = store
.music_queues
.get(guild_id)
.ok_or(BotError::from("I'm not in a Voice Channel"))?
.clone();
Ok(queue)
}
/// Plays the next song in the queue players.get(&guild_id.0).cloned()
pub async fn play_next_in_queue(
http: &Arc<Http>,
channel_id: &ChannelId,
guild_id: &GuildId,
queue: &Arc<Mutex<MusicQueue>>,
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
} }
/// Returns the list of songs for a given url /// Returns the list of songs for a given url

@ -1,10 +1,11 @@
use crate::commands::common::handle_autodelete; 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::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage; use bot_serenityutils::ephemeral_message::EphemeralMessage;
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::macros::command; 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 serenity::model::channel::Message;
#[command] #[command]
@ -22,15 +23,17 @@ async fn move_song(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
let pos1 = args.single::<usize>()?; let pos1 = args.single::<usize>()?;
let pos2 = args.single::<usize>()?; let pos2 = args.single::<usize>()?;
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!( let mut player = player.lock().await;
ctx, player.queue().move_position(pos1, pos2);
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let mut queue_lock = queue.lock().await;
queue_lock.move_position(pos1, pos2);
} }
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content(format!( m.content(format!(

@ -1,12 +1,11 @@
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult; use serenity::framework::standard::{CommandError, CommandResult};
use serenity::model::channel::Message; use serenity::model::channel::Message;
use serenity::prelude::*; use serenity::prelude::*;
use crate::commands::common::handle_autodelete; 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::now_playing::update_now_playing_msg; use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
use crate::providers::music::lavalink::Lavalink;
use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage; 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(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Pausing playback for guild {}", guild.id); log::debug!("Pausing playback for guild {}", guild.id);
let queue = forward_error!( let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {
ctx, player
msg.channel_id, } else {
get_queue_for_guild(ctx, &guild.id).await return create_no_voicechannel_message(&ctx.http, msg.channel_id)
); .await
let mut queue_lock = queue.lock().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::<Lavalink>().unwrap();
player.set_pause(guild.id.0, !queue_lock.paused()).await?;
!queue_lock.paused()
};
queue_lock.set_paused(is_paused);
if is_paused { if is_paused {
log::debug!("Paused"); log::debug!("Paused");
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("⏸️ Paused playback") m.content("⏸️ Paused playback")
}) })
.await?; .await?;
if let (Some(menu), Some(song)) = (&queue_lock.now_playing_msg, queue_lock.current()) { player.update_now_playing().await?;
update_now_playing_msg(&ctx.http, menu, &mut song.clone(), true).await?;
}
} else { } else {
log::debug!("Resumed"); log::debug!("Resumed");
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("▶ Resumed playback") m.content("▶ Resumed playback")
}) })
.await?; .await?;
if let (Some(menu), Some(song)) = (&queue_lock.now_playing_msg, queue_lock.current()) { player.update_now_playing().await?;
update_now_playing_msg(&ctx.http, menu, &mut song.clone(), true).await?;
}
} }
} else { } else {
msg.channel_id.say(ctx, "Nothing to pause").await?; msg.channel_id.say(ctx, "Nothing to pause").await?;

@ -5,12 +5,12 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete; use crate::commands::common::handle_autodelete;
use crate::commands::music::{ use crate::commands::music::{
get_channel_for_author, get_queue_for_guild, get_songs_for_query, get_voice_manager, get_channel_for_author, get_music_player_for_guild, get_songs_for_query,
join_channel, play_next_in_queue,
}; };
use crate::messages::music::now_playing::create_now_playing_msg; 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 crate::providers::settings::{get_setting, Setting};
use std::sync::Arc;
#[command] #[command]
#[only_in(guilds)] #[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(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Play request received for guild {}", guild.id); log::debug!("Play request received for guild {}", guild.id);
let manager = get_voice_manager(ctx).await; let mut player = get_music_player_for_guild(ctx, guild.id).await;
let handler = manager.get(guild.id);
if handler.is_none() { if player.is_none() {
log::debug!("Not in a channel. Joining authors channel..."); 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)?; 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 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 (play_first, create_now_playing) = {
log::debug!("Adding song to queue"); log::debug!("Adding song to queue");
let mut queue_lock = queue.lock().await; let mut player_lock = player.lock().await;
for song in songs { for song in songs {
queue_lock.add(song); player_lock.queue().add(song);
} }
let autoshuffle = get_setting(ctx, guild.id, Setting::MusicAutoShuffle) let autoshuffle = get_setting(ctx, guild.id, Setting::MusicAutoShuffle)
.await? .await?
@ -51,26 +48,23 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
if autoshuffle { if autoshuffle {
log::debug!("Autoshuffeling"); log::debug!("Autoshuffeling");
queue_lock.shuffle(); player_lock.queue().shuffle();
} }
( (
queue_lock.current().is_none(), player_lock.queue().current().is_none(),
queue_lock.now_playing_msg.is_none(), player_lock.now_playing_message().is_none(),
) )
}; };
if play_first { if play_first {
log::debug!("Playing first song in queue"); log::debug!("Playing first song in queue");
let data = ctx.data.read().await; let mut player_lock = player.lock().await;
let lava_player = data.get::<Lavalink>().unwrap(); player_lock.play_next().await?;
while !play_next_in_queue(&ctx.http, &msg.channel_id, &guild.id, &queue, &lava_player).await
{
}
} }
if create_now_playing { if create_now_playing {
let handle = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?; let handle = create_now_playing_msg(ctx, Arc::clone(&player), msg.channel_id).await?;
let mut queue_lock = queue.lock().await; let mut player_lock = player.lock().await;
queue_lock.now_playing_msg = Some(handle); player_lock.set_now_playing(handle).await;
} }
handle_autodelete(ctx, msg).await?; handle_autodelete(ctx, msg).await?;

@ -5,11 +5,11 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete; use crate::commands::common::handle_autodelete;
use crate::commands::music::{ use crate::commands::music::{
get_channel_for_author, get_queue_for_guild, get_songs_for_query, get_voice_manager, get_channel_for_author, get_music_player_for_guild, get_songs_for_query, DJ_CHECK,
join_channel, play_next_in_queue, DJ_CHECK,
}; };
use crate::messages::music::now_playing::create_now_playing_msg; 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] #[command]
#[only_in(guilds)] #[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(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Playing song as next song for guild {}", guild.id); 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() { let mut player = get_music_player_for_guild(ctx, guild.id).await;
log::debug!("Not in a voice channel. Joining authors channel");
msg.guild(&ctx.cache).await.unwrap(); if player.is_none() {
log::debug!("Not in a channel. Joining authors channel...");
let channel_id = get_channel_for_author(&msg.author.id, &guild)?; 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 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 (play_first, create_now_playing) = {
let mut queue_lock = queue.lock().await; let mut player_lock = player.lock().await;
songs.reverse(); songs.reverse();
log::debug!("Enqueueing songs as next songs in the queue"); log::debug!("Enqueueing songs as next songs in the queue");
for song in songs { for song in songs {
queue_lock.add_next(song); player_lock.queue().add_next(song);
} }
( (
queue_lock.current().is_none(), player_lock.queue().current().is_none(),
queue_lock.now_playing_msg.is_none(), player_lock.now_playing_message().is_none(),
) )
}; };
if play_first { if play_first {
let data = ctx.data.read().await; let mut player_lock = player.lock().await;
let player = data.get::<Lavalink>().unwrap(); player_lock.play_next().await?;
while !play_next_in_queue(&ctx.http, &msg.channel_id, &guild.id, &queue, &player).await {}
} }
if create_now_playing { if create_now_playing {
let handle = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?; let handle = create_now_playing_msg(ctx, Arc::clone(&player), msg.channel_id).await?;
let mut queue_lock = queue.lock().await; let mut player_lock = player.lock().await;
queue_lock.now_playing_msg = Some(handle); player_lock.set_now_playing(handle).await;
} }
handle_autodelete(ctx, msg).await?; handle_autodelete(ctx, msg).await?;

@ -1,10 +1,11 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::macros::command; 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 serenity::model::channel::Message;
use crate::commands::common::handle_autodelete; 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::messages::music::queue::create_queue_menu;
use crate::providers::music::queue::Song; 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()) .map(|s| s.unwrap().to_lowercase())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let queue = forward_error!( let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {
ctx, player
msg.channel_id, } else {
get_queue_for_guild(ctx, &guild.id).await return create_no_voicechannel_message(&ctx.http, msg.channel_id)
); .await
let queue_lock = queue.lock().await; .map_err(CommandError::from);
let songs: Vec<(usize, Song)> = queue_lock };
let mut player = player.lock().await;
let songs: Vec<(usize, Song)> = player
.queue()
.entries() .entries()
.into_iter() .into_iter()
.enumerate() .enumerate()

@ -1,10 +1,11 @@
use crate::commands::common::handle_autodelete; 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::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage; use bot_serenityutils::ephemeral_message::EphemeralMessage;
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::macros::command; 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 serenity::model::channel::Message;
#[command] #[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); log::debug!("Moving song for guild {}", guild.id);
let pos = args.single::<usize>()?; let pos = args.single::<usize>()?;
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!( let mut player = player.lock().await;
ctx, player.queue().remove(pos);
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let mut queue_lock = queue.lock().await;
queue_lock.remove(pos);
} }
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {

@ -1,10 +1,11 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult; use serenity::framework::standard::{CommandError, CommandResult};
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete; 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::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage; 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(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Shuffling queue for guild {}", guild.id); log::debug!("Shuffling queue for guild {}", guild.id);
let queue = forward_error!( let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {
ctx, player
msg.channel_id, } else {
get_queue_for_guild(ctx, &guild.id).await return create_no_voicechannel_message(&ctx.http, msg.channel_id)
); .await
.map_err(CommandError::from);
};
{ {
let mut queue_lock = queue.lock().await; let mut player = player.lock().await;
queue_lock.shuffle(); player.queue().shuffle();
} }
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {

@ -1,11 +1,11 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult; use serenity::framework::standard::{CommandError, CommandResult};
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete; use crate::commands::common::handle_autodelete;
use crate::commands::music::DJ_CHECK; use crate::commands::music::{get_music_player_for_guild, DJ_CHECK};
use crate::providers::music::lavalink::Lavalink; use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage; 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(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Skipping song for guild {}", guild.id); 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 mut player = player.lock().await;
let player = data.get::<Lavalink>().unwrap(); player.skip().await?;
player.stop(guild.id.0).await?;
} }
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {

@ -8,7 +8,7 @@ use serenity::model::id::{ChannelId, GuildId, MessageId};
use serenity::model::voice::VoiceState; use serenity::model::voice::VoiceState;
use serenity::prelude::*; 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 crate::utils::delete_messages_from_database;
use bot_serenityutils::menu::{ use bot_serenityutils::menu::{
handle_message_delete, handle_message_delete_bulk, handle_reaction_add, handle_reaction_remove, 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 { if let Some(count) = member_count {
log::debug!("{} Members in channel", count); log::debug!("{} Members in channel", count);
if let Ok(queue) = get_queue_for_guild(&ctx, &guild_id).await { if let Some(player) = get_music_player_for_guild(&ctx, guild_id).await {
let mut queue_lock = queue.lock().await; let mut player = player.lock().await;
log::debug!("Setting leave flag to {}", count == 0); log::debug!("Setting leave flag to {}", count == 0);
queue_lock.leave_flag = count == 0; player.set_leave_flag(count == 0);
} }
} }
} }

@ -1,2 +1,3 @@
pub mod no_voicechannel;
pub mod now_playing; pub mod now_playing;
pub mod queue; pub mod queue;

@ -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<Http>,
channel_id: ChannelId,
) -> BotResult<()> {
EphemeralMessage::create(http, channel_id, SHORT_TIMEOUT, |m| {
m.content("‼️ I'm not in a Voice Channel")
})
.await?;
Ok(())
}

@ -4,11 +4,11 @@ use serenity::builder::CreateEmbed;
use serenity::http::Http; use serenity::http::Http;
use serenity::model::prelude::ChannelId; 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::messages::add_ephemeral_handle_to_database;
use crate::providers::music::add_youtube_song_to_database; use crate::providers::music::add_youtube_song_to_database;
use crate::providers::music::lavalink::Lavalink; use crate::providers::music::player::MusicPlayer;
use crate::providers::music::queue::{MusicQueue, Song}; use crate::providers::music::queue::Song;
use crate::utils::context_data::{DatabaseContainer, Store}; use crate::utils::context_data::{DatabaseContainer, Store};
use crate::utils::error::*; use crate::utils::error::*;
use bot_serenityutils::core::MessageHandle; 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 /// Creates a new now playing message and returns the embed for that message
pub async fn create_now_playing_msg( pub async fn create_now_playing_msg(
ctx: &Context, ctx: &Context,
queue: Arc<Mutex<MusicQueue>>, player: Arc<Mutex<MusicPlayer>>,
channel_id: ChannelId, channel_id: ChannelId,
) -> BotResult<Arc<RwLock<MessageHandle>>> { ) -> BotResult<Arc<RwLock<MessageHandle>>> {
log::debug!("Creating now playing menu"); log::debug!("Creating now playing menu");
@ -61,16 +61,17 @@ pub async fn create_now_playing_msg(
) )
.show_help() .show_help()
.add_page(Page::new_builder(move || { .add_page(Page::new_builder(move || {
let queue = Arc::clone(&queue); let player = Arc::clone(&player);
Box::pin(async move { Box::pin(async move {
log::debug!("Creating now playing embed for page"); log::debug!("Creating now playing embed for page");
let queue = queue.lock().await; let mut player = player.lock().await;
log::debug!("Queue locked"); log::debug!("player locked");
let mut page = CreateMessage::default(); 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(); 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| { page.embed(|e| {
e.0.clone_from(&embed.0); e.0.clone_from(&embed.0);
e e
@ -166,25 +167,16 @@ async fn play_pause_button_action(
return Ok(()); 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) = { let (current, message, paused) = {
log::debug!("Queue is locked"); log::debug!("Queue is locked");
let mut queue = queue.lock().await; let mut player = player.lock().await;
{ player.toggle_paused().await?;
let pause = !queue.paused();
let data = ctx.data.read().await;
let player = data.get::<Lavalink>().unwrap();
player
.set_pause(guild_id.0, pause)
.await
.map_err(BotError::from)?;
queue.set_paused(pause);
}
( (
queue.current().clone(), player.queue().current().clone(),
queue.now_playing_msg.clone().unwrap(), player.now_playing_message().clone().unwrap(),
queue.paused(), player.is_paused(),
) )
}; };
log::debug!("Queue is unlocked"); log::debug!("Queue is unlocked");
@ -211,9 +203,9 @@ async fn skip_button_action(
} }
{ {
let data = ctx.data.read().await; let player = get_music_player_for_guild(ctx, guild_id).await.unwrap();
let player = data.get::<Lavalink>().unwrap(); let mut player = player.lock().await;
player.stop(guild_id.0).await.map_err(BotError::from)?; player.skip().await?;
} }
Ok(()) Ok(())
@ -243,9 +235,9 @@ async fn stop_button_action(
if manager.get(guild_id).is_some() { if manager.get(guild_id).is_some() {
manager.remove(guild_id).await.map_err(BotError::from)?; manager.remove(guild_id).await.map_err(BotError::from)?;
let data = ctx.data.read().await; let player = get_music_player_for_guild(ctx, guild_id).await.unwrap();
let player = data.get::<Lavalink>().unwrap(); let mut player = player.lock().await;
player.destroy(guild_id.0).await.map_err(BotError::from)?; player.stop().await?;
log::debug!("Left the voice channel"); log::debug!("Left the voice channel");
} else { } else {
log::debug!("Not in a voice channel"); log::debug!("Not in a voice channel");
@ -268,10 +260,10 @@ async fn good_pick_action(
reaction: Reaction, reaction: Reaction,
) -> SerenityUtilsResult<()> { ) -> SerenityUtilsResult<()> {
let guild_id = reaction.guild_id.unwrap(); let guild_id = reaction.guild_id.unwrap();
let queue = get_queue_for_guild(ctx, &guild_id).await?; let player = get_music_player_for_guild(ctx, guild_id).await.unwrap();
let queue = queue.lock().await; 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 data = ctx.data.read().await;
let store = data.get::<Store>().unwrap(); let store = data.get::<Store>().unwrap();
let database = data.get::<DatabaseContainer>().unwrap(); let database = data.get::<DatabaseContainer>().unwrap();
@ -292,9 +284,9 @@ async fn delete_action(
handle.clone() handle.clone()
}; };
{ {
let queue = get_queue_for_guild(ctx, &guild_id).await?; let player = get_music_player_for_guild(ctx, guild_id).await.unwrap();
let mut queue = queue.lock().await; let mut player = player.lock().await;
queue.now_playing_msg = None; player.clear_now_playing();
} }
ctx.http ctx.http
.delete_message(handle.channel_id, handle.message_id) .delete_message(handle.channel_id, handle.message_id)

@ -1,11 +1,8 @@
use crate::commands::music::play_next_in_queue; use crate::utils::context_data::MusicPlayers;
use crate::utils::context_data::Store;
use lavalink_rs::gateway::LavalinkEventHandler; use lavalink_rs::gateway::LavalinkEventHandler;
use lavalink_rs::model::{TrackFinish, TrackStart}; use lavalink_rs::model::{TrackFinish, TrackStart};
use lavalink_rs::LavalinkClient; use lavalink_rs::LavalinkClient;
use serenity::async_trait; use serenity::async_trait;
use serenity::http::Http;
use serenity::model::id::GuildId;
use serenity::prelude::TypeMapKey; use serenity::prelude::TypeMapKey;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::RwLock; use tokio::sync::RwLock;
@ -13,7 +10,6 @@ use typemap_rev::TypeMap;
pub struct LavalinkHandler { pub struct LavalinkHandler {
pub data: Arc<RwLock<TypeMap>>, pub data: Arc<RwLock<TypeMap>>,
pub http: Arc<Http>,
} }
#[async_trait] #[async_trait]
@ -21,36 +17,28 @@ impl LavalinkEventHandler for LavalinkHandler {
async fn track_start(&self, _client: LavalinkClient, event: TrackStart) { async fn track_start(&self, _client: LavalinkClient, event: TrackStart) {
log::info!("Track started!\nGuild: {}", event.guild_id); 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); log::info!("Track finished!\nGuild: {}", event.guild_id);
let queue = { let player = {
let data = self.data.read().await; let data = self.data.read().await;
let store = data.get::<Store>().unwrap(); let players = data.get::<MusicPlayers>().unwrap();
store players.get(&event.guild_id).cloned()
.music_queues
.get(&GuildId(event.guild_id))
.unwrap()
.clone()
}; };
let channel_id = { if let Some(player) = player {
let queue = queue.lock().await; let mut player = player.lock().await;
queue.channel_id() if let Err(e) = player.play_next().await {
}; log::error!("Failed to play next song: {:?}", e);
while !play_next_in_queue( }
&self.http, if let Err(e) = player.update_now_playing().await {
&channel_id, log::error!("Failed to update now playing embed: {:?}", e);
&GuildId(event.guild_id), }
&queue, }
&client,
)
.await
{}
} }
} }
pub struct Lavalink; pub struct Lavalink;
impl TypeMapKey for Lavalink { impl TypeMapKey for Lavalink {
type Value = LavalinkClient; type Value = Arc<LavalinkClient>;
} }

@ -7,12 +7,13 @@ use regex::Regex;
use responses::VideoInformation; use responses::VideoInformation;
use youtube_dl::search_video_information; use youtube_dl::search_video_information;
pub(crate) mod lavalink; pub mod lavalink;
pub(crate) mod lyrics; pub mod lyrics;
pub(crate) mod queue; pub mod player;
pub(crate) mod responses; pub mod queue;
pub(crate) mod spotify; pub mod responses;
pub(crate) mod youtube_dl; pub mod spotify;
pub mod youtube_dl;
/// Searches for a youtube video for the specified song /// Searches for a youtube video for the specified song
pub(crate) async fn song_to_youtube_video(song: &Song) -> BotResult<Option<VideoInformation>> { pub(crate) async fn song_to_youtube_video(song: &Song) -> BotResult<Option<VideoInformation>> {

@ -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<LavalinkClient>,
http: Arc<Http>,
queue: MusicQueue,
guild_id: GuildId,
now_playing_msg: Option<Arc<RwLock<MessageHandle>>>,
leave_flag: bool,
paused: bool,
}
impl MusicPlayer {
/// Creates a new music player
pub fn new(client: Arc<LavalinkClient>, http: Arc<Http>, 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<Arc<Mutex<MusicPlayer>>> {
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::<Lavalink>().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::<MusicPlayers>().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<Option<String>> {
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<bool> {
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<RwLock<MessageHandle>>) {
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<Arc<RwLock<MessageHandle>>> {
&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;
}
}

@ -7,30 +7,20 @@ use bot_coreutils::shuffle::Shuffle;
use crate::providers::music::responses::{PlaylistEntry, VideoInformation}; use crate::providers::music::responses::{PlaylistEntry, VideoInformation};
use crate::providers::music::song_to_youtube_video; use crate::providers::music::song_to_youtube_video;
use bot_database::models::YoutubeSong; 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)] #[derive(Clone)]
pub struct MusicQueue { pub struct MusicQueue {
inner: VecDeque<Song>, inner: VecDeque<Song>,
current: Option<Song>, current: Option<Song>,
paused: bool,
pub now_playing_msg: Option<Arc<RwLock<MessageHandle>>>,
pub leave_flag: bool, pub leave_flag: bool,
channel_id: ChannelId,
} }
impl MusicQueue { impl MusicQueue {
pub fn new(channel_id: ChannelId) -> Self { pub fn new() -> Self {
Self { Self {
inner: VecDeque::new(), inner: VecDeque::new(),
current: None, current: None,
paused: false,
leave_flag: false, leave_flag: false,
now_playing_msg: None,
channel_id,
} }
} }
@ -64,26 +54,11 @@ impl MusicQueue {
self.current = Some(song) 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 /// Returns the reference to the currently playing song
pub fn current(&self) -> &Option<Song> { pub fn current(&self) -> &Option<Song> {
&self.current &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 /// Clears the queue
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.inner.clear(); self.inner.clear();
@ -100,11 +75,6 @@ impl MusicQueue {
pub fn remove(&mut self, index: usize) { pub fn remove(&mut self, index: usize) {
self.inner.remove(index); 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)] #[derive(Clone, Debug)]

@ -5,18 +5,16 @@ use std::sync::Arc;
use bot_database::Database; use bot_database::Database;
use sauce_api::prelude::SauceNao; use sauce_api::prelude::SauceNao;
use serenity::client::Context; use serenity::client::Context;
use serenity::model::id::GuildId;
use serenity::prelude::TypeMapKey; use serenity::prelude::TypeMapKey;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use crate::providers::music::queue::MusicQueue; use crate::providers::music::player::MusicPlayer;
use crate::providers::music::spotify::SpotifyApi; use crate::providers::music::spotify::SpotifyApi;
pub struct Store; pub struct Store;
pub struct StoreData { pub struct StoreData {
pub minecraft_data_api: minecraft_data_rs::api::Api, pub minecraft_data_api: minecraft_data_rs::api::Api,
pub music_queues: HashMap<GuildId, Arc<Mutex<MusicQueue>>>,
pub spotify_api: SpotifyApi, pub spotify_api: SpotifyApi,
pub sauce_nao: SauceNao, pub sauce_nao: SauceNao,
} }
@ -31,7 +29,6 @@ impl StoreData {
minecraft_data_api: minecraft_data_rs::api::Api::new( minecraft_data_api: minecraft_data_rs::api::Api::new(
minecraft_data_rs::api::versions::latest_stable().unwrap(), minecraft_data_rs::api::versions::latest_stable().unwrap(),
), ),
music_queues: HashMap::new(),
spotify_api: SpotifyApi::new(), spotify_api: SpotifyApi::new(),
sauce_nao, sauce_nao,
} }
@ -57,3 +54,9 @@ pub async fn get_database_from_context(ctx: &Context) -> Database {
database.clone() database.clone()
} }
pub struct MusicPlayers;
impl TypeMapKey for MusicPlayers {
type Value = HashMap<u64, Arc<Mutex<MusicPlayer>>>;
}

Loading…
Cancel
Save