Merge pull request #25 from Trivernis/develop

Develop
pull/26/head
Trivernis 3 years ago committed by GitHub
commit 24772ac9fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,4 +1,4 @@
name: Build and Test
name: Build Docker Container
on:
workflow_dispatch:

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,50 @@ use songbird::SerenityInit;
use crate::commands::*;
use crate::handler::Handler;
use crate::utils::context_data::{get_database_from_context, DatabaseContainer, Store, StoreData};
use crate::providers::music::lavalink::{Lavalink, LavalinkHandler};
use crate::utils::context_data::{
get_database_from_context, DatabaseContainer, MusicPlayers, Store, StoreData,
};
use crate::utils::error::{BotError, BotResult};
use bot_serenityutils::menu::EventDrivenMessageContainer;
use lavalink_rs::LavalinkClient;
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 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_PASSWORD").expect("Missing lavalink password"))
.set_port(
env::var("LAVALINK_PORT")
.ok()
.and_then(|s| s.parse().ok())
.expect("Missing lavalink port"),
)
.build(LavalinkHandler { data })
.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::<MusicPlayers>(HashMap::new());
data.insert::<Lavalink>(Arc::new(lava_client));
}
Ok(client)

@ -0,0 +1,38 @@
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
use futures::future::BoxFuture;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use serenity::Result as SerenityResult;
#[command]
#[description("Clears the chat (maximum 100 messages)")]
#[usage("[<number>]")]
#[example("20")]
#[min_args(0)]
#[max_args(1)]
#[bucket("general")]
#[required_permissions("MANAGE_MESSAGES")]
async fn clear(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let limit = args.single::<u64>().unwrap_or(20);
log::debug!("Deleting messages for channel {}", msg.channel_id);
let messages = msg.channel_id.messages(ctx, |b| b.limit(limit)).await?;
log::debug!("Deleting {} messages", messages.len());
let futures: Vec<BoxFuture<SerenityResult<()>>> = messages
.into_iter()
.map(|m| async move { ctx.http.delete_message(m.channel_id.0, m.id.0).await }.boxed())
.collect();
log::debug!("Waiting for all messages to be deleted");
let deleted = futures::future::join_all(futures).await;
let deleted_count = deleted.into_iter().filter(|d| d.is_ok()).count();
log::debug!("{} Messages deleted", deleted_count);
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |f| {
f.content(format!("Deleted {} messages", deleted_count))
})
.await?;
Ok(())
}

@ -2,6 +2,7 @@ use serenity::framework::standard::macros::group;
use about::ABOUT_COMMAND;
use add_gif::ADD_GIF_COMMAND;
use clear::CLEAR_COMMAND;
use gifs::GIFS_COMMAND;
use pain::PAIN_COMMAND;
use ping::PING_COMMAND;
@ -13,6 +14,7 @@ use timezones::TIMEZONES_COMMAND;
mod about;
mod add_gif;
mod clear;
mod gifs;
pub(crate) mod help;
mod pain;
@ -25,6 +27,6 @@ mod timezones;
#[group]
#[commands(
ping, stats, shutdown, time, timezones, qalc, about, add_gif, gifs, pain
ping, stats, shutdown, time, timezones, qalc, about, add_gif, gifs, pain, clear
)]
pub struct Misc;

