Add logging with fern

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/2/head
trivernis 3 years ago
parent 288675c2ea
commit b4e874ae07
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

3
.gitignore vendored

@ -1,4 +1,5 @@
/target /target
bot.db bot.db
.env .env
*.env *.env
logs

35
Cargo.lock generated

@ -100,6 +100,17 @@ dependencies = [
"webpki-roots 0.21.1", "webpki-roots 0.21.1",
] ]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"libc",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "audiopus" name = "audiopus"
version = "0.2.0" version = "0.2.0"
@ -223,6 +234,17 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "colored"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
dependencies = [
"atty",
"lazy_static",
"winapi 0.3.9",
]
[[package]] [[package]]
name = "command_attr" name = "command_attr"
version = "0.3.5" version = "0.3.5"
@ -354,6 +376,15 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fern"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065"
dependencies = [
"log 0.4.14",
]
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.20" version = "1.0.20"
@ -1868,9 +1899,13 @@ name = "tobi-rs"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"aspotify", "aspotify",
"chrono",
"colored",
"dotenv", "dotenv",
"fern",
"futures", "futures",
"lazy_static", "lazy_static",
"log 0.4.14",
"minecraft-data-rs", "minecraft-data-rs",
"rand 0.8.3", "rand 0.8.3",
"regex", "regex",

@ -22,4 +22,8 @@ rand = "0.8.3"
regex = "1.4.5" regex = "1.4.5"
aspotify = "0.7.0" aspotify = "0.7.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
futures = "0.3.13" futures = "0.3.13"
log = "0.4.14"
fern = "0.6.0"
chrono = "0.4.19"
colored = "2.0.0"

@ -1,5 +1,4 @@
use serenity::async_trait; use serenity::client::Context;
use serenity::client::{Context, EventHandler};
use serenity::framework::standard::macros::hook; use serenity::framework::standard::macros::hook;
use serenity::framework::standard::{CommandResult, DispatchError}; use serenity::framework::standard::{CommandResult, DispatchError};
use serenity::framework::StandardFramework; use serenity::framework::StandardFramework;
@ -9,19 +8,16 @@ use songbird::SerenityInit;
use crate::commands::*; use crate::commands::*;
use crate::database::get_database; use crate::database::get_database;
use crate::handler::Handler;
use crate::utils::error::{BotError, BotResult}; use crate::utils::error::{BotError, BotResult};
use crate::utils::store::{Store, StoreData}; use crate::utils::store::{Store, StoreData};
struct Handler;
#[async_trait]
impl EventHandler for Handler {}
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 = dotenv::var("BOT_TOKEN").map_err(|_| BotError::MissingToken)?;
let database = get_database()?; let database = get_database()?;
let client = Client::builder(token) let client = Client::builder(token)
.event_handler(Handler)
.framework(get_framework()) .framework(get_framework())
.register_songbird() .register_songbird()
.await?; .await?;
@ -61,18 +57,16 @@ async fn after_hook(ctx: &Context, msg: &Message, cmd_name: &str, error: Command
let _ = msg let _ = msg
.channel_id .channel_id
.send_message(ctx, |m| { .send_message(ctx, |m| {
m.embed(|e| { m.embed(|e| e.title("Error occurred").description(format!("{}", why)))
e.title("Error occurred")
.description(format!("```\n{}\n```", why))
})
}) })
.await; .await;
println!("Error in {}: {:?}", cmd_name, why); log::warn!("Error in {}: {:?}", cmd_name, why);
} }
} }
#[hook] #[hook]
async fn before_hook(ctx: &Context, msg: &Message, _: &str) -> bool { async fn before_hook(ctx: &Context, msg: &Message, _: &str) -> bool {
log::trace!("Got command message {}", msg.content);
let _ = msg.channel_id.broadcast_typing(ctx).await; let _ = msg.channel_id.broadcast_typing(ctx).await;
true true
} }

