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

@ -38,4 +38,5 @@ sauce-api = "0.7.1"
rustc_version_runtime = "0.2.0"
trigram = "0.4.4"
typemap_rev = "0.1.5"
youtube-metadata = "0.1.1"
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::handler::Handler;
use crate::providers::music::lavalink::{Lavalink, LavalinkHandler};
use crate::utils::context_data::{get_database_from_context, DatabaseContainer, Store, StoreData};
use crate::utils::error::{BotError, BotResult};
use bot_serenityutils::menu::EventDrivenMessageContainer;
use lavalink_rs::LavalinkClient;
use serenity::framework::standard::buckets::LimitedFor;
use serenity::http::Http;
use std::env;
use std::sync::Arc;
use std::time::SystemTime;
use tokio::sync::Mutex;
pub async fn get_client() -> BotResult<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 http = Http::new_with_token(&token);
let current_application = http.get_current_application_info().await?;
let client = Client::builder(token)
.event_handler(Handler)
.framework(get_framework().await)
.register_songbird()
.await?;
let data = client.data.clone();
let http = client.cache_and_http.http.clone();
let lava_client = LavalinkClient::builder(current_application.id.0)
.set_host(env::var("LAVALINK_HOST").unwrap_or("172.0.0.1".to_string()))
.set_password(env::var("LAVALINK_PORT").expect("Missing lavalink port"))
.set_password(env::var("LAVALINK_PASSWORD").expect("Missing lavalink password"))
.build(LavalinkHandler { data, http })
.await?;
{
let mut data = client.data.write().await;
data.insert::<Store>(StoreData::new());
data.insert::<DatabaseContainer>(database);
data.insert::<EventDrivenMessageContainer>(Arc::new(Mutex::new(HashMap::new())));
data.insert::<Lavalink>(lava_client);
}
Ok(client)

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

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

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