@ -9,9 +9,7 @@ use serenity::prelude::*;
use sysinfo::{ProcessExt, SystemExt};
use crate::commands::common::handle_autodelete;
use crate::providers::music::queue::MusicQueue;
use crate::utils::context_data::{get_database_from_context, Store};
use std::sync::Arc;
use crate::utils::context_data::{get_database_from_context, MusicPlayers};
#[command]
#[description("Shows some statistics about the bot")]
@ -94,23 +92,7 @@ async fn stats(ctx: &Context, msg: &Message) -> CommandResult {
/// Returns the total number of queues that are not
/// flagged to leave
async fn get_queue_count(ctx: &Context) -> usize {
let queues: Vec<Arc<Mutex<MusicQueue>>> = {
let data = ctx.data.read().await;
let store = data.get::<Store>().unwrap();
store
.music_queues
.iter()
.map(|(_, q)| Arc::clone(q))
.collect()
};
let mut count = 0;
for queue in queues {
let queue = queue.lock().await;
if !queue.leave_flag {
count += 1;
}
}
count
let data = ctx.data.read().await;
let players = data.get::<MusicPlayers>().unwrap();
players.len()
}

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

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

@ -4,7 +4,8 @@ use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_channel_for_author, is_dj, join_channel};
use crate::commands::music::{get_channel_for_author, get_music_player_for_guild, is_dj};
use crate::providers::music::player::MusicPlayer;
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
use serenity::model::id::ChannelId;
@ -33,8 +34,15 @@ async fn join(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
get_channel_for_author(&msg.author.id, &guild)
)
};
if get_music_player_for_guild(ctx, guild.id).await.is_some() {
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("‼️ I'm already in a Voice Channel")
})
.await?;
return Ok(());
}
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, msg.channel_id).await?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("🎤 Joined the Voice Channel")
})

