Change music backend to lavalink

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

51
Cargo.lock generated

@ -89,8 +89,10 @@ dependencies = [
"futures-io", "futures-io",
"futures-util", "futures-util",
"log 0.4.14", "log 0.4.14",
"native-tls",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
"tokio-native-tls",
"tokio-rustls", "tokio-rustls",
"tungstenite 0.13.0", "tungstenite 0.13.0",
"webpki-roots 0.21.1", "webpki-roots 0.21.1",
@ -1077,6 +1079,31 @@ dependencies = [
"winapi-build", "winapi-build",
] ]
[[package]]
name = "lavalink-rs"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c63ca28d0378fa5e51d24a2cb6cc900a5f654d7931a53784ae01ff9a94b443e1"
dependencies = [
"async-trait",
"async-tungstenite 0.13.1",
"dashmap",
"futures",
"http",
"regex",
"reqwest",
"serde",
"serde-aux",
"serde_json",
"songbird",
"tokio",
"tokio-native-tls",
"tracing",
"tracing-log",
"typemap_rev",
"version_check",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -2047,6 +2074,17 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-aux"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77eb8c83f6ebaedf5e8f970a8a44506b180b8e6268de03885c8547031ccaee00"
dependencies = [
"chrono",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.125" version = "1.0.125"
@ -2495,6 +2533,7 @@ dependencies = [
"dotenv", "dotenv",
"fern", "fern",
"futures", "futures",
"lavalink-rs",
"lazy_static", "lazy_static",
"log 0.4.14", "log 0.4.14",
"minecraft-data-rs", "minecraft-data-rs",
@ -2642,6 +2681,17 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "tracing-log"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
dependencies = [
"lazy_static",
"log 0.4.14",
"tracing-core",
]
[[package]] [[package]]
name = "trigram" name = "trigram"
version = "0.4.4" version = "0.4.4"
@ -2690,6 +2740,7 @@ dependencies = [
"httparse", "httparse",
"input_buffer 0.4.0", "input_buffer 0.4.0",
"log 0.4.14", "log 0.4.14",
"native-tls",
"rand 0.8.3", "rand 0.8.3",
"rustls", "rustls",
"sha-1", "sha-1",

@ -38,4 +38,5 @@ sauce-api = "0.7.1"
rustc_version_runtime = "0.2.0" rustc_version_runtime = "0.2.0"
trigram = "0.4.4" trigram = "0.4.4"
typemap_rev = "0.1.5" typemap_rev = "0.1.5"
youtube-metadata = "0.1.1" youtube-metadata = "0.1.1"
lavalink-rs = {version="0.7.1", features=["native"]}

@ -12,28 +12,42 @@ 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::utils::context_data::{get_database_from_context, DatabaseContainer, Store, StoreData}; use crate::utils::context_data::{get_database_from_context, DatabaseContainer, 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 serenity::framework::standard::buckets::LimitedFor; use serenity::framework::standard::buckets::LimitedFor;
use serenity::http::Http;
use std::env;
use std::sync::Arc; use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
use tokio::sync::Mutex; use tokio::sync::Mutex;
pub async fn get_client() -> BotResult<Client> { pub async fn get_client() -> BotResult<Client> {
let token = dotenv::var("BOT_TOKEN").map_err(|_| BotError::MissingToken)?; let token = env::var("BOT_TOKEN").map_err(|_| BotError::MissingToken)?;
let database = get_database()?; let database = get_database()?;
let http = Http::new_with_token(&token);
let current_application = http.get_current_application_info().await?;
let client = Client::builder(token) let client = Client::builder(token)
.event_handler(Handler) .event_handler(Handler)
.framework(get_framework().await) .framework(get_framework().await)
.register_songbird() .register_songbird()
.await?; .await?;
let data = client.data.clone();
let http = client.cache_and_http.http.clone();
let lava_client = LavalinkClient::builder(current_application.id.0)
.set_host(env::var("LAVALINK_HOST").unwrap_or("172.0.0.1".to_string()))
.set_password(env::var("LAVALINK_PORT").expect("Missing lavalink port"))
.set_password(env::var("LAVALINK_PASSWORD").expect("Missing lavalink password"))
.build(LavalinkHandler { data, http })
.await?;
{ {
let mut data = client.data.write().await; 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);
} }
Ok(client) Ok(client)

@ -30,9 +30,7 @@ async fn current(ctx: &Context, msg: &Message) -> CommandResult {
queue_lock.current().clone() queue_lock.current().clone()
}; };
if let Some((current, _)) = current { if let Some(_) = current {
let metadata = current.metadata().clone();
log::trace!("Metadata is {:?}", metadata);
let np_msg = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?; let np_msg = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?;
let mut queue_lock = queue.lock().await; let mut queue_lock = queue.lock().await;

@ -5,7 +5,7 @@ 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::{get_voice_manager, DJ_CHECK};
use crate::utils::context_data::Store; use crate::providers::music::lavalink::Lavalink;
use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage; use bot_serenityutils::ephemeral_message::EphemeralMessage;
@ -21,15 +21,6 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
log::debug!("Leave request received for guild {}", guild.id); log::debug!("Leave request received for guild {}", guild.id);
let manager = get_voice_manager(ctx).await; let manager = get_voice_manager(ctx).await;
let queue = {
let mut data = ctx.data.write().await;
let store = data.get_mut::<Store>().unwrap();
store
.music_queues
.remove(&guild.id)
.expect("No queue for guild.")
};
let queue_lock = queue.lock().await;
let handler = manager.get(guild.id); let handler = manager.get(guild.id);
if let Some(handler) = handler { if let Some(handler) = handler {
@ -38,10 +29,10 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
} }
if manager.get(guild.id).is_some() { if manager.get(guild.id).is_some() {
if let Some((current, _)) = queue_lock.current() {
current.stop()?;
}
manager.remove(guild.id).await?; 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| { EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("👋 Left the Voice Channel") m.content("👋 Left the Voice Channel")
}) })

@ -23,11 +23,10 @@ async fn lyrics(ctx: &Context, msg: &Message) -> CommandResult {
); );
let queue_lock = queue.lock().await; let queue_lock = queue.lock().await;
if let Some((current, _)) = queue_lock.current() { if let Some(song) = queue_lock.current() {
log::debug!("Playing music. Fetching lyrics for currently playing song..."); log::debug!("Playing music. Fetching lyrics for currently playing song...");
let metadata = current.metadata(); let title = song.title().clone();
let title = metadata.title.clone().unwrap(); let author = song.author().clone();
let author = metadata.artist.clone().unwrap();
if let Some(lyrics) = get_lyrics(&*author, &*title).await? { if let Some(lyrics) = get_lyrics(&*author, &*title).await? {
log::trace!("Lyrics for '{}' are {}", title, lyrics); log::trace!("Lyrics for '{}' are {}", title, lyrics);

@ -35,6 +35,7 @@ use shuffle::SHUFFLE_COMMAND;
use skip::SKIP_COMMAND; use skip::SKIP_COMMAND;
use crate::messages::music::now_playing::update_now_playing_msg; 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::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};
@ -43,7 +44,9 @@ 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;
@ -84,16 +87,27 @@ pub struct Music;
struct SongEndNotifier { struct SongEndNotifier {
channel_id: ChannelId, channel_id: ChannelId,
guild_id: GuildId,
http: Arc<Http>, http: Arc<Http>,
queue: Arc<Mutex<MusicQueue>>, queue: Arc<Mutex<MusicQueue>>,
handler: Arc<Mutex<Call>>, data: Arc<RwLock<TypeMap>>,
} }
#[async_trait] #[async_trait]
impl VoiceEventHandler for SongEndNotifier { impl VoiceEventHandler for SongEndNotifier {
async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> { async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> {
log::debug!("Song ended in {}. Playing next one", self.channel_id); log::debug!("Song ended in {}. Playing next one", self.channel_id);
while !play_next_in_queue(&self.http, &self.channel_id, &self.queue, &self.handler).await { let data = self.data.read().await;
let player = data.get::<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; tokio::time::sleep(Duration::from_millis(100)).await;
} }
@ -129,9 +143,6 @@ impl VoiceEventHandler for ChannelDurationNotifier {
let mut handler_lock = self.handler.lock().await; let mut handler_lock = self.handler.lock().await;
handler_lock.remove_all_global_events(); handler_lock.remove_all_global_events();
} }
if let Some((current, _)) = queue_lock.current() {
let _ = current.stop();
}
let _ = self.manager.remove(self.guild_id).await; let _ = self.manager.remove(self.guild_id).await;
log::debug!("Left the voice channel"); log::debug!("Left the voice channel");
} }
@ -156,11 +167,17 @@ async fn join_channel(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) -
.expect("Songbird Voice client placed in at initialisation.") .expect("Songbird Voice client placed in at initialisation.")
.clone(); .clone();
let (handler, _) = manager.join(guild_id, channel_id).await; let (handler, connection) = manager.join_gateway(guild_id, channel_id).await;
let connection = connection.expect("Failed to join gateway");
let mut data = ctx.data.write().await; let 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(); let store = data.get_mut::<Store>().unwrap();
log::debug!("Creating new queue"); log::debug!("Creating new queue");
let queue = Arc::new(Mutex::new(MusicQueue::new())); let queue = Arc::new(Mutex::new(MusicQueue::new(channel_id)));
store.music_queues.insert(guild_id, queue.clone()); store.music_queues.insert(guild_id, queue.clone());
{ {
@ -170,10 +187,11 @@ async fn join_channel(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) -
handler_lock.add_global_event( handler_lock.add_global_event(
Event::Track(TrackEvent::End), Event::Track(TrackEvent::End),
SongEndNotifier { SongEndNotifier {
channel_id: channel_id.clone(), channel_id,
guild_id,
http: ctx.http.clone(), http: ctx.http.clone(),
queue: Arc::clone(&queue), queue: Arc::clone(&queue),
handler: handler.clone(), data: ctx.data.clone(),
}, },
); );
@ -228,11 +246,12 @@ pub(crate) async fn get_queue_for_guild(
} }
/// Plays the next song in the queue /// Plays the next song in the queue
async fn play_next_in_queue( pub async fn play_next_in_queue(
http: &Arc<Http>, http: &Arc<Http>,
channel_id: &ChannelId, channel_id: &ChannelId,
guild_id: &GuildId,
queue: &Arc<Mutex<MusicQueue>>, queue: &Arc<Mutex<MusicQueue>>,
handler: &Arc<Mutex<Call>>, player: &LavalinkClient,
) -> bool { ) -> bool {
let mut queue_lock = queue.lock().await; let mut queue_lock = queue.lock().await;
@ -247,7 +266,8 @@ async fn play_next_in_queue(
} }
}; };
log::debug!("Getting source for song '{}'", url); log::debug!("Getting source for song '{}'", url);
let source = match songbird::ytdl(&url).await {
let query_information = match player.auto_search_tracks(url).await {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
let _ = channel_id let _ = channel_id
@ -259,22 +279,25 @@ async fn play_next_in_queue(
return false; return false;
} }
}; };
let mut handler_lock = handler.lock().await; if let Err(e) = player
let track = handler_lock.play_only_source(source); .play(guild_id.0, query_information.tracks[0].clone())
log::trace!("Track is {:?}", track); .start()
.await
{
log::error!("Failed to play song: {:?}", e);
}
log::trace!("Track is {:?}", query_information.tracks[0]);
if queue_lock.paused() { if queue_lock.paused() {
let _ = track.pause(); let _ = player.pause(guild_id.0).await;
} }
if let Some(np) = &queue_lock.now_playing_msg { if let Some(np) = &queue_lock.now_playing_msg {
if let Err(e) = if let Err(e) = update_now_playing_msg(http, np, &mut next, queue_lock.paused()).await {
update_now_playing_msg(http, np, track.metadata(), queue_lock.paused()).await
{
log::error!("Failed to update now playing message: {:?}", e); log::error!("Failed to update now playing message: {:?}", e);
} }
} }
queue_lock.set_current(track, next); queue_lock.set_current(next);
} else { } else {
if let Some(np) = mem::take(&mut queue_lock.now_playing_msg) { if let Some(np) = mem::take(&mut queue_lock.now_playing_msg) {
let np = np.read().await; let np = np.read().await;

@ -6,6 +6,7 @@ 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_queue_for_guild, DJ_CHECK};
use crate::messages::music::now_playing::update_now_playing_msg; use crate::messages::music::now_playing::update_now_playing_msg;
use crate::providers::music::lavalink::Lavalink;
use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage; use bot_serenityutils::ephemeral_message::EphemeralMessage;
@ -27,17 +28,21 @@ async fn pause(ctx: &Context, msg: &Message) -> CommandResult {
let mut queue_lock = queue.lock().await; let mut queue_lock = queue.lock().await;
if let Some(_) = queue_lock.current() { if let Some(_) = queue_lock.current() {
queue_lock.pause(); let is_paused = {
if queue_lock.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 {
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((current, _))) = if let (Some(menu), Some(song)) = (&queue_lock.now_playing_msg, queue_lock.current()) {
(&queue_lock.now_playing_msg, queue_lock.current()) update_now_playing_msg(&ctx.http, menu, &mut song.clone(), true).await?;
{
update_now_playing_msg(&ctx.http, menu, current.metadata(), true).await?;
} }
} else { } else {
log::debug!("Resumed"); log::debug!("Resumed");
@ -45,10 +50,8 @@ async fn pause(ctx: &Context, msg: &Message) -> CommandResult {
m.content("▶ Resumed playback") m.content("▶ Resumed playback")
}) })
.await?; .await?;
if let (Some(menu), Some((current, _))) = if let (Some(menu), Some(song)) = (&queue_lock.now_playing_msg, queue_lock.current()) {
(&queue_lock.now_playing_msg, queue_lock.current()) update_now_playing_msg(&ctx.http, menu, &mut song.clone(), true).await?;
{
update_now_playing_msg(&ctx.http, menu, current.metadata(), true).await?;
} }
} }
} else { } else {

@ -1,6 +1,6 @@
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, CommandError, CommandResult}; 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;
@ -9,6 +9,7 @@ use crate::commands::music::{
join_channel, play_next_in_queue, 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::settings::{get_setting, Setting}; use crate::providers::settings::{get_setting, Setting};
#[command] #[command]
@ -25,21 +26,15 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
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 manager = get_voice_manager(ctx).await;
let mut handler = manager.get(guild.id); let handler = manager.get(guild.id);
if handler.is_none() { if handler.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(); 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)?;
handler = Some(join_channel(ctx, channel_id, guild.id).await); join_channel(ctx, channel_id, guild.id).await;
} }
let handler_lock = forward_error!(
ctx,
msg.channel_id,
handler.ok_or(CommandError::from("I'm not in a voice channel"))
);
let songs = get_songs_for_query(&ctx, msg, query).await?; let songs = get_songs_for_query(&ctx, msg, query).await?;
let queue = get_queue_for_guild(ctx, &guild.id).await?; let queue = get_queue_for_guild(ctx, &guild.id).await?;
@ -66,7 +61,11 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
if play_first { if play_first {
log::debug!("Playing first song in queue"); log::debug!("Playing first song in queue");
while !play_next_in_queue(&ctx.http, &msg.channel_id, &queue, &handler_lock).await {} let data = ctx.data.read().await;
let lava_player = data.get::<Lavalink>().unwrap();
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, queue.clone(), msg.channel_id).await?;

@ -1,6 +1,6 @@
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, CommandError, CommandResult}; 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;
@ -9,6 +9,7 @@ use crate::commands::music::{
join_channel, play_next_in_queue, 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;
#[command] #[command]
#[only_in(guilds)] #[only_in(guilds)]
@ -24,21 +25,15 @@ async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap(); 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 manager = get_voice_manager(ctx).await;
let mut handler = manager.get(guild.id); let handler = manager.get(guild.id);
if handler.is_none() { if handler.is_none() {
log::debug!("Not in a voice channel. Joining authors channel"); log::debug!("Not in a voice channel. Joining authors channel");
msg.guild(&ctx.cache).await.unwrap(); 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)?;
handler = Some(join_channel(ctx, channel_id, guild.id).await); join_channel(ctx, channel_id, guild.id).await;
} }
let handler = forward_error!(
ctx,
msg.channel_id,
handler.ok_or(CommandError::from("I'm not in a voice channel"))
);
let mut songs = get_songs_for_query(&ctx, msg, query).await?; let mut songs = get_songs_for_query(&ctx, msg, query).await?;
let queue = get_queue_for_guild(ctx, &guild.id).await?; let queue = get_queue_for_guild(ctx, &guild.id).await?;
@ -57,7 +52,9 @@ async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
}; };
if play_first { if play_first {
while !play_next_in_queue(&ctx.http, &msg.channel_id, &queue, &handler).await {} let data = ctx.data.read().await;
let player = data.get::<Lavalink>().unwrap();
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, queue.clone(), msg.channel_id).await?;

@ -4,7 +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_queue_for_guild, DJ_CHECK}; use crate::commands::music::DJ_CHECK;
use crate::providers::music::lavalink::Lavalink;
use bot_serenityutils::core::SHORT_TIMEOUT; use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage; use bot_serenityutils::ephemeral_message::EphemeralMessage;
@ -18,15 +19,11 @@ use bot_serenityutils::ephemeral_message::EphemeralMessage;
async fn skip(ctx: &Context, msg: &Message) -> CommandResult { 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 queue = forward_error!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let queue_lock = queue.lock().await;
if let Some((current, _)) = queue_lock.current() { {
current.stop()?; let data = ctx.data.read().await;
let player = data.get::<Lavalink>().unwrap();
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| {

@ -3,12 +3,12 @@ use std::sync::Arc;
use serenity::builder::CreateEmbed; use serenity::builder::CreateEmbed;
use serenity::http::Http; use serenity::http::Http;
use serenity::model::prelude::ChannelId; use serenity::model::prelude::ChannelId;
use songbird::input::Metadata;
use crate::commands::music::{get_queue_for_guild, get_voice_manager, is_dj}; use crate::commands::music::{get_queue_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::queue::MusicQueue; use crate::providers::music::lavalink::Lavalink;
use crate::providers::music::queue::{MusicQueue, Song};
use crate::utils::context_data::{DatabaseContainer, Store}; use crate::utils::context_data::{DatabaseContainer, Store};
use crate::utils::error::*; use crate::utils::error::*;
use bot_serenityutils::core::MessageHandle; use bot_serenityutils::core::MessageHandle;
@ -68,9 +68,12 @@ pub async fn create_now_playing_msg(
log::debug!("Queue locked"); log::debug!("Queue locked");
let mut page = CreateMessage::default(); let mut page = CreateMessage::default();
if let Some((current, _)) = queue.current() { if let Some(mut current) = queue.current().clone() {
let mut embed = CreateEmbed::default();
create_now_playing_embed(&mut current, &mut embed, queue.paused(), nsfw).await;
page.embed(|e| { page.embed(|e| {
create_now_playing_embed(current.metadata(), e, queue.paused(), nsfw) e.0.clone_from(&embed.0);
e
}); });
} else { } else {
page.embed(|e| e.description("Queue is empty")); page.embed(|e| e.description("Queue is empty"));
@ -94,7 +97,7 @@ pub async fn create_now_playing_msg(
pub async fn update_now_playing_msg( pub async fn update_now_playing_msg(
http: &Arc<Http>, http: &Arc<Http>,
handle: &Arc<RwLock<MessageHandle>>, handle: &Arc<RwLock<MessageHandle>>,
meta: &Metadata, song: &mut Song,
paused: bool, paused: bool,
) -> BotResult<()> { ) -> BotResult<()> {
log::debug!("Updating now playing message"); log::debug!("Updating now playing message");
@ -102,9 +105,14 @@ pub async fn update_now_playing_msg(
let mut message = handle.get_message(http).await?; let mut message = handle.get_message(http).await?;
let nsfw = http.get_channel(handle.channel_id).await?.is_nsfw(); let nsfw = http.get_channel(handle.channel_id).await?.is_nsfw();
let mut embed = CreateEmbed::default();
create_now_playing_embed(song, &mut embed, paused, nsfw).await;
message message
.edit(http, |m| { .edit(http, |m| {
m.embed(|e| create_now_playing_embed(meta, e, paused, nsfw)) m.embed(|e| {
e.0.clone_from(&embed.0);
e
})
}) })
.await?; .await?;
log::debug!("Message updated."); log::debug!("Message updated.");
@ -113,19 +121,20 @@ pub async fn update_now_playing_msg(
} }
/// Creates the embed of the now playing message /// Creates the embed of the now playing message
fn create_now_playing_embed<'a>( async fn create_now_playing_embed<'a>(
meta: &Metadata, song: &mut Song,
mut embed: &'a mut CreateEmbed, mut embed: &'a mut CreateEmbed,
paused: bool, paused: bool,
nsfw: bool, nsfw: bool,
) -> &'a mut CreateEmbed { ) -> &'a mut CreateEmbed {
let url = song.url().await.unwrap();
embed = embed embed = embed
.title(if paused { "Paused" } else { "Playing" }) .title(if paused { "Paused" } else { "Playing" })
.description(format!( .description(format!(
"[{}]({}) by {}", "[{}]({}) by {}",
meta.title.clone().unwrap(), song.title().clone(),
meta.source_url.clone().unwrap(), url,
meta.artist.clone().unwrap() song.author().clone()
)) ))
.footer(|f| { .footer(|f| {
f.text(format!( f.text(format!(
@ -135,7 +144,7 @@ fn create_now_playing_embed<'a>(
}); });
if nsfw { if nsfw {
if let Some(thumb) = meta.thumbnail.clone() { if let Some(thumb) = song.thumbnail().clone() {
embed = embed.thumbnail(thumb); embed = embed.thumbnail(thumb);
} }
} }
@ -162,7 +171,16 @@ async fn play_pause_button_action(
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 queue = queue.lock().await;
queue.pause(); {
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(), queue.current().clone(),
queue.now_playing_msg.clone().unwrap(), queue.now_playing_msg.clone().unwrap(),
@ -171,8 +189,8 @@ async fn play_pause_button_action(
}; };
log::debug!("Queue is unlocked"); log::debug!("Queue is unlocked");
if let Some((current, _)) = current { if let Some(mut current) = current {
update_now_playing_msg(&ctx.http, &message, current.metadata(), paused).await?; update_now_playing_msg(&ctx.http, &message, &mut current, paused).await?;
} }
} }
@ -191,16 +209,11 @@ async fn skip_button_action(
if !is_dj(ctx, guild_id, &user).await? { if !is_dj(ctx, guild_id, &user).await? {
return Ok(()); return Ok(());
} }
{
let current = {
let queue = get_queue_for_guild(ctx, &guild_id).await?;
let queue = queue.lock().await;
queue.current().clone()
};
if let Some((current, _)) = current { {
let _ = current.stop(); let data = ctx.data.read().await;
} let player = data.get::<Lavalink>().unwrap();
player.stop(guild_id.0).await.map_err(BotError::from)?;
} }
Ok(()) Ok(())
@ -220,8 +233,6 @@ async fn stop_button_action(
} }
{ {
let manager = get_voice_manager(ctx).await; let manager = get_voice_manager(ctx).await;
let queue = get_queue_for_guild(ctx, &guild_id).await?;
let queue = queue.lock().await;
let handler = manager.get(guild_id); let handler = manager.get(guild_id);
@ -229,12 +240,12 @@ async fn stop_button_action(
let mut handler_lock = handler.lock().await; let mut handler_lock = handler.lock().await;
handler_lock.remove_all_global_events(); handler_lock.remove_all_global_events();
} }
if let Some(current) = queue.current() {
current.0.stop().map_err(BotError::from)?;
}
if manager.get(guild_id).is_some() { 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 = data.get::<Lavalink>().unwrap();
player.destroy(guild_id.0).await.map_err(BotError::from)?;
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");
@ -260,7 +271,7 @@ async fn good_pick_action(
let queue = get_queue_for_guild(ctx, &guild_id).await?; let queue = get_queue_for_guild(ctx, &guild_id).await?;
let queue = queue.lock().await; let queue = queue.lock().await;
if let Some((_, song)) = queue.current() { if let Some(song) = queue.current() {
let data = ctx.data.read().await; let 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();

@ -0,0 +1,56 @@
use crate::commands::music::play_next_in_queue;
use crate::utils::context_data::Store;
use lavalink_rs::gateway::LavalinkEventHandler;
use lavalink_rs::model::{TrackFinish, TrackStart};
use lavalink_rs::LavalinkClient;
use serenity::async_trait;
use serenity::http::Http;
use serenity::model::id::GuildId;
use serenity::prelude::TypeMapKey;
use std::sync::Arc;
use tokio::sync::RwLock;
use typemap_rev::TypeMap;
pub struct LavalinkHandler {
pub data: Arc<RwLock<TypeMap>>,
pub http: Arc<Http>,
}
#[async_trait]
impl LavalinkEventHandler for LavalinkHandler {
async fn track_start(&self, _client: LavalinkClient, event: TrackStart) {
log::info!("Track started!\nGuild: {}", event.guild_id);
}
async fn track_finish(&self, client: LavalinkClient, event: TrackFinish) {
log::info!("Track finished!\nGuild: {}", event.guild_id);
let queue = {
let data = self.data.read().await;
let store = data.get::<Store>().unwrap();
store
.music_queues
.get(&GuildId(event.guild_id))
.unwrap()
.clone()
};
let channel_id = {
let queue = queue.lock().await;
queue.channel_id()
};
while !play_next_in_queue(
&self.http,
&channel_id,
&GuildId(event.guild_id),
&queue,
&client,
)
.await
{}
}
}
pub struct Lavalink;
impl TypeMapKey for Lavalink {
type Value = LavalinkClient;
}

@ -7,6 +7,7 @@ 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(crate) mod lyrics; pub(crate) mod lyrics;
pub(crate) mod queue; pub(crate) mod queue;
pub(crate) mod responses; pub(crate) mod responses;

@ -1,7 +1,6 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use aspotify::Track; use aspotify::Track;
use songbird::tracks::TrackHandle;
use bot_coreutils::shuffle::Shuffle; use bot_coreutils::shuffle::Shuffle;
@ -9,26 +8,29 @@ use crate::providers::music::responses::{PlaylistEntry, VideoInformation};
use crate::providers::music::song_to_youtube_video; use crate::providers::music::song_to_youtube_video;
use bot_database::models::YoutubeSong; use bot_database::models::YoutubeSong;
use bot_serenityutils::core::MessageHandle; use bot_serenityutils::core::MessageHandle;
use serenity::model::id::ChannelId;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::RwLock; use tokio::sync::RwLock;
#[derive(Clone)] #[derive(Clone)]
pub struct MusicQueue { pub struct MusicQueue {
inner: VecDeque<Song>, inner: VecDeque<Song>,
current: Option<(TrackHandle, Song)>, current: Option<Song>,
paused: bool, paused: bool,
pub now_playing_msg: Option<Arc<RwLock<MessageHandle>>>, 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() -> Self { pub fn new(channel_id: ChannelId) -> Self {
Self { Self {
inner: VecDeque::new(), inner: VecDeque::new(),
current: None, current: None,
paused: false, paused: false,
leave_flag: false, leave_flag: false,
now_playing_msg: None, now_playing_msg: None,
channel_id,
} }
} }
@ -58,8 +60,8 @@ impl MusicQueue {
} }
/// Sets the currently playing song /// Sets the currently playing song
pub fn set_current(&mut self, handle: TrackHandle, song: Song) { pub fn set_current(&mut self, song: Song) {
self.current = Some((handle, song)) self.current = Some(song)
} }
/// Clears the currently playing song /// Clears the currently playing song
@ -68,10 +70,20 @@ impl MusicQueue {
} }
/// Returns the reference to the currently playing song /// Returns the reference to the currently playing song
pub fn current(&self) -> &Option<(TrackHandle, 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();
@ -89,24 +101,9 @@ impl MusicQueue {
self.inner.remove(index); self.inner.remove(index);
} }
/// Toggles pause /// The channel id where the music messages should be sent to
pub fn pause(&mut self) { pub fn channel_id(&self) -> ChannelId {
if let Some(current) = &self.current { self.channel_id
if self.paused {
let _ = current.0.play();
} else {
let _ = current.0.pause();
}
self.paused = !self.paused;
} else {
self.paused = false;
}
}
/// Returns if the queue is paused
pub fn paused(&self) -> bool {
self.paused
} }
} }

@ -1,4 +1,5 @@
use bot_serenityutils::error::SerenityUtilsError; use bot_serenityutils::error::SerenityUtilsError;
use lavalink_rs::error::LavalinkError;
use thiserror::Error; use thiserror::Error;
pub type BotResult<T> = Result<T, BotError>; pub type BotResult<T> = Result<T, BotError>;
@ -44,6 +45,9 @@ pub enum BotError {
#[error("YouTube Error: {0}")] #[error("YouTube Error: {0}")]
YoutubeError(#[from] youtube_metadata::error::YoutubeError), YoutubeError(#[from] youtube_metadata::error::YoutubeError),
#[error("Lavalink Error: {0}")]
LavalinkError(#[from] LavalinkError),
#[error("{0}")] #[error("{0}")]
Msg(String), Msg(String),
} }

Loading…
Cancel
Save