@ -35,6 +35,7 @@ use shuffle::SHUFFLE_COMMAND;
use skip::SKIP_COMMAND;
use crate::messages::music::now_playing::update_now_playing_msg;
use crate::providers::music::lavalink::Lavalink;
use crate::providers::music::queue::{MusicQueue, Song};
use crate::providers::music::{add_youtube_song_to_database, youtube_dl};
use crate::providers::settings::{get_setting, Setting};
@ -43,7 +44,9 @@ use crate::utils::error::{BotError, BotResult};
use bot_database::Database;
use futures::future::BoxFuture;
use futures::FutureExt;
use lavalink_rs::LavalinkClient;
use serenity::framework::standard::{Args, CommandOptions, Reason};
use serenity::prelude::{RwLock, TypeMap};
use youtube_metadata::get_video_information;
mod clear_queue;
@ -84,16 +87,27 @@ pub struct Music;
struct SongEndNotifier {
channel_id: ChannelId,
guild_id: GuildId,
http: Arc<Http>,
queue: Arc<Mutex<MusicQueue>>,
handler: Arc<Mutex<Call>>,
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);
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;
}
@ -129,9 +143,6 @@ impl VoiceEventHandler for ChannelDurationNotifier {
let mut handler_lock = self.handler.lock().await;
handler_lock.remove_all_global_events();
}
if let Some((current, _)) = queue_lock.current() {
let _ = current.stop();
}
let _ = self.manager.remove(self.guild_id).await;
log::debug!("Left the voice channel");
}
@ -156,11 +167,17 @@ async fn join_channel(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) -
.expect("Songbird Voice client placed in at initialisation.")
.clone();
let (handler, _) = manager.join(guild_id, channel_id).await;
let (handler, connection) = manager.join_gateway(guild_id, channel_id).await;
let connection = connection.expect("Failed to join gateway");
let mut data = ctx.data.write().await;
let lava_client = data.get::<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()));
let queue = Arc::new(Mutex::new(MusicQueue::new(channel_id)));
store.music_queues.insert(guild_id, queue.clone());
{
@ -170,10 +187,11 @@ async fn join_channel(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) -
handler_lock.add_global_event(
Event::Track(TrackEvent::End),
SongEndNotifier {
channel_id: channel_id.clone(),
channel_id,
guild_id,
http: ctx.http.clone(),
queue: Arc::clone(&queue),
handler: handler.clone(),
data: ctx.data.clone(),
},
);
@ -228,11 +246,12 @@ pub(crate) async fn get_queue_for_guild(
}
/// Plays the next song in the queue
async fn play_next_in_queue(
pub async fn play_next_in_queue(
http: &Arc<Http>,
channel_id: &ChannelId,
guild_id: &GuildId,
queue: &Arc<Mutex<MusicQueue>>,
handler: &Arc<Mutex<Call>>,
player: &LavalinkClient,
) -> bool {
let mut queue_lock = queue.lock().await;
@ -247,7 +266,8 @@ async fn play_next_in_queue(
}
};
log::debug!("Getting source for song '{}'", url);
let source = match songbird::ytdl(&url).await {
let query_information = match player.auto_search_tracks(url).await {
Ok(s) => s,
Err(e) => {
let _ = channel_id
@ -259,22 +279,25 @@ async fn play_next_in_queue(
return false;
}
};
let mut handler_lock = handler.lock().await;
let track = handler_lock.play_only_source(source);
log::trace!("Track is {:?}", track);
if let Err(e) = player
.play(guild_id.0, query_information.tracks[0].clone())
.start()
.await
{
log::error!("Failed to play song: {:?}", e);
}
log::trace!("Track is {:?}", query_information.tracks[0]);
if queue_lock.paused() {
let _ = track.pause();
let _ = player.pause(guild_id.0).await;
}
if let Some(np) = &queue_lock.now_playing_msg {
if let Err(e) =
update_now_playing_msg(http, np, track.metadata(), queue_lock.paused()).await
{
if let Err(e) = update_now_playing_msg(http, np, &mut next, queue_lock.paused()).await {
log::error!("Failed to update now playing message: {:?}", e);
}
}
queue_lock.set_current(track, next);
queue_lock.set_current(next);
} else {
if let Some(np) = mem::take(&mut queue_lock.now_playing_msg) {
let np = np.read().await;

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

@ -1,6 +1,6 @@
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandError, CommandResult};
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
@ -9,6 +9,7 @@ use crate::commands::music::{
join_channel, play_next_in_queue,
};
use crate::messages::music::now_playing::create_now_playing_msg;
use crate::providers::music::lavalink::Lavalink;
use crate::providers::settings::{get_setting, Setting};
#[command]
@ -25,21 +26,15 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
log::debug!("Play request received for guild {}", guild.id);
let manager = get_voice_manager(ctx).await;
let mut handler = manager.get(guild.id);
let handler = manager.get(guild.id);
if handler.is_none() {
log::debug!("Not in a channel. Joining authors channel...");
msg.guild(&ctx.cache).await.unwrap();
let channel_id = get_channel_for_author(&msg.author.id, &guild)?;
handler = Some(join_channel(ctx, channel_id, guild.id).await);
join_channel(ctx, channel_id, guild.id).await;
}
let handler_lock = forward_error!(
ctx,
msg.channel_id,
handler.ok_or(CommandError::from("I'm not in a voice channel"))
);
let songs = get_songs_for_query(&ctx, msg, query).await?;
let queue = get_queue_for_guild(ctx, &guild.id).await?;
@ -66,7 +61,11 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
if play_first {
log::debug!("Playing first song in queue");
while !play_next_in_queue(&ctx.http, &msg.channel_id, &queue, &handler_lock).await {}
let data = ctx.data.read().await;
let lava_player = data.get::<Lavalink>().unwrap();
while !play_next_in_queue(&ctx.http, &msg.channel_id, &guild.id, &queue, &lava_player).await
{
}
}
if create_now_playing {
let handle = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?;

@ -1,6 +1,6 @@
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandError, CommandResult};
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
@ -9,6 +9,7 @@ use crate::commands::music::{
join_channel, play_next_in_queue, DJ_CHECK,
};
use crate::messages::music::now_playing::create_now_playing_msg;
use crate::providers::music::lavalink::Lavalink;
#[command]
#[only_in(guilds)]
@ -24,21 +25,15 @@ async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Playing song as next song for guild {}", guild.id);
let manager = get_voice_manager(ctx).await;
let mut handler = manager.get(guild.id);
let handler = manager.get(guild.id);
if handler.is_none() {
log::debug!("Not in a voice channel. Joining authors channel");
msg.guild(&ctx.cache).await.unwrap();
let channel_id = get_channel_for_author(&msg.author.id, &guild)?;
handler = Some(join_channel(ctx, channel_id, guild.id).await);
join_channel(ctx, channel_id, guild.id).await;
}
let handler = forward_error!(
ctx,
msg.channel_id,
handler.ok_or(CommandError::from("I'm not in a voice channel"))
);
let mut songs = get_songs_for_query(&ctx, msg, query).await?;
let queue = get_queue_for_guild(ctx, &guild.id).await?;
@ -57,7 +52,9 @@ async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
};
if play_first {
while !play_next_in_queue(&ctx.http, &msg.channel_id, &queue, &handler).await {}
let data = ctx.data.read().await;
let player = data.get::<Lavalink>().unwrap();
while !play_next_in_queue(&ctx.http, &msg.channel_id, &guild.id, &queue, &player).await {}
}
if create_now_playing {
let handle = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?;

@ -4,7 +4,8 @@ use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, DJ_CHECK};
use crate::commands::music::DJ_CHECK;
use crate::providers::music::lavalink::Lavalink;
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
@ -18,15 +19,11 @@ use bot_serenityutils::ephemeral_message::EphemeralMessage;
async fn skip(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Skipping song for guild {}", guild.id);
let queue = forward_error!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let queue_lock = queue.lock().await;
if let Some((current, _)) = queue_lock.current() {
current.stop()?;
{
let data = ctx.data.read().await;
let player = data.get::<Lavalink>().unwrap();
player.stop(guild.id.0).await?;
}
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {

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

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

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

Loading…
Cancel
Save