@ -4,8 +4,8 @@ use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_voice_manager, DJ_CHECK};
use crate::utils::context_data::Store;
use crate::commands::music::DJ_CHECK;
use crate::utils::context_data::MusicPlayers;
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
@ -20,40 +20,30 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Leave request received for guild {}", guild.id);
let manager = get_voice_manager(ctx).await;
let 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 {
let manager = songbird::get(ctx).await.unwrap();
if let Some(handler) = manager.get(guild.id) {
let mut handler_lock = handler.lock().await;
handler_lock.remove_all_global_events();
let _ = handler_lock.leave().await;
}
if manager.get(guild.id).is_some() {
if let Some((current, _)) = queue_lock.current() {
current.stop()?;
let mut data = ctx.data.write().await;
let players = data.get_mut::<MusicPlayers>().unwrap();
match players.remove(&guild.id.0) {
None => {
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("‼️ I'm not in a Voice Channel")
})
.await?;
}
Some(player) => {
let mut player = player.lock().await;
player.stop().await?;
player.delete_now_playing().await?;
}
manager.remove(guild.id).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");
}
manager.remove(guild.id).await?;
handle_autodelete(ctx, msg).await?;
Ok(())

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

@ -1,21 +1,14 @@
use std::mem;
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use aspotify::Track;
use regex::Regex;
use serenity::async_trait;
use serenity::client::Context;
use serenity::framework::standard::macros::{check, group};
use serenity::http::Http;
use serenity::model::channel::Message;
use serenity::model::guild::Guild;
use serenity::model::id::{ChannelId, GuildId, UserId};
use serenity::model::user::User;
use songbird::{
Call, Event, EventContext, EventHandler as VoiceEventHandler, Songbird, TrackEvent,
};
use songbird::Songbird;
use tokio::sync::Mutex;
use clear_queue::CLEAR_QUEUE_COMMAND;
@ -34,11 +27,11 @@ use save_playlist::SAVE_PLAYLIST_COMMAND;
use shuffle::SHUFFLE_COMMAND;
use skip::SKIP_COMMAND;
use crate::messages::music::now_playing::update_now_playing_msg;
use crate::providers::music::queue::{MusicQueue, Song};
use crate::providers::music::player::MusicPlayer;
use crate::providers::music::queue::Song;
use crate::providers::music::{add_youtube_song_to_database, youtube_dl};
use crate::providers::settings::{get_setting, Setting};
use crate::utils::context_data::{DatabaseContainer, Store};
use crate::utils::context_data::{DatabaseContainer, MusicPlayers, Store};
use crate::utils::error::{BotError, BotResult};
use bot_database::Database;
use futures::future::BoxFuture;
@ -82,116 +75,12 @@ mod skip;
)]
pub struct Music;
struct SongEndNotifier {
channel_id: ChannelId,
http: Arc<Http>,
queue: Arc<Mutex<MusicQueue>>,
handler: Arc<Mutex<Call>>,
}
#[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 {
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();
}
if let Some((current, _)) = queue_lock.current() {
let _ = current.stop();
}
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)
/// Returns the voice manager from the context
pub async fn get_voice_manager(ctx: &Context) -> Arc<Songbird> {
songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
let (handler, _) = manager.join(guild_id, channel_id).await;
let mut data = ctx.data.write().await;
let store = data.get_mut::<Store>().unwrap();
log::debug!("Creating new queue");
let queue = Arc::new(Mutex::new(MusicQueue::new()));
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: channel_id.clone(),
http: ctx.http.clone(),
queue: Arc::clone(&queue),
handler: handler.clone(),
},
);
handler_lock.add_global_event(
Event::Periodic(Duration::from_secs(60), None),
ChannelDurationNotifier {
channel_id,
guild_id,
count: Default::default(),
queue: Arc::clone(&queue),
handler: handler.clone(),
leave_in: Arc::new(AtomicIsize::new(5)),
manager: manager.clone(),
},
);
}
handler
.clone()
}
/// Returns the voice channel the author is in
@ -203,88 +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"))
}
/// Returns the voice manager from the context
pub async fn get_voice_manager(ctx: &Context) -> Arc<Songbird> {
songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone()
}
/// Returns a reference to a guilds music queue
pub(crate) async fn get_queue_for_guild(
/// Returns the music player for a given guild
pub async fn get_music_player_for_guild(
ctx: &Context,
guild_id: &GuildId,
) -> BotResult<Arc<Mutex<MusicQueue>>> {
guild_id: GuildId,
) -> Option<Arc<Mutex<MusicPlayer>>> {
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
async fn play_next_in_queue(
http: &Arc<Http>,
channel_id: &ChannelId,
queue: &Arc<Mutex<MusicQueue>>,
handler: &Arc<Mutex<Call>>,
) -> 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 source = match songbird::ytdl(&url).await {
Ok(s) => s,
Err(e) => {
let _ = channel_id
.say(
&http,
format!("Failed to enqueue {}: {:?}", next.title(), e),
)
.await;
return false;
}
};
let mut handler_lock = handler.lock().await;
let track = handler_lock.play_only_source(source);
log::trace!("Track is {:?}", track);
if queue_lock.paused() {
let _ = track.pause();
}
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
{
log::error!("Failed to update now playing message: {:?}", e);
}
}
queue_lock.set_current(track, next);
} else {
if let Some(np) = mem::take(&mut queue_lock.now_playing_msg) {
let np = np.read().await;
if let Ok(message) = np.get_message(http).await {
let _ = message.delete(http).await;
}
}
queue_lock.clear_current();
}
true
players.get(&guild_id.0).cloned()
}
/// Returns the list of songs for a given url
@ -473,16 +289,18 @@ async fn get_youtube_song_for_track(database: &Database, track: Track) -> BotRes
if let Some(id) = track.id {
let entry = database.get_song(&id).await?;
if let Some(song) = &entry {
if let Some(song) = entry {
// check if the video is still available
if get_video_information(&song.url).await.is_err() {
log::trace!("Found entry is {:?}", song);
if let Ok(info) = get_video_information(&song.url).await {
return Ok(Some(info.into()));
} else {
log::debug!("Video '{}' is not available. Deleting entry", song.url);
database.delete_song(song.id).await?;
return Ok(None);
}
}
log::trace!("Found entry is {:?}", entry);
Ok(entry.map(Song::from))
Ok(None)
} else {
log::debug!("Track has no ID");
Ok(None)

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

@ -1,11 +1,11 @@
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::framework::standard::{CommandError, CommandResult};
use serenity::model::channel::Message;
use serenity::prelude::*;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, DJ_CHECK};
use crate::messages::music::now_playing::update_now_playing_msg;
use crate::commands::music::{get_music_player_for_guild, DJ_CHECK};
use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
@ -19,37 +19,33 @@ async fn pause(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Pausing playback for guild {}", guild.id);
let queue = forward_error!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let mut queue_lock = queue.lock().await;
let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {
player
} else {
return create_no_voicechannel_message(&ctx.http, msg.channel_id)
.await
.map_err(CommandError::from);
};
let mut player = player.lock().await;
if let Some(_) = player.queue().current() {
player.toggle_paused().await?;
let is_paused = player.is_paused();
if let Some(_) = queue_lock.current() {
queue_lock.pause();
if queue_lock.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?;
}
player.update_now_playing().await?;
} else {
log::debug!("Resumed");
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("▶ Resumed playback")
})
.await?;
if let (Some(menu), Some((current, _))) =
(&queue_lock.now_playing_msg, queue_lock.current())
{
update_now_playing_msg(&ctx.http, menu, current.metadata(), true).await?;
}
player.update_now_playing().await?;
}
} else {
msg.channel_id.say(ctx, "Nothing to pause").await?;

@ -1,15 +1,16 @@
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;
use crate::commands::music::{
get_channel_for_author, get_queue_for_guild, get_songs_for_query, get_voice_manager,
join_channel, play_next_in_queue,
get_channel_for_author, get_music_player_for_guild, get_songs_for_query,
};
use crate::messages::music::now_playing::create_now_playing_msg;
use crate::providers::music::player::MusicPlayer;
use crate::providers::settings::{get_setting, Setting};
use std::sync::Arc;
#[command]
#[only_in(guilds)]
@ -24,31 +25,22 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Play request received for guild {}", guild.id);
let manager = get_voice_manager(ctx).await;
let mut handler = manager.get(guild.id);
let mut player = get_music_player_for_guild(ctx, guild.id).await;
if handler.is_none() {
if player.is_none() {
log::debug!("Not in a channel. Joining authors channel...");
msg.guild(&ctx.cache).await.unwrap();
let channel_id = get_channel_for_author(&msg.author.id, &guild)?;
handler = Some(join_channel(ctx, channel_id, guild.id).await);
let music_player = MusicPlayer::join(ctx, guild.id, channel_id, msg.channel_id).await?;
player = Some(music_player);
}
let handler_lock = forward_error!(
ctx,
msg.channel_id,
handler.ok_or(CommandError::from("I'm not in a voice channel"))
);
let player = player.unwrap();
let songs = get_songs_for_query(&ctx, msg, query).await?;
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let (play_first, create_now_playing) = {
log::debug!("Adding song to queue");
let mut queue_lock = queue.lock().await;
let mut player_lock = player.lock().await;
for song in songs {
queue_lock.add(song);
player_lock.queue().add(song);
}
let autoshuffle = get_setting(ctx, guild.id, Setting::MusicAutoShuffle)
.await?
@ -56,22 +48,23 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
if autoshuffle {
log::debug!("Autoshuffeling");
queue_lock.shuffle();
player_lock.queue().shuffle();
}
(
queue_lock.current().is_none(),
queue_lock.now_playing_msg.is_none(),
player_lock.queue().current().is_none(),
player_lock.now_playing_message().is_none(),
)
};
if play_first {
log::debug!("Playing first song in queue");
while !play_next_in_queue(&ctx.http, &msg.channel_id, &queue, &handler_lock).await {}
let mut player_lock = player.lock().await;
player_lock.play_next().await?;
}
if create_now_playing {
let handle = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?;
let mut queue_lock = queue.lock().await;
queue_lock.now_playing_msg = Some(handle);
let handle = create_now_playing_msg(ctx, Arc::clone(&player), msg.channel_id).await?;
let mut player_lock = player.lock().await;
player_lock.set_now_playing(handle).await;
}
handle_autodelete(ctx, msg).await?;

@ -1,14 +1,15 @@
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;
use crate::commands::music::{
get_channel_for_author, get_queue_for_guild, get_songs_for_query, get_voice_manager,
join_channel, play_next_in_queue, DJ_CHECK,
get_channel_for_author, get_music_player_for_guild, get_songs_for_query, DJ_CHECK,
};
use crate::messages::music::now_playing::create_now_playing_msg;
use crate::providers::music::player::MusicPlayer;
use std::sync::Arc;
#[command]
#[only_in(guilds)]
@ -23,46 +24,41 @@ async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Playing song as next song for guild {}", guild.id);
let manager = get_voice_manager(ctx).await;
let mut handler = manager.get(guild.id);
if handler.is_none() {
log::debug!("Not in a voice channel. Joining authors channel");
msg.guild(&ctx.cache).await.unwrap();
let mut player = get_music_player_for_guild(ctx, guild.id).await;
if player.is_none() {
log::debug!("Not in a channel. Joining authors channel...");
let channel_id = get_channel_for_author(&msg.author.id, &guild)?;
handler = Some(join_channel(ctx, channel_id, guild.id).await);
let music_player = MusicPlayer::join(ctx, guild.id, channel_id, msg.channel_id).await?;
player = Some(music_player);
}
let handler = forward_error!(
ctx,
msg.channel_id,
handler.ok_or(CommandError::from("I'm not in a voice channel"))
);
let player = player.unwrap();
let mut songs = get_songs_for_query(&ctx, msg, query).await?;
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let (play_first, create_now_playing) = {
let mut queue_lock = queue.lock().await;
let mut player_lock = player.lock().await;
songs.reverse();
log::debug!("Enqueueing songs as next songs in the queue");
for song in songs {
queue_lock.add_next(song);
player_lock.queue().add_next(song);
}
(
queue_lock.current().is_none(),
queue_lock.now_playing_msg.is_none(),
player_lock.queue().current().is_none(),
player_lock.now_playing_message().is_none(),
)
};
if play_first {
while !play_next_in_queue(&ctx.http, &msg.channel_id, &queue, &handler).await {}
let mut player_lock = player.lock().await;
player_lock.play_next().await?;
}
if create_now_playing {
let handle = create_now_playing_msg(ctx, queue.clone(), msg.channel_id).await?;
let mut queue_lock = queue.lock().await;
queue_lock.now_playing_msg = Some(handle);
let handle = create_now_playing_msg(ctx, Arc::clone(&player), msg.channel_id).await?;
let mut player_lock = player.lock().await;
player_lock.set_now_playing(handle).await;
}
handle_autodelete(ctx, msg).await?;

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

@ -1,10 +1,11 @@
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, DJ_CHECK};
use crate::commands::music::{get_music_player_for_guild, DJ_CHECK};
use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::framework::standard::{Args, CommandError, CommandResult};
use serenity::model::channel::Message;
#[command]
@ -21,15 +22,17 @@ async fn remove_song(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes
log::debug!("Moving song for guild {}", guild.id);
let pos = args.single::<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!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let mut queue_lock = queue.lock().await;
queue_lock.remove(pos);
let mut player = player.lock().await;
player.queue().remove(pos);
}
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {

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

@ -1,10 +1,11 @@
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::framework::standard::{CommandError, CommandResult};
use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, DJ_CHECK};
use crate::commands::music::{get_music_player_for_guild, DJ_CHECK};
use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
@ -18,15 +19,17 @@ 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 player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {
player
} else {
return create_no_voicechannel_message(&ctx.http, msg.channel_id)
.await
.map_err(CommandError::from);
};
{
let mut player = player.lock().await;
player.skip().await?;
}
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::prelude::*;
use crate::commands::music::get_queue_for_guild;
use crate::commands::music::get_music_player_for_guild;
use crate::utils::delete_messages_from_database;
use bot_serenityutils::menu::{
handle_message_delete, handle_message_delete_bulk, handle_reaction_add, handle_reaction_remove,
@ -120,10 +120,10 @@ impl EventHandler for Handler {
if let Some(count) = member_count {
log::debug!("{} Members in channel", count);
if let Ok(queue) = get_queue_for_guild(&ctx, &guild_id).await {
let mut queue_lock = queue.lock().await;
if let Some(player) = get_music_player_for_guild(&ctx, guild_id).await {
let mut player = player.lock().await;
log::debug!("Setting leave flag to {}", count == 0);
queue_lock.leave_flag = count == 0;
player.set_leave_flag(count == 0);
}
}
}

@ -1,2 +1,3 @@
pub mod no_voicechannel;
pub mod now_playing;
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(())
}

@ -3,13 +3,13 @@ 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::commands::music::{get_music_player_for_guild, get_voice_manager, is_dj};
use crate::messages::add_ephemeral_handle_to_database;
use crate::providers::music::add_youtube_song_to_database;
use crate::providers::music::queue::MusicQueue;
use crate::utils::context_data::{DatabaseContainer, Store};
use crate::providers::music::player::MusicPlayer;
use crate::providers::music::queue::Song;
use crate::utils::context_data::{DatabaseContainer, MusicPlayers, Store};
use crate::utils::error::*;
use bot_serenityutils::core::MessageHandle;
use bot_serenityutils::error::SerenityUtilsResult;
@ -30,7 +30,7 @@ static GOOD_PICK_BUTTON: &str = "👍";
/// Creates a new now playing message and returns the embed for that message
pub async fn create_now_playing_msg(
ctx: &Context,
queue: Arc<Mutex<MusicQueue>>,
player: Arc<Mutex<MusicPlayer>>,
channel_id: ChannelId,
) -> BotResult<Arc<RwLock<MessageHandle>>> {
log::debug!("Creating now playing menu");
@ -61,16 +61,20 @@ pub async fn create_now_playing_msg(
)
.show_help()
.add_page(Page::new_builder(move || {
let queue = Arc::clone(&queue);
let player = Arc::clone(&player);
Box::pin(async move {
log::debug!("Creating now playing embed for page");
let queue = queue.lock().await;
log::debug!("Queue locked");
let mut player = player.lock().await;
log::debug!("player locked");
let mut page = CreateMessage::default();
if let Some((current, _)) = queue.current() {
if let Some(mut current) = player.queue().current().clone() {
let mut embed = CreateEmbed::default();
create_now_playing_embed(&mut current, &mut embed, player.is_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 +98,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 +106,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 +122,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 +145,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);
}
}
@ -157,22 +167,22 @@ async fn play_pause_button_action(
return Ok(());
}
{
let queue = get_queue_for_guild(ctx, &guild_id).await?;
let player = get_music_player_for_guild(ctx, guild_id).await.unwrap();
let (current, message, paused) = {
log::debug!("Queue is locked");
let mut queue = queue.lock().await;
queue.pause();
let mut player = player.lock().await;
player.toggle_paused().await?;
(
queue.current().clone(),
queue.now_playing_msg.clone().unwrap(),
queue.paused(),
player.queue().current().clone(),
player.now_playing_message().clone().unwrap(),
player.is_paused(),
)
};
log::debug!("Queue is unlocked");
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 +201,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 player = get_music_player_for_guild(ctx, guild_id).await.unwrap();
let mut player = player.lock().await;
player.skip().await?;
}
Ok(())
@ -220,21 +225,24 @@ 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);
if let Some(handler) = handler {
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)?;
let _ = handler_lock.leave().await;
}
if manager.get(guild_id).is_some() {
manager.remove(guild_id).await.map_err(BotError::from)?;
let mut data = ctx.data.write().await;
let players = data.get_mut::<MusicPlayers>().unwrap();
if let Some(player) = players.remove(&guild_id.0) {
let mut player = player.lock().await;
player.stop().await?;
}
log::debug!("Left the voice channel");
} else {
log::debug!("Not in a voice channel");
@ -257,10 +265,10 @@ async fn good_pick_action(
reaction: Reaction,
) -> SerenityUtilsResult<()> {
let guild_id = reaction.guild_id.unwrap();
let queue = get_queue_for_guild(ctx, &guild_id).await?;
let queue = queue.lock().await;
let player = get_music_player_for_guild(ctx, guild_id).await.unwrap();
let mut player = player.lock().await;
if let Some((_, song)) = queue.current() {
if let Some(song) = player.queue().current() {
let data = ctx.data.read().await;
let store = data.get::<Store>().unwrap();
let database = data.get::<DatabaseContainer>().unwrap();
@ -281,9 +289,9 @@ async fn delete_action(
handle.clone()
};
{
let queue = get_queue_for_guild(ctx, &guild_id).await?;
let mut queue = queue.lock().await;
queue.now_playing_msg = None;
let player = get_music_player_for_guild(ctx, guild_id).await.unwrap();
let mut player = player.lock().await;
player.clear_now_playing();
}
ctx.http
.delete_message(handle.channel_id, handle.message_id)

@ -0,0 +1,44 @@
use crate::utils::context_data::MusicPlayers;
use lavalink_rs::gateway::LavalinkEventHandler;
use lavalink_rs::model::{TrackFinish, TrackStart};
use lavalink_rs::LavalinkClient;
use serenity::async_trait;
use serenity::prelude::TypeMapKey;
use std::sync::Arc;
use tokio::sync::RwLock;
use typemap_rev::TypeMap;
pub struct LavalinkHandler {
pub data: Arc<RwLock<TypeMap>>,
}
#[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, _: LavalinkClient, event: TrackFinish) {
log::info!("Track finished!\nGuild: {}", event.guild_id);
let player = {
let data = self.data.read().await;
let players = data.get::<MusicPlayers>().unwrap();
players.get(&event.guild_id).cloned()
};
if let Some(player) = player {
let mut player = player.lock().await;
if let Err(e) = player.play_next().await {
log::error!("Failed to play next song: {:?}", e);
}
if let Err(e) = player.update_now_playing().await {
log::error!("Failed to update now playing embed: {:?}", e);
}
}
}
}
pub struct Lavalink;
impl TypeMapKey for Lavalink {
type Value = Arc<LavalinkClient>;
}

@ -7,11 +7,13 @@ use regex::Regex;
use responses::VideoInformation;
use youtube_dl::search_video_information;
pub(crate) mod lyrics;
pub(crate) mod queue;
pub(crate) mod responses;
pub(crate) mod spotify;
pub(crate) mod youtube_dl;
pub mod lavalink;
pub mod lyrics;
pub mod player;
pub mod queue;
pub mod responses;
pub mod spotify;
pub mod youtube_dl;
/// Searches for a youtube video for the specified song
pub(crate) async fn song_to_youtube_video(song: &Song) -> BotResult<Option<VideoInformation>> {
@ -38,10 +40,12 @@ pub(crate) async fn song_to_youtube_video(song: &Song) -> BotResult<Option<Video
{
return Ok(Some(video));
}
log::debug!("Video title is not similar enough to song name.");
last_result = Some(video);
}
}
log::debug!("Returning last result");
Ok(last_result)
}

@ -0,0 +1,280 @@
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, SHORT_TIMEOUT};
use bot_serenityutils::ephemeral_message::EphemeralMessage;
use lavalink_rs::LavalinkClient;
use serenity::prelude::TypeMap;
use serenity::{
client::Context,
http::Http,
model::id::{ChannelId, GuildId},
};
use songbird::Songbird;
use std::mem;
use std::sync::Arc;
use std::time::Duration;
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>>>,
msg_channel: ChannelId,
leave_flag: bool,
paused: bool,
}
impl MusicPlayer {
/// Creates a new music player
pub fn new(
client: Arc<LavalinkClient>,
http: Arc<Http>,
guild_id: GuildId,
msg_channel: ChannelId,
) -> Self {
Self {
client,
http,
guild_id,
queue: MusicQueue::new(),
msg_channel,
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,
msg_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,
msg_channel_id,
);
let player = Arc::new(Mutex::new(player));
let players = data.get_mut::<MusicPlayers>().unwrap();
players.insert(guild_id.0, Arc::clone(&player));
player
};
wait_for_disconnect(
Arc::clone(&ctx.data),
Arc::clone(&player),
manager,
guild_id,
);
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() {
log::trace!("Next is {:?}", n);
n
} else {
return Ok(true);
};
let url = if let Some(url) = next.url().await {
url
} else {
self.send_error_message(format!(
"‼️ Could not find a video to play for '{}' by '{}'",
next.title(),
next.author()
))
.await?;
log::debug!("Could not find playable candidate for song.");
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);
self.send_error_message(format!(
"‼️ Failed to retrieve information for song '{}' by '{}': {:?}",
next.title(),
next.author(),
e
))
.await?;
return Ok(false);
}
};
if query_information.tracks.len() == 0 {
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;
}
/// Sends a play error message to the players test channel
async fn send_error_message(&self, content: String) -> BotResult<()> {
EphemeralMessage::create(&self.http, self.msg_channel, SHORT_TIMEOUT, |m| {
m.content(content)
})
.await?;
Ok(())
}
}
/// Stats a tokio coroutine to check for player disconnect conditions
fn wait_for_disconnect(
data: Arc<RwLock<TypeMap>>,
player: Arc<Mutex<MusicPlayer>>,
manager: Arc<Songbird>,
guild_id: GuildId,
) {
let mut leave_in: i32 = 5;
tokio::spawn(async move {
loop {
tokio::time::sleep(Duration::from_secs(60)).await;
if manager.get(guild_id).is_none() {
return; // leave when there's no connection to handle
}
let mut player_lock = player.lock().await;
if player_lock.leave_flag {
log::debug!("Waiting to leave");
if leave_in <= 0 {
log::debug!("Leaving voice channel");
if let Some(handler) = manager.get(guild_id) {
let mut handler_lock = handler.lock().await;
let _ = handler_lock.leave().await;
}
let _ = manager.remove(guild_id).await;
let mut data = data.write().await;
let players = data.get_mut::<MusicPlayers>().unwrap();
players.remove(&guild_id.0);
let _ = player_lock.stop().await;
let _ = player_lock.delete_now_playing().await;
log::debug!("Left the voice channel");
return;
}
leave_in -= 1;
} else {
log::debug!("Resetting leave value");
leave_in = 5
}
}
});
}

@ -1,23 +1,17 @@
use std::collections::VecDeque;
use aspotify::Track;
use songbird::tracks::TrackHandle;
use bot_coreutils::shuffle::Shuffle;
use crate::providers::music::responses::{PlaylistEntry, VideoInformation};
use crate::providers::music::song_to_youtube_video;
use bot_database::models::YoutubeSong;
use bot_serenityutils::core::MessageHandle;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
pub struct MusicQueue {
inner: VecDeque<Song>,
current: Option<(TrackHandle, Song)>,
paused: bool,
pub now_playing_msg: Option<Arc<RwLock<MessageHandle>>>,
current: Option<Song>,
pub leave_flag: bool,
}
@ -26,9 +20,7 @@ impl MusicQueue {
Self {
inner: VecDeque::new(),
current: None,
paused: false,
leave_flag: false,
now_playing_msg: None,
}
}
@ -58,17 +50,12 @@ impl MusicQueue {
}
/// Sets the currently playing song
pub fn set_current(&mut self, handle: TrackHandle, song: Song) {
self.current = Some((handle, song))
}
/// Clears the currently playing song
pub fn clear_current(&mut self) {
self.current = None;
pub fn set_current(&mut self, song: Song) {
self.current = Some(song)
}
/// Returns the reference to the currently playing song
pub fn current(&self) -> &Option<(TrackHandle, Song)> {
pub fn current(&self) -> &Option<Song> {
&self.current
}
@ -88,26 +75,6 @@ impl MusicQueue {
pub fn remove(&mut self, index: usize) {
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
}
}
#[derive(Clone, Debug)]
@ -118,11 +85,11 @@ pub enum SongSource {
#[derive(Clone, Debug)]
pub struct Song {
url: Option<String>,
title: String,
author: String,
thumbnail: Option<String>,
source: SongSource,
pub(crate) url: Option<String>,
pub(crate) title: String,
pub(crate) author: String,
pub(crate) thumbnail: Option<String>,
pub(crate) source: SongSource,
}
impl Song {

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

@ -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