@ -1,5 +1,5 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::{Args, CommandError, CommandResult, macros::command}; use serenity::framework::standard::{macros::command, Args, CommandError, CommandResult};
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::utils::store::Store; use crate::utils::store::Store;
@ -14,6 +14,8 @@ pub(crate) async fn enchantment(ctx: &Context, msg: &Message, args: Args) -> Com
let data = ctx.data.read().await; let data = ctx.data.read().await;
let store = data.get::<Store>().expect("Failed to get store"); let store = data.get::<Store>().expect("Failed to get store");
let enchantment_name = args.message().to_lowercase(); let enchantment_name = args.message().to_lowercase();
log::debug!("Searching for enchantment {}", enchantment_name);
let enchantments_by_name = store let enchantments_by_name = store
.minecraft_data_api .minecraft_data_api
.enchantments .enchantments
@ -25,6 +27,7 @@ pub(crate) async fn enchantment(ctx: &Context, msg: &Message, args: Args) -> Com
enchantment_name enchantment_name
)))? )))?
.clone(); .clone();
log::trace!("Enchantment is {:?}", enchantment);
msg.channel_id msg.channel_id
.send_message(ctx, |m| { .send_message(ctx, |m| {

@ -15,6 +15,7 @@ pub(crate) async fn item(ctx: &Context, msg: &Message, args: Args) -> CommandRes
let store = data.get::<Store>().expect("Failed to get store"); let store = data.get::<Store>().expect("Failed to get store");
let item_name = args.message().to_lowercase(); let item_name = args.message().to_lowercase();
log::debug!("Searching for item '{}'", item_name);
let items_by_name = store.minecraft_data_api.items.items_by_name()?; let items_by_name = store.minecraft_data_api.items.items_by_name()?;
let item = items_by_name let item = items_by_name
.get(&item_name) .get(&item_name)
@ -26,6 +27,7 @@ pub(crate) async fn item(ctx: &Context, msg: &Message, args: Args) -> CommandRes
.minecraft_data_api .minecraft_data_api
.enchantments .enchantments
.enchantments_by_category()?; .enchantments_by_category()?;
log::trace!("Item is {:?}", item);
msg.channel_id msg.channel_id
.send_message(ctx, |m| { .send_message(ctx, |m| {

@ -1,13 +1,14 @@
use std::collections::HashSet; use std::collections::HashSet;
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::{Args, help_commands};
use serenity::framework::standard::{CommandGroup, CommandResult, HelpOptions};
use serenity::framework::standard::macros::help; use serenity::framework::standard::macros::help;
use serenity::framework::standard::{help_commands, Args};
use serenity::framework::standard::{CommandGroup, CommandResult, HelpOptions};
use serenity::model::channel::Message; use serenity::model::channel::Message;
use serenity::model::id::UserId; use serenity::model::id::UserId;
#[help] #[help]
#[max_levenshtein_distance(2)]
pub async fn help( pub async fn help(
ctx: &Context, ctx: &Context,
msg: &Message, msg: &Message,

@ -1,6 +1,6 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::CommandResult;
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::music::get_queue_for_guild; use crate::commands::music::get_queue_for_guild;
@ -13,6 +13,7 @@ use crate::commands::music::get_queue_for_guild;
#[allowed_roles("DJ")] #[allowed_roles("DJ")]
async fn clear(ctx: &Context, msg: &Message) -> CommandResult { async fn clear(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Clearing queue for guild {}", guild.id);
let queue = get_queue_for_guild(ctx, &guild.id).await?; let queue = get_queue_for_guild(ctx, &guild.id).await?;
{ {

@ -1,6 +1,6 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::CommandResult;
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::music::get_queue_for_guild; use crate::commands::music::get_queue_for_guild;
@ -13,11 +13,13 @@ use crate::commands::music::get_queue_for_guild;
async fn current(ctx: &Context, msg: &Message) -> CommandResult { async fn current(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Displaying current song for queue in {}", guild.id);
let queue = get_queue_for_guild(ctx, &guild.id).await?; let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue_lock = queue.lock().await; let queue_lock = queue.lock().await;
if let Some(current) = queue_lock.current() { if let Some(current) = queue_lock.current() {
let metadata = current.metadata().clone(); let metadata = current.metadata().clone();
log::trace!("Metadata is {:?}", metadata);
msg.channel_id msg.channel_id
.send_message(ctx, |m| { .send_message(ctx, |m| {
m.embed(|mut e| { m.embed(|mut e| {

@ -12,6 +12,7 @@ use crate::commands::music::{get_channel_for_author, join_channel};
async fn join(ctx: &Context, msg: &Message) -> CommandResult { async fn join(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = 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)?;
log::debug!("Joining channel {} for guild {}", channel_id, guild.id);
join_channel(ctx, channel_id, guild.id).await; join_channel(ctx, channel_id, guild.id).await;
Ok(()) Ok(())

@ -1,6 +1,6 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::CommandResult;
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::music::{get_queue_for_guild, get_voice_manager}; use crate::commands::music::{get_queue_for_guild, get_voice_manager};
@ -14,9 +14,11 @@ use crate::commands::music::{get_queue_for_guild, get_voice_manager};
async fn leave(ctx: &Context, msg: &Message) -> CommandResult { async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Leave request received for guild {}", guild.id);
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 = get_queue_for_guild(ctx, &guild.id).await?;
let queue_lock = queue.lock().await; let queue_lock = queue.lock().await;
log::trace!("Queue is {:?}", queue_lock);
let handler = manager.get(guild.id); let handler = manager.get(guild.id);
if let Some(handler) = handler { if let Some(handler) = handler {
@ -30,8 +32,10 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
if manager.get(guild.id).is_some() { if manager.get(guild.id).is_some() {
manager.remove(guild.id).await?; manager.remove(guild.id).await?;
msg.channel_id.say(ctx, "Left the voice channel").await?; msg.channel_id.say(ctx, "Left the voice channel").await?;
log::debug!("Left the voice channel");
} else { } else {
msg.channel_id.say(ctx, "Not in a voice channel").await?; msg.channel_id.say(ctx, "Not in a voice channel").await?;
log::debug!("Not in a voice channel");
} }
Ok(()) Ok(())

@ -48,6 +48,11 @@ pub struct Music;
/// Joins a voice channel /// Joins a voice channel
async fn join_channel(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) -> Arc<Mutex<Call>> { 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) let manager = songbird::get(ctx)
.await .await
.expect("Songbird Voice client placed in at initialisation.") .expect("Songbird Voice client placed in at initialisation.")
@ -56,12 +61,14 @@ async fn join_channel(ctx: &Context, channel_id: ChannelId, guild_id: GuildId) -
let (handler, _) = manager.join(guild_id, channel_id).await; let (handler, _) = manager.join(guild_id, channel_id).await;
let mut data = ctx.data.write().await; let mut data = ctx.data.write().await;
let store = data.get_mut::<Store>().unwrap(); 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()));
store.music_queues.insert(guild_id, queue.clone()); store.music_queues.insert(guild_id, queue.clone());
{ {
let mut handler_lock = handler.lock().await; let mut handler_lock = handler.lock().await;
log::debug!("Registering track end handler");
handler_lock.add_global_event( handler_lock.add_global_event(
Event::Track(TrackEvent::End), Event::Track(TrackEvent::End),
SongEndNotifier { SongEndNotifier {
@ -119,6 +126,7 @@ struct SongEndNotifier {
#[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);
while !play_next_in_queue(&self.http, &self.channel_id, &self.queue, &self.handler).await { while !play_next_in_queue(&self.http, &self.channel_id, &self.queue, &self.handler).await {
tokio::time::sleep(Duration::from_millis(100)).await; tokio::time::sleep(Duration::from_millis(100)).await;
} }
@ -146,6 +154,7 @@ async fn play_next_in_queue(
return false; return false;
} }
}; };
log::debug!("Getting source for song '{}'", url);
let source = match songbird::ytdl(&url).await { let source = match songbird::ytdl(&url).await {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
@ -160,6 +169,7 @@ async fn play_next_in_queue(
}; };
let mut handler_lock = handler.lock().await; let mut handler_lock = handler.lock().await;
let track = handler_lock.play_only_source(source); let track = handler_lock.play_only_source(source);
log::trace!("Track is {:?}", track);
queue_lock.set_current(track); queue_lock.set_current(track);
} else { } else {
queue_lock.clear_current(); queue_lock.clear_current();
@ -180,7 +190,9 @@ async fn get_songs_for_query(ctx: &Context, msg: &Message, query: &str) -> BotRe
let data = ctx.data.read().await; let data = ctx.data.read().await;
let store = data.get::<Store>().unwrap(); let store = data.get::<Store>().unwrap();
log::debug!("Querying play input {}", query);
if YOUTUBE_URL_REGEX.is_match(query) { if YOUTUBE_URL_REGEX.is_match(query) {
log::debug!("Query is youtube video or playlist");
// try fetching the url as a playlist // try fetching the url as a playlist
songs = get_videos_for_playlist(query) songs = get_videos_for_playlist(query)
.await? .await?
@ -190,30 +202,37 @@ async fn get_songs_for_query(ctx: &Context, msg: &Message, query: &str) -> BotRe
// if no songs were found fetch the song as a video // if no songs were found fetch the song as a video
if songs.len() == 0 { if songs.len() == 0 {
log::debug!("Query is youtube video");
let mut song: Song = get_video_information(query).await?.into(); let mut song: Song = get_video_information(query).await?.into();
added_one_msg(&ctx, msg, &mut song).await?; added_one_msg(&ctx, msg, &mut song).await?;
songs.push(song); songs.push(song);
} else { } else {
log::debug!("Query is playlist with {} songs", songs.len());
added_multiple_msg(&ctx, msg, &mut songs).await?; added_multiple_msg(&ctx, msg, &mut songs).await?;
} }
} else if SPOTIFY_PLAYLIST_REGEX.is_match(query) { } else if SPOTIFY_PLAYLIST_REGEX.is_match(query) {
// search for all songs in the playlist and search for them on youtube // search for all songs in the playlist and search for them on youtube
log::debug!("Query is spotify playlist");
songs = store.spotify_api.get_songs_in_playlist(query).await?; songs = store.spotify_api.get_songs_in_playlist(query).await?;
added_multiple_msg(&ctx, msg, &mut songs).await?; added_multiple_msg(&ctx, msg, &mut songs).await?;
} else if SPOTIFY_ALBUM_REGEX.is_match(query) { } else if SPOTIFY_ALBUM_REGEX.is_match(query) {
// fetch all songs in the album and search for them on youtube // fetch all songs in the album and search for them on youtube
log::debug!("Query is spotify album");
songs = store.spotify_api.get_songs_in_album(query).await?; songs = store.spotify_api.get_songs_in_album(query).await?;
added_multiple_msg(&ctx, msg, &mut songs).await?; added_multiple_msg(&ctx, msg, &mut songs).await?;
} else if SPOTIFY_SONG_REGEX.is_match(query) { } else if SPOTIFY_SONG_REGEX.is_match(query) {
// fetch the song name and search it on youtube // fetch the song name and search it on youtube
log::debug!("Query is a spotify song");
let mut song = store.spotify_api.get_song_name(query).await?; let mut song = store.spotify_api.get_song_name(query).await?;
added_one_msg(ctx, msg, &mut song).await?; added_one_msg(ctx, msg, &mut song).await?;
songs.push(song); songs.push(song);
} else { } else {
log::debug!("Query is a youtube search");
let mut song: Song = search_video_information(query.to_string()) let mut song: Song = search_video_information(query.to_string())
.await? .await?
.ok_or(BotError::Msg(format!("Noting found for {}", query)))? .ok_or(BotError::Msg(format!("Noting found for {}", query)))?
.into(); .into();
log::trace!("Search result is {:?}", song);
added_one_msg(&ctx, msg, &mut song).await?; added_one_msg(&ctx, msg, &mut song).await?;
songs.push(song); songs.push(song);

@ -20,11 +20,13 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let query = args.message(); let query = args.message();
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Play request received for guild {}", guild.id);
let manager = get_voice_manager(ctx).await; let manager = get_voice_manager(ctx).await;
let mut handler = manager.get(guild.id); let mut handler = manager.get(guild.id);
if handler.is_none() { if handler.is_none() {
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); handler = Some(join_channel(ctx, channel_id, guild.id).await);
@ -37,6 +39,7 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let queue = get_queue_for_guild(ctx, &guild.id).await?; let queue = get_queue_for_guild(ctx, &guild.id).await?;
let play_first = { let play_first = {
log::debug!("Adding song to queue");
let mut queue_lock = queue.lock().await; let mut queue_lock = queue.lock().await;
for song in songs { for song in songs {
queue_lock.add(song); queue_lock.add(song);
@ -47,12 +50,14 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
.get_guild_setting(&guild.id, SETTING_AUTOSHUFFLE) .get_guild_setting(&guild.id, SETTING_AUTOSHUFFLE)
.unwrap_or(false); .unwrap_or(false);
if autoshuffle { if autoshuffle {
log::debug!("Autoshuffeling");
queue_lock.shuffle(); queue_lock.shuffle();
} }
queue_lock.current().is_none() queue_lock.current().is_none()
}; };
if play_first { if play_first {
log::debug!("Playing first song in queue");
play_next_in_queue(&ctx.http, &msg.channel_id, &queue, &handler_lock).await; play_next_in_queue(&ctx.http, &msg.channel_id, &queue, &handler_lock).await;
} }

@ -19,11 +19,13 @@ async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let query = args.message(); let query = args.message();
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);
let manager = get_voice_manager(ctx).await; let manager = get_voice_manager(ctx).await;
let mut handler = manager.get(guild.id); let mut handler = manager.get(guild.id);
if handler.is_none() { if handler.is_none() {
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); handler = Some(join_channel(ctx, channel_id, guild.id).await);
@ -37,6 +39,7 @@ async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let play_first = { let play_first = {
let mut queue_lock = queue.lock().await; let mut queue_lock = queue.lock().await;
songs.reverse(); songs.reverse();
log::debug!("Enqueueing songs as next songs in the queue");
for song in songs { for song in songs {
queue_lock.add_next(song); queue_lock.add_next(song);

@ -14,6 +14,7 @@ use crate::commands::music::get_queue_for_guild;
#[aliases("q")] #[aliases("q")]
async fn queue(ctx: &Context, msg: &Message) -> CommandResult { async fn queue(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::trace!("Displaying queue for guild {}", guild.id);
let queue = get_queue_for_guild(ctx, &guild.id).await?; let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue_lock = queue.lock().await; let queue_lock = queue.lock().await;
@ -23,6 +24,7 @@ async fn queue(ctx: &Context, msg: &Message) -> CommandResult {
.map(|s| s.title().clone()) .map(|s| s.title().clone())
.enumerate() .enumerate()
.collect(); .collect();
log::trace!("Songs are {:?}", songs);
if songs.len() == 0 { if songs.len() == 0 {
msg.channel_id msg.channel_id
@ -37,13 +39,14 @@ async fn queue(ctx: &Context, msg: &Message) -> CommandResult {
let mut song_list = Vec::new(); let mut song_list = Vec::new();
for i in 0..min(10, songs.len() - 1) { for i in 0..min(10, songs.len() - 1) {
song_list.push(format!("{:0>3} - {}", songs[i].0, songs[i].1)) song_list.push(format!("{:0>3} - {}", songs[i].0 + 1, songs[i].1))
} }
if songs.len() > 10 { if songs.len() > 10 {
song_list.push("...".to_string()); song_list.push("...".to_string());
let last = songs.last().unwrap(); let last = songs.last().unwrap();
song_list.push(format!("{:0>3} - {}", last.0, last.1)) song_list.push(format!("{:0>3} - {}", last.0 + 1, last.1))
} }
log::trace!("Song list is {:?}", song_list);
msg.channel_id msg.channel_id
.send_message(ctx, |m| { .send_message(ctx, |m| {
m.embed(|e| { m.embed(|e| {

@ -14,6 +14,7 @@ use crate::commands::music::get_queue_for_guild;
async fn shuffle(ctx: &Context, msg: &Message) -> CommandResult { async fn shuffle(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Shuffling queue for guild {}", guild.id);
let queue = get_queue_for_guild(ctx, &guild.id).await?; let queue = get_queue_for_guild(ctx, &guild.id).await?;
{ {
let mut queue_lock = queue.lock().await; let mut queue_lock = queue.lock().await;

@ -1,6 +1,6 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::CommandResult;
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::commands::music::get_queue_for_guild; use crate::commands::music::get_queue_for_guild;
@ -14,6 +14,7 @@ use crate::commands::music::get_queue_for_guild;
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);
let queue = get_queue_for_guild(ctx, &guild.id).await?; let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue_lock = queue.lock().await; let queue_lock = queue.lock().await;

@ -1,6 +1,6 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::{Args, CommandResult};
use serenity::framework::standard::macros::command; use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message; use serenity::model::channel::Message;
use crate::database::get_database_from_context; use crate::database::get_database_from_context;
@ -17,8 +17,10 @@ use crate::database::guild::GUILD_SETTINGS;
async fn get(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { async fn get(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let database = get_database_from_context(ctx).await; let database = get_database_from_context(ctx).await;
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Displaying guild setting for guild {}", guild.id);
if let Some(key) = args.single::<String>().ok() { if let Some(key) = args.single::<String>().ok() {
log::debug!("Displaying guild setting of '{}'", key);
let database_lock = database.lock().await; let database_lock = database.lock().await;
let setting = database_lock.get_guild_setting::<String>(&guild.id, &key); let setting = database_lock.get_guild_setting::<String>(&guild.id, &key);
@ -36,6 +38,7 @@ async fn get(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
} }
} }
} else { } else {
log::debug!("Displaying all guild settings");
for key in GUILD_SETTINGS { for key in GUILD_SETTINGS {
let mut kv_pairs = Vec::new(); let mut kv_pairs = Vec::new();
{ {

@ -1,7 +1,7 @@
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
use rusqlite::{Connection, NO_PARAMS, params}; use rusqlite::{params, Connection, NO_PARAMS};
use serenity::client::Context; use serenity::client::Context;
use serenity::model::id::GuildId; use serenity::model::id::GuildId;
use tokio::sync::Mutex; use tokio::sync::Mutex;
@ -10,6 +10,7 @@ use crate::database::guild::GuildSettings;
use crate::database::scripts::{CREATE_SCRIPT, UPDATE_SCRIPT}; use crate::database::scripts::{CREATE_SCRIPT, UPDATE_SCRIPT};
use crate::utils::error::{BotError, BotResult}; use crate::utils::error::{BotError, BotResult};
use crate::utils::store::Store; use crate::utils::store::Store;
use std::fmt::Debug;
pub mod guild; pub mod guild;
pub mod scripts; pub mod scripts;
@ -28,15 +29,21 @@ impl Database {
pub fn init(&self) -> BotResult<()> { pub fn init(&self) -> BotResult<()> {
self.connection.execute(CREATE_SCRIPT, NO_PARAMS)?; self.connection.execute(CREATE_SCRIPT, NO_PARAMS)?;
self.connection.execute(UPDATE_SCRIPT, NO_PARAMS)?; self.connection.execute(UPDATE_SCRIPT, NO_PARAMS)?;
log::info!("Database initialized");
Ok(()) Ok(())
} }
/// Returns a guild setting /// Returns a guild setting
pub fn get_guild_setting<T>(&self, guild_id: &GuildId, key: &str) -> BotResult<T> pub fn get_guild_setting<T>(&self, guild_id: &GuildId, key: &str) -> BotResult<T>
where where
T: Clone + FromStr, T: Clone + FromStr + Debug,
{ {
log::trace!(
"Fetching value of guild setting '{}' for guild {}",
key,
guild_id
);
self.connection self.connection
.query_row( .query_row(
"SELECT guild_id, setting_key, setting_value FROM guild_settings WHERE guild_id = ?1 AND setting_key = ?2", "SELECT guild_id, setting_key, setting_value FROM guild_settings WHERE guild_id = ?1 AND setting_key = ?2",
@ -53,10 +60,11 @@ impl Database {
/// Sets a guild setting and overrides it if it already exists /// Sets a guild setting and overrides it if it already exists
pub fn set_guild_setting<T>(&self, guild_id: &GuildId, key: &str, value: T) -> BotResult<()> pub fn set_guild_setting<T>(&self, guild_id: &GuildId, key: &str, value: T) -> BotResult<()>
where where
T: ToString + FromStr + Clone, T: ToString + FromStr + Clone + Debug,
{ {
if self.get_guild_setting::<T>(guild_id, key).is_ok() { if self.get_guild_setting::<T>(guild_id, key).is_ok() {
log::trace!("Clearing previous guild setting");
self.connection.execute( self.connection.execute(
"DELETE FROM guild_settings WHERE guild_id = ?1 AND setting_key = ?2", "DELETE FROM guild_settings WHERE guild_id = ?1 AND setting_key = ?2",
params![guild_id.to_string(), key], params![guild_id.to_string(), key],
@ -66,6 +74,12 @@ impl Database {
"INSERT INTO guild_settings (guild_id, setting_key, setting_value) VALUES (?1, ?2, ?3)", "INSERT INTO guild_settings (guild_id, setting_key, setting_value) VALUES (?1, ?2, ?3)",
params![guild_id.to_string(), key, value.to_string()], params![guild_id.to_string(), key, value.to_string()],
)?; )?;
log::debug!(
"Setting '{}' set to '{:?}' for guild {}",
key,
value,
guild_id
);
Ok(()) Ok(())
} }

@ -0,0 +1,21 @@
use serenity::async_trait;
use serenity::client::Context;
use serenity::model::event::ResumedEvent;
use serenity::model::gateway::{Activity, Ready};
use serenity::prelude::*;
pub(crate) struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn ready(&self, ctx: Context, ready: Ready) {
log::info!("Connected as {}", ready.user.name);
let prefix = dotenv::var("BOT_PREFIX").unwrap_or("~!".to_string());
ctx.set_activity(Activity::listening(format!("{}help", prefix).as_str()))
.await;
}
async fn resume(&self, _: Context, _: ResumedEvent) {
log::info!("Reconnected to gateway")
}
}

@ -1,17 +1,20 @@
use crate::client::get_client; use crate::client::get_client;
use crate::utils::logging::init_logger;
pub(crate) mod client; pub mod client;
mod commands; mod commands;
pub(crate) mod database; pub mod database;
pub mod handler;
mod providers; mod providers;
pub(crate) mod utils; pub mod utils;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
init_logger();
let mut client = get_client().await.unwrap(); let mut client = get_client().await.unwrap();
// start listening for events by starting a single shard // start listening for events by starting a single shard
if let Err(why) = client.start().await { if let Err(why) = client.start().await {
println!("An error occurred while running the client: {:?}", why); log::error!("An error occurred while running the client: {:?}", why);
} }
} }

@ -18,6 +18,7 @@ static THREAD_LIMIT: u8 = 64;
/// Returns a list of youtube videos for a given url /// Returns a list of youtube videos for a given url
pub(crate) async fn get_videos_for_playlist(url: &str) -> BotResult<Vec<PlaylistEntry>> { pub(crate) async fn get_videos_for_playlist(url: &str) -> BotResult<Vec<PlaylistEntry>> {
log::debug!("Getting playlist information for {}", url);
let output = let output =
youtube_dl(&["--no-warnings", "--flat-playlist", "--dump-json", "-i", url]).await?; youtube_dl(&["--no-warnings", "--flat-playlist", "--dump-json", "-i", url]).await?;
@ -31,6 +32,7 @@ pub(crate) async fn get_videos_for_playlist(url: &str) -> BotResult<Vec<Playlist
/// Returns information for a single video by using youtube-dl /// Returns information for a single video by using youtube-dl
pub(crate) async fn get_video_information(url: &str) -> BotResult<VideoInformation> { pub(crate) async fn get_video_information(url: &str) -> BotResult<VideoInformation> {
log::debug!("Fetching information for '{}'", url);
let output = youtube_dl(&["--no-warnings", "--dump-json", "-i", url]).await?; let output = youtube_dl(&["--no-warnings", "--dump-json", "-i", url]).await?;
let information = serde_json::from_str(&*output)?; let information = serde_json::from_str(&*output)?;
@ -40,6 +42,7 @@ pub(crate) async fn get_video_information(url: &str) -> BotResult<VideoInformati
/// Searches for a video /// Searches for a video
pub(crate) async fn search_video_information(query: String) -> BotResult<Option<VideoInformation>> { pub(crate) async fn search_video_information(query: String) -> BotResult<Option<VideoInformation>> {
log::debug!("Searching for video '{}'", query);
let output = youtube_dl(&[ let output = youtube_dl(&[
"--no-warnings", "--no-warnings",
"--dump-json", "--dump-json",
@ -72,6 +75,7 @@ async fn parallel_search_youtube(song_names: Vec<String>) -> Vec<Song> {
/// to avoid using too much memory /// to avoid using too much memory
async fn youtube_dl(args: &[&str]) -> BotResult<String> { async fn youtube_dl(args: &[&str]) -> BotResult<String> {
lazy_static::lazy_static! { static ref THREAD_LOCK: Arc<AtomicU8> = Arc::new(AtomicU8::new(0)); } lazy_static::lazy_static! { static ref THREAD_LOCK: Arc<AtomicU8> = Arc::new(AtomicU8::new(0)); }
log::trace!("Running youtube-dl with args {:?}", args);
while THREAD_LOCK.load(Ordering::SeqCst) >= THREAD_LIMIT { while THREAD_LOCK.load(Ordering::SeqCst) >= THREAD_LIMIT {
tokio::time::sleep(Duration::from_millis(100)).await; tokio::time::sleep(Duration::from_millis(100)).await;
@ -95,6 +99,7 @@ async fn youtube_dl(args: &[&str]) -> BotResult<String> {
THREAD_LOCK.fetch_sub(1, Ordering::Relaxed); THREAD_LOCK.fetch_sub(1, Ordering::Relaxed);
e e
})?; })?;
log::trace!("youtube-dl response is {}", output);
THREAD_LOCK.fetch_sub(1, Ordering::Relaxed); THREAD_LOCK.fetch_sub(1, Ordering::Relaxed);
Ok(output) Ok(output)

@ -82,6 +82,7 @@ impl Song {
if let Some(url) = self.url.clone() { if let Some(url) = self.url.clone() {
Some(url) Some(url)
} else { } else {
log::debug!("Lazy fetching video for title");
let information = search_video_information(format!("{} - {}", self.author, self.title)) let information = search_video_information(format!("{} - {}", self.author, self.title))
.await .await
.ok() .ok()

@ -15,12 +15,14 @@ impl SpotifyApi {
secret: dotenv::var("SPOTIFY_CLIENT_SECRET").expect("Missing Spotify Credentials"), secret: dotenv::var("SPOTIFY_CLIENT_SECRET").expect("Missing Spotify Credentials"),
}; };
let client = Client::new(credentials); let client = Client::new(credentials);
log::info!("Spotify API initialized.");
Self { client } Self { client }
} }
/// Returns the song names for a playlist /// Returns the songs for a playlist
pub async fn get_songs_in_playlist(&self, url: &str) -> BotResult<Vec<Song>> { pub async fn get_songs_in_playlist(&self, url: &str) -> BotResult<Vec<Song>> {
log::debug!("Fetching spotify songs from playlist '{}'", url);
let id = self.get_id_for_url(url)?; let id = self.get_id_for_url(url)?;
let mut playlist_tracks = Vec::new(); let mut playlist_tracks = Vec::new();
let mut offset = 0; let mut offset = 0;
@ -33,8 +35,13 @@ impl SpotifyApi {
playlist_tracks.append(&mut tracks); playlist_tracks.append(&mut tracks);
offset += 100; offset += 100;
} }
log::debug!(
"{} Songs found in spotify playlist '{}'",
playlist_tracks.len(),
url
);
let song_names = playlist_tracks let songs = playlist_tracks
.into_iter() .into_iter()
.filter_map(|item| item.item) .filter_map(|item| item.item)
.filter_map(|t| match t { .filter_map(|t| match t {
@ -42,8 +49,9 @@ impl SpotifyApi {
PlaylistItemType::Episode(_) => None, PlaylistItemType::Episode(_) => None,
}) })
.collect(); .collect();
log::trace!("Songs are {:?}", songs);
Ok(song_names) Ok(songs)
} }
/// Returns the tracks of a playlist with pagination /// Returns the tracks of a playlist with pagination
@ -53,29 +61,40 @@ impl SpotifyApi {
limit: usize, limit: usize,
offset: usize, offset: usize,
) -> BotResult<Vec<PlaylistItem>> { ) -> BotResult<Vec<PlaylistItem>> {
log::trace!(
"Fetching songs from spotify playlist: limit {}, offset {}",
limit,
offset
);
let tracks = self let tracks = self
.client .client
.playlists() .playlists()
.get_playlists_items(id, limit, offset, None) .get_playlists_items(id, limit, offset, None)
.await? .await?
.data; .data;
log::trace!("Tracks are {:?}", tracks);
Ok(tracks.items) Ok(tracks.items)
} }
/// Returns all song names for a given album /// Returns all songs for a given album
pub async fn get_songs_in_album(&self, url: &str) -> BotResult<Vec<Song>> { pub async fn get_songs_in_album(&self, url: &str) -> BotResult<Vec<Song>> {
log::debug!("Fetching songs for spotify album '{}'", url);
let id = self.get_id_for_url(url)?; let id = self.get_id_for_url(url)?;
let album = self.client.albums().get_album(&*id, None).await?.data; let album = self.client.albums().get_album(&*id, None).await?.data;
let song_names = album.tracks.items.into_iter().map(Song::from).collect(); log::trace!("Album is {:?}", album);
let song_names: Vec<Song> = album.tracks.items.into_iter().map(Song::from).collect();
log::debug!("{} songs found in album '{}'", song_names.len(), url);
Ok(song_names) Ok(song_names)
} }
/// Returns the name for a spotify song url /// Returns song entity for a given spotify url
pub async fn get_song_name(&self, url: &str) -> BotResult<Song> { pub async fn get_song_name(&self, url: &str) -> BotResult<Song> {
log::debug!("Getting song for {}", url);
let id = self.get_id_for_url(url)?; let id = self.get_id_for_url(url)?;
let track = self.client.tracks().get_track(&*id, None).await?.data; let track = self.client.tracks().get_track(&*id, None).await?.data;
log::trace!("Track info is {:?}", track);
Ok(track.into()) Ok(track.into())
} }

@ -0,0 +1,82 @@
/*
* snekcloud node based network
* Copyright (C) 2020 trivernis
* See LICENSE for more information
*/
use chrono::Local;
use colored::*;
use log::{Level, LevelFilter};
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
/// Initializes the env_logger with a custom format
/// that also logs the thread names
pub fn init_logger() {
let log_dir = PathBuf::from(dotenv::var("LOG_DIR").unwrap_or("logs".to_string()));
if !log_dir.exists() {
fs::create_dir(&log_dir).expect("failed to create log dir");
}
fern::Dispatch::new()
.format(|out, message, record| {
let color = get_level_style(record.level());
let mut target = record.target().to_string();
target.truncate(39);
out.finish(format_args!(
"{:<40}| {} {}: {}",
target.dimmed().italic(),
Local::now().format("%Y-%m-%dT%H:%M:%S"),
record
.level()
.to_string()
.to_lowercase()
.as_str()
.color(color),
message
))
})
.level(
log::LevelFilter::from_str(
std::env::var("RUST_LOG")
.unwrap_or("info".to_string())
.as_str(),
)
.unwrap_or(LevelFilter::Info),
)
.level_for("tokio", log::LevelFilter::Info)
.level_for("tracing", log::LevelFilter::Warn)
.level_for("serenity", log::LevelFilter::Warn)
.level_for("rustls", log::LevelFilter::Warn)
.level_for("h2", log::LevelFilter::Warn)
.level_for("reqwest", log::LevelFilter::Warn)
.level_for("tungstenite", log::LevelFilter::Warn)
.level_for("hyper", log::LevelFilter::Warn)
.level_for("async_tungstenite", log::LevelFilter::Warn)
.level_for("tokio_util", log::LevelFilter::Warn)
.level_for("want", log::LevelFilter::Warn)
.level_for("mio", log::LevelFilter::Warn)
.level_for("songbird", log::LevelFilter::Warn)
.chain(std::io::stdout())
.chain(
fern::log_file(log_dir.join(PathBuf::from(format!(
"{}.log",
Local::now().format("%Y-%m-%d"),
))))
.expect("failed to create log file"),
)
.apply()
.expect("failed to init logger");
}
fn get_level_style(level: Level) -> colored::Color {
match level {
Level::Trace => colored::Color::Magenta,
Level::Debug => colored::Color::Blue,
Level::Info => colored::Color::Green,
Level::Warn => colored::Color::Yellow,
Level::Error => colored::Color::Red,
}
}

@ -2,8 +2,9 @@ use std::collections::VecDeque;
use rand::Rng; use rand::Rng;
pub mod error; pub(crate) mod error;
pub mod store; pub(crate) mod logging;
pub(crate) mod store;
/// Fisher-Yates shuffle for VecDeque /// Fisher-Yates shuffle for VecDeque
pub fn shuffle_vec_deque<T>(deque: &mut VecDeque<T>) { pub fn shuffle_vec_deque<T>(deque: &mut VecDeque<T>) {

Loading…
Cancel
Save