diff --git a/src/guilddata.rs b/src/guilddata.rs index 60a7ca9..5268453 100644 --- a/src/guilddata.rs +++ b/src/guilddata.rs @@ -17,17 +17,16 @@ impl GuildData { SELECT id, name, prefix, volume, allow_greets FROM servers WHERE id = ? - ", guild.id.as_u64() + ", + guild.id.as_u64() ) - .fetch_one(&db_pool) - .await; + .fetch_one(&db_pool) + .await; match guild_data { Ok(g) => Some(g), - Err(sqlx::Error::RowNotFound) => { - Self::create_from_guild(guild, db_pool).await.ok() - } + Err(sqlx::Error::RowNotFound) => Self::create_from_guild(guild, db_pool).await.ok(), Err(e) => { println!("{:?}", e); @@ -37,36 +36,45 @@ SELECT id, name, prefix, volume, allow_greets } } - pub async fn create_from_guild(guild: Guild, db_pool: MySqlPool) -> Result> { + pub async fn create_from_guild( + guild: Guild, + db_pool: MySqlPool, + ) -> Result> { sqlx::query!( " INSERT INTO servers (id, name) VALUES (?, ?) - ", guild.id.as_u64(), guild.name + ", + guild.id.as_u64(), + guild.name ) - .execute(&db_pool) - .await?; + .execute(&db_pool) + .await?; sqlx::query!( " INSERT IGNORE INTO roles (guild_id, role) VALUES (?, ?) ", - guild.id.as_u64(), guild.id.as_u64() + guild.id.as_u64(), + guild.id.as_u64() ) - .execute(&db_pool) - .await?; + .execute(&db_pool) + .await?; Ok(GuildData { id: *guild.id.as_u64(), name: Some(guild.name.clone()), prefix: String::from("?"), volume: 100, - allow_greets: true + allow_greets: true, }) } - pub async fn commit(&self, db_pool: MySqlPool) -> Result<(), Box> { + pub async fn commit( + &self, + db_pool: MySqlPool, + ) -> Result<(), Box> { sqlx::query!( " UPDATE servers @@ -78,10 +86,14 @@ SET WHERE id = ? ", - self.name, self.prefix, self.volume, self.allow_greets, self.id + self.name, + self.prefix, + self.volume, + self.allow_greets, + self.id ) - .execute(&db_pool) - .await?; + .execute(&db_pool) + .await?; Ok(()) } diff --git a/src/main.rs b/src/main.rs index bcaae9a..85fb479 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,74 +3,48 @@ extern crate lazy_static; extern crate reqwest; +mod error; mod guilddata; mod sound; -mod error; -use sound::Sound; use guilddata::GuildData; +use sound::Sound; use serenity::{ client::{ - bridge::{ - gateway::GatewayIntents, - voice::ClientVoiceManager, - }, + bridge::{gateway::GatewayIntents, voice::ClientVoiceManager}, Client, Context, }, framework::standard::{ - Args, CommandError, CommandResult, CheckResult, DispatchError, StandardFramework, Reason, - macros::{ - command, group, check, hook, - } + macros::{check, command, group, hook}, + Args, CheckResult, CommandError, CommandResult, DispatchError, Reason, StandardFramework, }, model::{ - channel::{ - Channel, - Message, - }, + channel::{Channel, Message}, guild::Guild, - id::{ - GuildId, - RoleId, - UserId, - }, + id::{GuildId, RoleId, UserId}, voice::VoiceState, }, - prelude::{ - Mutex as SerenityMutex, - * - }, + prelude::{Mutex as SerenityMutex, *}, voice::Handler as VoiceHandler, }; use sqlx::{ + mysql::{MySqlConnection, MySqlPool}, Pool, - mysql::{ - MySqlPool, - MySqlConnection, - } }; use dotenv::dotenv; -use tokio::{ - sync::RwLock, - time, -}; +use tokio::{sync::RwLock, time}; use std::{ collections::HashMap, env, sync::Arc, - time::{ - Duration, - SystemTime, - UNIX_EPOCH, - }, + time::{Duration, SystemTime, UNIX_EPOCH}, }; - struct SQLPool; impl TypeMapKey for SQLPool { @@ -102,12 +76,10 @@ lazy_static! { dotenv().unwrap(); env::var("MAX_SOUNDS").unwrap().parse::().unwrap() }; - static ref PATREON_GUILD: u64 = { dotenv().unwrap(); env::var("PATREON_GUILD").unwrap().parse::().unwrap() }; - static ref PATREON_ROLE: u64 = { dotenv().unwrap(); env::var("PATREON_ROLE").unwrap().parse::().unwrap() @@ -115,7 +87,16 @@ lazy_static! { } #[group] -#[commands(info, help, list_sounds, change_public, search_sounds, show_popular_sounds, show_random_sounds, set_greet_sound)] +#[commands( + info, + help, + list_sounds, + change_public, + search_sounds, + show_popular_sounds, + show_random_sounds, + set_greet_sound +)] #[checks(self_perm_check)] struct AllUsers; @@ -136,25 +117,25 @@ async fn self_perm_check(ctx: &Context, msg: &Message, _args: &mut Args) -> Chec if let Some(channel_e) = channel_o { if let Channel::Guild(channel) = channel_e { - let permissions_r = channel.permissions_for_user(&ctx, &ctx.cache.current_user_id().await).await; + let permissions_r = channel + .permissions_for_user(&ctx, &ctx.cache.current_user_id().await) + .await; if let Ok(permissions) = permissions_r { if permissions.send_messages() && permissions.embed_links() { CheckResult::Success + } else { + CheckResult::Failure(Reason::Log( + "Bot does not have enough permissions".to_string(), + )) } - else { - CheckResult::Failure(Reason::Log("Bot does not have enough permissions".to_string())) - } - } - else { + } else { CheckResult::Failure(Reason::Log("No perms found".to_string())) } - } - else { + } else { CheckResult::Failure(Reason::Log("No DM commands".to_string())) } - } - else { + } else { CheckResult::Failure(Reason::Log("Channel not available".to_string())) } } @@ -162,10 +143,14 @@ async fn self_perm_check(ctx: &Context, msg: &Message, _args: &mut Args) -> Chec #[check] #[name("role_check")] async fn role_check(ctx: &Context, msg: &Message, _args: &mut Args) -> CheckResult { - async fn check_for_roles(ctx: &&Context, msg: &&Message) -> CheckResult { - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let guild_opt = msg.guild(&ctx).await; @@ -175,7 +160,8 @@ async fn role_check(ctx: &Context, msg: &Message, _args: &mut Args) -> CheckResu match member_res { Ok(member) => { - let user_roles: String = member.roles + let user_roles: String = member + .roles .iter() .map(|r| (*r.as_u64()).to_string()) .collect::>() @@ -191,9 +177,12 @@ SELECT COUNT(1) as count (guild_id = ? AND role IN (?)) OR (role = ?) ", - guild_id, user_roles, guild_id + guild_id, + user_roles, + guild_id ) - .fetch_one(&pool).await; + .fetch_one(&pool) + .await; match role_res { Ok(role_count) => { @@ -211,22 +200,21 @@ SELECT COUNT(1) as count } } - Err(_) => { - CheckResult::Failure(Reason::User("Unexpected error looking up user roles".to_string())) - } + Err(_) => CheckResult::Failure(Reason::User( + "Unexpected error looking up user roles".to_string(), + )), } } - None => { - CheckResult::Failure(Reason::User("Unexpected error looking up guild".to_string())) - } + None => CheckResult::Failure(Reason::User( + "Unexpected error looking up guild".to_string(), + )), } } if perform_permission_check(ctx, &msg).await.is_success() { CheckResult::Success - } - else { + } else { check_for_roles(&ctx, &msg).await } } @@ -241,12 +229,12 @@ async fn perform_permission_check(ctx: &Context, msg: &&Message) -> CheckResult if let Some(guild) = msg.guild(&ctx).await { if guild.member_permissions(&msg.author).await.manage_guild() { CheckResult::Success + } else { + CheckResult::Failure(Reason::User(String::from( + "User needs `Manage Guild` permission", + ))) } - else { - CheckResult::Failure(Reason::User(String::from("User needs `Manage Guild` permission"))) - } - } - else { + } else { CheckResult::Failure(Reason::User(String::from("Guild not cached"))) } } @@ -264,10 +252,22 @@ impl EventHandler for Handler { let mut hm = HashMap::new(); hm.insert("server_count", guild_count); - let client = ctx.data.read().await - .get::().cloned().expect("Could not get ReqwestClient from data"); + let client = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get ReqwestClient from data"); - let response = client.post(format!("https://top.gg/api/bots/{}/stats", ctx.cache.current_user_id().await.as_u64()).as_str()) + let response = client + .post( + format!( + "https://top.gg/api/bots/{}/stats", + ctx.cache.current_user_id().await.as_u64() + ) + .as_str(), + ) .header("Authorization", token) .json(&hm) .send() @@ -280,15 +280,23 @@ impl EventHandler for Handler { } } - async fn voice_state_update(&self, ctx: Context, guild_id_opt: Option, old: Option, new: VoiceState) { + async fn voice_state_update( + &self, + ctx: Context, + guild_id_opt: Option, + old: Option, + new: VoiceState, + ) { if let (Some(guild_id), Some(user_channel)) = (guild_id_opt, new.channel_id) { - if old.is_none() { - if let Some(guild) = ctx.cache.guild(guild_id).await { - - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let guild_data_opt = GuildData::get_from_id(guild, pool.clone()).await; @@ -302,11 +310,10 @@ SELECT join_sound_id ", new.user_id.as_u64() ) - .fetch_one(&pool) - .await; + .fetch_one(&pool) + .await; if let Ok(join_id_record) = join_id_res { - let join_id = join_id_record.join_sound_id; let mut sound = sqlx::query_as_unchecked!( @@ -318,19 +325,37 @@ SELECT name, id, plays, public, server_id, uploader_id ", join_id ) - .fetch_one(&pool) - .await.unwrap(); + .fetch_one(&pool) + .await + .unwrap(); - let voice_manager_lock = ctx.data.read().await - .get::().cloned().expect("Could not get VoiceManager from data"); + let voice_manager_lock = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get VoiceManager from data"); - let voice_guilds = ctx.data.read().await - .get::().cloned().expect("Could not get VoiceGuilds from data"); + let voice_guilds = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get VoiceGuilds from data"); let mut voice_manager = voice_manager_lock.lock().await; if let Some(handler) = voice_manager.join(guild_id, user_channel) { - let _audio = play_audio(&mut sound, guild_data, handler, voice_guilds, pool).await; + let _audio = play_audio( + &mut sound, + guild_data, + handler, + voice_guilds, + pool, + ) + .await; } } } @@ -341,9 +366,13 @@ SELECT name, id, plays, public, server_id, uploader_id } } -async fn play_audio(sound: &mut Sound, guild: GuildData, handler: &mut VoiceHandler, voice_guilds: Arc>>, pool: MySqlPool) - -> Result<(), Box> { - +async fn play_audio( + sound: &mut Sound, + guild: GuildData, + handler: &mut VoiceHandler, + voice_guilds: Arc>>, + pool: MySqlPool, +) -> Result<(), Box> { let audio = handler.play_only(sound.store_sound_source(pool.clone()).await?); { @@ -358,7 +387,10 @@ async fn play_audio(sound: &mut Sound, guild: GuildData, handler: &mut VoiceHand { let since_epoch = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); - voice_guilds.write().await.insert(GuildId(guild.id), since_epoch.as_secs()); + voice_guilds + .write() + .await + .insert(GuildId(guild.id), since_epoch.as_secs()); } Ok(()) @@ -376,7 +408,9 @@ async fn dispatch_error_hook(ctx: &Context, msg: &Message, error: DispatchError) match error { DispatchError::CheckFailed(_f, reason) => { if let Reason::User(description) = reason { - let _ = msg.reply(ctx, format!("You cannot do this command: {}", description)).await; + let _ = msg + .reply(ctx, format!("You cannot do this command: {}", description)) + .await; } } @@ -387,62 +421,79 @@ async fn dispatch_error_hook(ctx: &Context, msg: &Message, error: DispatchError) // entry point #[tokio::main] async fn main() -> Result<(), Box> { - dotenv()?; let voice_guilds = Arc::new(RwLock::new(HashMap::new())); let framework = StandardFramework::new() - .configure(|c| c - .dynamic_prefix(|ctx, msg| Box::pin(async move { - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + .configure(|c| { + c.dynamic_prefix(|ctx, msg| { + Box::pin(async move { + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); - let guild = match msg.guild(&ctx.cache).await { - Some(guild) => guild, + let guild = match msg.guild(&ctx.cache).await { + Some(guild) => guild, - None => { - return Some(String::from("?")); - } - }; - - match GuildData::get_from_id(guild.clone(), pool.clone()).await { - Some(mut guild_data) => { - let name = Some(guild.name); - - if guild_data.name != name { - guild_data.name = name; - guild_data.commit(pool).await.unwrap(); + None => { + return Some(String::from("?")); } - Some(guild_data.prefix) - }, + }; - None => { - GuildData::create_from_guild(guild, pool).await.unwrap(); - Some(String::from("?")) + match GuildData::get_from_id(guild.clone(), pool.clone()).await { + Some(mut guild_data) => { + let name = Some(guild.name); + + if guild_data.name != name { + guild_data.name = name; + guild_data.commit(pool).await.unwrap(); + } + Some(guild_data.prefix) + } + + None => { + GuildData::create_from_guild(guild, pool).await.unwrap(); + Some(String::from("?")) + } } - } - })) + }) + }) .allow_dm(false) .ignore_bots(true) .ignore_webhooks(true) - .on_mention(env::var("CLIENT_ID") - .and_then(|val| Ok(UserId(val.parse::().expect("CLIENT_ID not valid")))).ok()) - ) + .on_mention( + env::var("CLIENT_ID") + .and_then(|val| Ok(UserId(val.parse::().expect("CLIENT_ID not valid")))) + .ok(), + ) + }) .group(&ALLUSERS_GROUP) .group(&ROLEMANAGEDUSERS_GROUP) .group(&PERMISSIONMANAGEDUSERS_GROUP) .after(log_errors) .on_dispatch_error(dispatch_error_hook); - let mut client = Client::new(&env::var("DISCORD_TOKEN").expect("Missing token from environment")) - .intents(GatewayIntents::GUILD_VOICE_STATES | GatewayIntents::GUILD_MESSAGES | GatewayIntents::GUILDS) - .framework(framework) - .event_handler(Handler) - .await.expect("Error occurred creating client"); + let mut client = + Client::new(&env::var("DISCORD_TOKEN").expect("Missing token from environment")) + .intents( + GatewayIntents::GUILD_VOICE_STATES + | GatewayIntents::GUILD_MESSAGES + | GatewayIntents::GUILDS, + ) + .framework(framework) + .event_handler(Handler) + .await + .expect("Error occurred creating client"); { - let pool = MySqlPool::new(&env::var("DATABASE_URL").expect("No database URL provided")).await.unwrap(); + let pool = MySqlPool::new(&env::var("DATABASE_URL").expect("No database URL provided")) + .await + .unwrap(); let mut data = client.data.write().await; data.insert::(pool); @@ -459,7 +510,8 @@ async fn main() -> Result<(), Box> { tokio::spawn(async { let disconnect_cycle_delay = env::var("DISCONNECT_CYCLE_DELAY") .unwrap_or("300".to_string()) - .parse::().expect("DISCONNECT_CYCLE_DELAY invalid"); + .parse::() + .expect("DISCONNECT_CYCLE_DELAY invalid"); disconnect_from_inactive(cvm, voice_guilds, disconnect_cycle_delay).await }); @@ -469,16 +521,22 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn disconnect_from_inactive(voice_manager_mutex: Arc>, voice_guilds: Arc>>, wait_time: u64) { +async fn disconnect_from_inactive( + voice_manager_mutex: Arc>, + voice_guilds: Arc>>, + wait_time: u64, +) { loop { time::delay_for(Duration::from_secs(wait_time)).await; let voice_guilds_acquired = voice_guilds.read().await; - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); for (guild, last_active) in voice_guilds_acquired.iter() { - if (now - last_active) > wait_time { let mut voice_manager = voice_manager_mutex.lock().await; @@ -491,7 +549,6 @@ async fn disconnect_from_inactive(voice_manager_mutex: Arc CommandResult { - let guild = match msg.guild(&ctx.cache).await { Some(guild) => guild, @@ -503,42 +560,66 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let guild_id = guild.id; let channel_to_join = guild - .voice_states.get(&msg.author.id) + .voice_states + .get(&msg.author.id) .and_then(|voice_state| voice_state.channel_id); match channel_to_join { Some(user_channel) => { let search_term = args.rest(); - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let mut sound_vec = Sound::search_for_sound( search_term, *guild_id.as_u64(), *msg.author.id.as_u64(), pool.clone(), - true).await?; + true, + ) + .await?; let sound_res = sound_vec.first_mut(); match sound_res { Some(sound) => { - let voice_manager_lock = ctx.data.read().await - .get::().cloned().expect("Could not get VoiceManager from data"); + let voice_manager_lock = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get VoiceManager from data"); let mut voice_manager = voice_manager_lock.lock().await; match voice_manager.join(guild_id, user_channel) { Some(handler) => { - let guild_data = GuildData::get_from_id(guild, pool.clone()).await.unwrap(); + let guild_data = + GuildData::get_from_id(guild, pool.clone()).await.unwrap(); - let voice_guilds = ctx.data.read().await - .get::().cloned().expect("Could not get VoiceGuilds from data"); + let voice_guilds = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get VoiceGuilds from data"); play_audio(sound, guild_data, handler, voice_guilds, pool).await?; - msg.channel_id.say(&ctx, format!("Playing sound {} with ID {}", sound.name, sound.id)).await?; + msg.channel_id + .say( + &ctx, + format!("Playing sound {} with ID {}", sound.name, sound.id), + ) + .await?; } None => { @@ -548,13 +629,17 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult { } None => { - msg.channel_id.say(&ctx, "Couldn't find sound by term provided").await?; + msg.channel_id + .say(&ctx, "Couldn't find sound by term provided") + .await?; } } } None => { - msg.channel_id.say(&ctx, "You are not in a voice chat!").await?; + msg.channel_id + .say(&ctx, "You are not in a voice chat!") + .await?; } } @@ -563,18 +648,21 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult { #[command] async fn help(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - msg.channel_id.send_message(&ctx, |m| m - .embed(|e| e - .title("Help") - .color(THEME_COLOR) - .description("Please visit our website at https://soundfx.jellywx.com/help"))).await?; + msg.channel_id + .send_message(&ctx, |m| { + m.embed(|e| { + e.title("Help") + .color(THEME_COLOR) + .description("Please visit our website at https://soundfx.jellywx.com/help") + }) + }) + .await?; Ok(()) } #[command] async fn info(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - msg.channel_id.send_message(&ctx, |m| m .embed(|e| e .title("Info") @@ -605,8 +693,13 @@ async fn change_volume(ctx: &Context, msg: &Message, mut args: Args) -> CommandR } }; - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let guild_data_opt = GuildData::get_from_id(guild, pool.clone()).await; let mut guild_data = guild_data_opt.unwrap(); @@ -618,7 +711,9 @@ async fn change_volume(ctx: &Context, msg: &Message, mut args: Args) -> CommandR guild_data.commit(pool).await?; - msg.channel_id.say(&ctx, format!("Volume changed to {}%", volume)).await?; + msg.channel_id + .say(&ctx, format!("Volume changed to {}%", volume)) + .await?; } Err(_) => { @@ -627,8 +722,7 @@ async fn change_volume(ctx: &Context, msg: &Message, mut args: Args) -> CommandR vol = guild_data.volume, prefix = guild_data.prefix)).await?; } } - } - else { + } else { msg.channel_id.say(&ctx, format!("Current server volume: {vol}%. Change the volume with ```{prefix}volume ```", vol = guild_data.volume, prefix = guild_data.prefix)).await?; @@ -647,8 +741,13 @@ async fn change_prefix(ctx: &Context, msg: &Message, mut args: Args) -> CommandR } }; - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let mut guild_data; @@ -666,20 +765,38 @@ async fn change_prefix(ctx: &Context, msg: &Message, mut args: Args) -> CommandR guild_data.commit(pool).await?; - msg.channel_id.say(&ctx, format!("Prefix changed to `{}`", guild_data.prefix)).await?; - } - else { - msg.channel_id.say(&ctx, "Prefix must be less than 5 characters long").await?; + msg.channel_id + .say(&ctx, format!("Prefix changed to `{}`", guild_data.prefix)) + .await?; + } else { + msg.channel_id + .say(&ctx, "Prefix must be less than 5 characters long") + .await?; } } Err(_) => { - msg.channel_id.say(&ctx, format!("Usage: `{prefix}prefix `", prefix = guild_data.prefix)).await?; + msg.channel_id + .say( + &ctx, + format!( + "Usage: `{prefix}prefix `", + prefix = guild_data.prefix + ), + ) + .await?; } } - } - else { - msg.channel_id.say(&ctx, format!("Usage: `{prefix}prefix `", prefix = guild_data.prefix)).await?; + } else { + msg.channel_id + .say( + &ctx, + format!( + "Usage: `{prefix}prefix `", + prefix = guild_data.prefix + ), + ) + .await?; } Ok(()) @@ -687,13 +804,11 @@ async fn change_prefix(ctx: &Context, msg: &Message, mut args: Args) -> CommandR #[command("upload")] async fn upload_new_sound(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - fn is_numeric(s: &String) -> bool { for char in s.chars() { if char.is_digit(10) { continue; - } - else { + } else { return false; } } @@ -703,13 +818,19 @@ async fn upload_new_sound(ctx: &Context, msg: &Message, args: Args) -> CommandRe let new_name = args.rest().to_string(); if !new_name.is_empty() && new_name.len() <= 20 { - if !is_numeric(&new_name) { - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); // need to check the name is not currently in use by the user - let count_name = Sound::count_named_user_sounds(*msg.author.id.as_u64(), &new_name, pool.clone()).await?; + let count_name = + Sound::count_named_user_sounds(*msg.author.id.as_u64(), &new_name, pool.clone()) + .await?; if count_name > 0 { msg.channel_id.say(&ctx, "You are already using that name. Please choose a unique name for your upload.").await?; } else { @@ -719,7 +840,8 @@ async fn upload_new_sound(ctx: &Context, msg: &Message, args: Args) -> CommandRe // need to check if user is patreon or nah if count >= *MAX_SOUNDS { - let patreon_guild_member = GuildId(*PATREON_GUILD).member(ctx, msg.author.id).await; + let patreon_guild_member = + GuildId(*PATREON_GUILD).member(ctx, msg.author.id).await; if let Ok(member) = patreon_guild_member { permit_upload = member.roles.contains(&RoleId(*PATREON_ROLE)); @@ -731,7 +853,9 @@ async fn upload_new_sound(ctx: &Context, msg: &Message, args: Args) -> CommandRe if permit_upload { msg.channel_id.say(&ctx, "Please now upload an audio file under 1MB in size (larger files will be automatically trimmed):").await?; - let reply = msg.channel_id.await_reply(&ctx) + let reply = msg + .channel_id + .await_reply(&ctx) .author_id(msg.author.id) .timeout(Duration::from_secs(30)) .await; @@ -744,13 +868,16 @@ async fn upload_new_sound(ctx: &Context, msg: &Message, args: Args) -> CommandRe &reply_msg.attachments[0].url, *msg.guild_id.unwrap().as_u64(), *msg.author.id.as_u64(), - pool).await { + pool, + ) + .await + { Ok(_) => { msg.channel_id.say(&ctx, "Sound has been uploaded").await?; } Err(e) => { - println!("Error occured during upload: {:?}", e); + println!("Error occurred during upload: {:?}", e); msg.channel_id.say(&ctx, "Sound failed to upload.").await?; } } @@ -760,7 +887,9 @@ async fn upload_new_sound(ctx: &Context, msg: &Message, args: Args) -> CommandRe } None => { - msg.channel_id.say(&ctx, "Upload timed out. Please redo the command").await?; + msg.channel_id + .say(&ctx, "Upload timed out. Please redo the command") + .await?; } } } else { @@ -772,12 +901,15 @@ async fn upload_new_sound(ctx: &Context, msg: &Message, args: Args) -> CommandRe )).await?; } } + } else { + msg.channel_id + .say( + &ctx, + "Please ensure the sound name contains a non-numerical character", + ) + .await?; } - else { - msg.channel_id.say(&ctx, "Please ensure the sound name contains a non-numerical character").await?; - } - } - else { + } else { msg.channel_id.say(&ctx, "Usage: `?upload `. Please ensure the name provided is less than 20 characters in length").await?; } @@ -788,8 +920,13 @@ async fn upload_new_sound(ctx: &Context, msg: &Message, args: Args) -> CommandRe async fn set_allowed_roles(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let guild_id = *msg.guild_id.unwrap().as_u64(); - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); if args.len() == 0 { let roles = sqlx::query!( @@ -800,22 +937,26 @@ SELECT role ", guild_id ) - .fetch_all(&pool) - .await?; + .fetch_all(&pool) + .await?; - let all_roles = roles.iter().map(|i| format!("<@&{}>", i.role.to_string())).collect::>().join(", "); + let all_roles = roles + .iter() + .map(|i| format!("<@&{}>", i.role.to_string())) + .collect::>() + .join(", "); msg.channel_id.say(&ctx, format!("Usage: `?roles `. Current roles: {}", all_roles)).await?; - } - else { - + } else { sqlx::query!( " DELETE FROM roles WHERE guild_id = ? ", guild_id - ).execute(&pool).await?; + ) + .execute(&pool) + .await?; if msg.mention_roles.len() > 0 { for role in msg.mention_roles.iter().map(|r| *r.as_u64()) { @@ -825,27 +966,32 @@ INSERT INTO roles (guild_id, role) VALUES (?, ?) ", - guild_id, role + guild_id, + role ) - .execute(&pool) - .await?; + .execute(&pool) + .await?; } - msg.channel_id.say(&ctx, "Specified roles whitelisted").await?; - } - else { + msg.channel_id + .say(&ctx, "Specified roles whitelisted") + .await?; + } else { sqlx::query!( - " + " INSERT INTO roles (guild_id, role) VALUES (?, ?) ", - guild_id, guild_id - ) - .execute(&pool) - .await?; + guild_id, + guild_id + ) + .execute(&pool) + .await?; - msg.channel_id.say(&ctx, "Role whitelisting disabled").await?; + msg.channel_id + .say(&ctx, "Role whitelisting disabled") + .await?; } } @@ -854,8 +1000,13 @@ INSERT INTO roles (guild_id, role) #[command("list")] async fn list_sounds(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let sounds; let mut message_buffer; @@ -864,15 +1015,21 @@ async fn list_sounds(ctx: &Context, msg: &Message, args: Args) -> CommandResult sounds = Sound::get_user_sounds(*msg.author.id.as_u64(), pool).await?; message_buffer = "All your sounds: ".to_string(); - } - else { + } else { sounds = Sound::get_guild_sounds(*msg.guild_id.unwrap().as_u64(), pool).await?; message_buffer = "All sounds on this server: ".to_string(); } for sound in sounds { - message_buffer.push_str(format!("**{}** ({}), ", sound.name, if sound.public { "🔓" } else { "🔒" }).as_str()); + message_buffer.push_str( + format!( + "**{}** ({}), ", + sound.name, + if sound.public { "🔓" } else { "🔒" } + ) + .as_str(), + ); if message_buffer.len() > 2000 { msg.channel_id.say(&ctx, message_buffer).await?; @@ -890,8 +1047,13 @@ async fn list_sounds(ctx: &Context, msg: &Message, args: Args) -> CommandResult #[command("public")] async fn change_public(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let uid = msg.author.id.as_u64(); let name = args.rest(); @@ -904,17 +1066,19 @@ async fn change_public(ctx: &Context, msg: &Message, args: Args) -> CommandResul Some(sound) => { if sound.uploader_id != Some(*uid) { msg.channel_id.say(&ctx, "You can only change the availability of sounds you have uploaded. Use `?list me` to view your sounds").await?; - } - - else { + } else { if sound.public { sound.public = false; - msg.channel_id.say(&ctx, "Sound has been set to private 🔒").await?; + msg.channel_id + .say(&ctx, "Sound has been set to private 🔒") + .await?; } else { sound.public = true; - msg.channel_id.say(&ctx, "Sound has been set to public 🔓").await?; + msg.channel_id + .say(&ctx, "Sound has been set to public 🔓") + .await?; } sound.commit(pool).await? @@ -922,7 +1086,9 @@ async fn change_public(ctx: &Context, msg: &Message, args: Args) -> CommandResul } None => { - msg.channel_id.say(&ctx, "Sound could not be found by that name.").await?; + msg.channel_id + .say(&ctx, "Sound could not be found by that name.") + .await?; } } @@ -931,8 +1097,13 @@ async fn change_public(ctx: &Context, msg: &Message, args: Args) -> CommandResul #[command("delete")] async fn delete_sound(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let uid = *msg.author.id.as_u64(); let gid = *msg.guild_id.unwrap().as_u64(); @@ -945,10 +1116,13 @@ async fn delete_sound(ctx: &Context, msg: &Message, args: Args) -> CommandResult match sound_result { Some(sound) => { if sound.uploader_id != Some(uid) && sound.server_id != gid { - msg.channel_id.say(&ctx, "You can only delete sounds from this guild or that you have uploaded.").await?; - } - - else { + msg.channel_id + .say( + &ctx, + "You can only delete sounds from this guild or that you have uploaded.", + ) + .await?; + } else { sound.delete(pool).await?; msg.channel_id.say(&ctx, "Sound has been deleted").await?; @@ -956,47 +1130,66 @@ async fn delete_sound(ctx: &Context, msg: &Message, args: Args) -> CommandResult } None => { - msg.channel_id.say(&ctx, "Sound could not be found by that name.").await?; + msg.channel_id + .say(&ctx, "Sound could not be found by that name.") + .await?; } } Ok(()) } -async fn format_search_results(search_results: Vec, msg: &Message, ctx: &Context) -> Result<(), Box> { +async fn format_search_results( + search_results: Vec, + msg: &Message, + ctx: &Context, +) -> Result<(), Box> { let mut current_character_count = 0; let title = "Public sounds matching filter:"; - let field_iter = search_results.iter().take(25).map(|item| { - - (&item.name, format!("ID: {}\nPlays: {}", item.id, item.plays), true) - - }).filter(|item| { - - current_character_count += item.0.len() + item.1.len(); - - current_character_count <= 2048 - title.len() - - }); - - msg.channel_id.send_message(&ctx, |m| { - m.embed(|e| { e - .title(title) - .fields(field_iter) + let field_iter = search_results + .iter() + .take(25) + .map(|item| { + ( + &item.name, + format!("ID: {}\nPlays: {}", item.id, item.plays), + true, + ) }) - }).await?; + .filter(|item| { + current_character_count += item.0.len() + item.1.len(); + + current_character_count <= 2048 - title.len() + }); + + msg.channel_id + .send_message(&ctx, |m| m.embed(|e| e.title(title).fields(field_iter))) + .await?; Ok(()) } #[command("search")] async fn search_sounds(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let query = args.rest(); - let search_results = Sound::search_for_sound(query, *msg.guild_id.unwrap().as_u64(), *msg.author.id.as_u64(), pool, false).await?; + let search_results = Sound::search_for_sound( + query, + *msg.guild_id.unwrap().as_u64(), + *msg.author.id.as_u64(), + pool, + false, + ) + .await?; format_search_results(search_results, msg, ctx).await?; @@ -1005,8 +1198,13 @@ async fn search_sounds(ctx: &Context, msg: &Message, args: Args) -> CommandResul #[command("popular")] async fn show_popular_sounds(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let search_results = sqlx::query_as_unchecked!( Sound, @@ -1017,8 +1215,8 @@ SELECT name, id, plays, public, server_id, uploader_id LIMIT 25 " ) - .fetch_all(&pool) - .await?; + .fetch_all(&pool) + .await?; format_search_results(search_results, msg, ctx).await?; @@ -1027,8 +1225,13 @@ SELECT name, id, plays, public, server_id, uploader_id #[command("random")] async fn show_random_sounds(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let search_results = sqlx::query_as_unchecked!( Sound, @@ -1039,18 +1242,26 @@ SELECT name, id, plays, public, server_id, uploader_id LIMIT 25 " ) - .fetch_all(&pool) - .await.unwrap(); + .fetch_all(&pool) + .await + .unwrap(); - format_search_results(search_results, msg, ctx).await.unwrap(); + format_search_results(search_results, msg, ctx) + .await + .unwrap(); Ok(()) } #[command("greet")] async fn set_greet_sound(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let pool = ctx.data.read().await - .get::().cloned().expect("Could not get SQLPool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get SQLPool from data"); let query = args.rest(); let user_id = *msg.author.id.as_u64(); @@ -1062,8 +1273,8 @@ INSERT IGNORE INTO users (user) ", user_id ) - .execute(&pool) - .await; + .execute(&pool) + .await; if query.len() == 0 { sqlx::query!( @@ -1076,24 +1287,41 @@ WHERE ", user_id ) - .execute(&pool) + .execute(&pool) + .await?; + + msg.channel_id + .say(&ctx, "Your greet sound has been unset.") .await?; - - msg.channel_id.say(&ctx, "Your greet sound has been unset.").await?; - } - else { - - let sound_vec = Sound::search_for_sound(query, *msg.guild_id.unwrap().as_u64(), user_id, pool.clone(), true).await?; + } else { + let sound_vec = Sound::search_for_sound( + query, + *msg.guild_id.unwrap().as_u64(), + user_id, + pool.clone(), + true, + ) + .await?; match sound_vec.first() { Some(sound) => { sound.set_as_greet(user_id, pool).await?; - msg.channel_id.say(&ctx, format!("Greet sound has been set to {} (ID {})", sound.name, sound.id)).await?; + msg.channel_id + .say( + &ctx, + format!( + "Greet sound has been set to {} (ID {})", + sound.name, sound.id + ), + ) + .await?; } None => { - msg.channel_id.say(&ctx, "Could not find a sound by that name.").await?; + msg.channel_id + .say(&ctx, "Could not find a sound by that name.") + .await?; } } } @@ -1103,8 +1331,13 @@ WHERE #[command("stop")] async fn stop_playing(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let voice_manager_lock = ctx.data.read().await - .get::().cloned().expect("Could not get VoiceManager from data"); + let voice_manager_lock = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not get VoiceManager from data"); let mut voice_manager = voice_manager_lock.lock().await; @@ -1127,8 +1360,13 @@ async fn allow_greet_sounds(ctx: &Context, msg: &Message, _args: Args) -> Comman } }; - let pool = ctx.data.read().await - .get::().cloned().expect("Could not acquire SQL pool from data"); + let pool = ctx + .data + .read() + .await + .get::() + .cloned() + .expect("Could not acquire SQL pool from data"); let guild_data_opt = GuildData::get_from_id(guild, pool.clone()).await; @@ -1137,7 +1375,15 @@ async fn allow_greet_sounds(ctx: &Context, msg: &Message, _args: Args) -> Comman guild_data.commit(pool).await?; - msg.channel_id.say(&ctx, format!("Greet sounds have been {}abled in this server", if guild_data.allow_greets { "en" } else { "dis" })).await?; + msg.channel_id + .say( + &ctx, + format!( + "Greet sounds have been {}abled in this server", + if guild_data.allow_greets { "en" } else { "dis" } + ), + ) + .await?; } Ok(()) diff --git a/src/sound.rs b/src/sound.rs index 5e95ff2..21e94f2 100644 --- a/src/sound.rs +++ b/src/sound.rs @@ -2,21 +2,11 @@ use super::error::ErrorTypes; use sqlx::mysql::MySqlPool; -use serenity::voice::{ - AudioSource, - ffmpeg, -}; +use serenity::voice::{ffmpeg, AudioSource}; -use tokio::{ - fs::File, - process::Command, -}; - -use std::{ - env, - path::Path, -}; +use tokio::{fs::File, process::Command}; +use std::{env, path::Path}; pub struct Sound { pub name: String, @@ -28,20 +18,23 @@ pub struct Sound { } impl Sound { - pub async fn search_for_sound(query: &str, guild_id: u64, user_id: u64, db_pool: MySqlPool, strict: bool) -> Result, sqlx::Error> { - + pub async fn search_for_sound( + query: &str, + guild_id: u64, + user_id: u64, + db_pool: MySqlPool, + strict: bool, + ) -> Result, sqlx::Error> { fn extract_id(s: &str) -> Option { if s.len() > 3 && s.to_lowercase().starts_with("id:") { match s[3..].parse::() { Ok(id) => Some(id), - Err(_) => None + Err(_) => None, } - } - else if let Ok(id) = s.parse::() { + } else if let Ok(id) = s.parse::() { Some(id) - } - else { + } else { None } } @@ -58,14 +51,15 @@ SELECT name, id, plays, public, server_id, uploader_id server_id = ? ) ", - id, user_id, guild_id + id, + user_id, + guild_id ) - .fetch_all(&db_pool) - .await?; + .fetch_all(&db_pool) + .await?; Ok(sound) - } - else { + } else { let name = query; let sound; @@ -82,13 +76,15 @@ SELECT name, id, plays, public, server_id, uploader_id ) ORDER BY uploader_id = ? DESC, server_id = ? DESC, public = 1 DESC, rand() ", - name, user_id, guild_id, user_id, guild_id + name, + user_id, + guild_id, + user_id, + guild_id ) - .fetch_all(&db_pool) - .await?; - - } - else { + .fetch_all(&db_pool) + .await?; + } else { sound = sqlx::query_as_unchecked!( Self, " @@ -101,10 +97,14 @@ SELECT name, id, plays, public, server_id, uploader_id ) ORDER BY uploader_id = ? DESC, server_id = ? DESC, public = 1 DESC, rand() ", - name, user_id, guild_id, user_id, guild_id + name, + user_id, + guild_id, + user_id, + guild_id ) - .fetch_all(&db_pool) - .await?; + .fetch_all(&db_pool) + .await?; } Ok(sound) @@ -113,7 +113,7 @@ SELECT name, id, plays, public, server_id, uploader_id async fn get_self_src(&self, db_pool: MySqlPool) -> Vec { struct Src { - src: Vec + src: Vec, } let record = sqlx::query_as_unchecked!( @@ -126,14 +126,17 @@ SELECT src ", self.id ) - .fetch_one(&db_pool) - .await.unwrap(); + .fetch_one(&db_pool) + .await + .unwrap(); - return record.src + return record.src; } - pub async fn store_sound_source(&self, db_pool: MySqlPool) -> Result, Box> { - + pub async fn store_sound_source( + &self, + db_pool: MySqlPool, + ) -> Result, Box> { let caching_location = env::var("CACHING_LOCATION").unwrap_or(String::from("/tmp")); let path_name = format!("{}/sound-{}", caching_location, self.id); @@ -150,39 +153,53 @@ SELECT src Ok(ffmpeg(path_name).await?) } - pub async fn count_user_sounds(user_id: u64, db_pool: MySqlPool) -> Result { + pub async fn count_user_sounds( + user_id: u64, + db_pool: MySqlPool, + ) -> Result { let c = sqlx::query!( - " + " SELECT COUNT(1) as count FROM sounds WHERE uploader_id = ? ", - user_id + user_id ) - .fetch_one(&db_pool) - .await?.count; + .fetch_one(&db_pool) + .await? + .count; Ok(c as u32) } - pub async fn count_named_user_sounds(user_id: u64, name: &String, db_pool: MySqlPool) -> Result { + pub async fn count_named_user_sounds( + user_id: u64, + name: &String, + db_pool: MySqlPool, + ) -> Result { let c = sqlx::query!( - " + " SELECT COUNT(1) as count FROM sounds WHERE uploader_id = ? AND name = ? ", - user_id, name + user_id, + name ) - .fetch_one(&db_pool) - .await?.count; + .fetch_one(&db_pool) + .await? + .count; Ok(c as u32) } - pub async fn set_as_greet(&self, user_id: u64, db_pool: MySqlPool) -> Result<(), Box> { + pub async fn set_as_greet( + &self, + user_id: u64, + db_pool: MySqlPool, + ) -> Result<(), Box> { sqlx::query!( " UPDATE users @@ -191,15 +208,19 @@ SET WHERE user = ? ", - self.id, user_id + self.id, + user_id ) - .execute(&db_pool) - .await?; + .execute(&db_pool) + .await?; Ok(()) } - pub async fn commit(&self, db_pool: MySqlPool) -> Result<(), Box> { + pub async fn commit( + &self, + db_pool: MySqlPool, + ) -> Result<(), Box> { sqlx::query!( " UPDATE sounds @@ -209,15 +230,20 @@ SET WHERE id = ? ", - self.plays, self.public, self.id + self.plays, + self.public, + self.id ) - .execute(&db_pool) - .await?; + .execute(&db_pool) + .await?; Ok(()) } - pub async fn delete(&self, db_pool: MySqlPool) -> Result<(), Box> { + pub async fn delete( + &self, + db_pool: MySqlPool, + ) -> Result<(), Box> { sqlx::query!( " DELETE @@ -226,15 +252,22 @@ DELETE ", self.id ) - .execute(&db_pool) - .await?; + .execute(&db_pool) + .await?; Ok(()) } - pub async fn create_anon(name: &str, src_url: &str, server_id: u64, user_id: u64, db_pool: MySqlPool) -> Result> { + pub async fn create_anon( + name: &str, + src_url: &str, + server_id: u64, + user_id: u64, + db_pool: MySqlPool, + ) -> Result> { async fn process_src(src_url: &str) -> Option> { - let future = Command::new("ffmpeg") + let output = Command::new("ffmpeg") + .kill_on_drop(true) .arg("-i") .arg(src_url) .arg("-loglevel") @@ -246,16 +279,14 @@ DELETE .arg("-fs") .arg("1048576") .arg("pipe:1") - .output(); - - let output = future.await; + .output() + .await; match output { Ok(out) => { if out.status.success() { Some(out.stdout) - } - else { + } else { None } } @@ -269,25 +300,32 @@ DELETE match source { Some(data) => { match sqlx::query!( - " + " INSERT INTO sounds (name, server_id, uploader_id, public, src) VALUES (?, ?, ?, 1, ?) ", - name, server_id, user_id, data + name, + server_id, + user_id, + data ) - .execute(&db_pool) - .await { + .execute(&db_pool) + .await + { Ok(u) => Ok(u), - Err(e) => Err(Box::new(e)) + Err(e) => Err(Box::new(e)), } } - None => Err(Box::new(ErrorTypes::InvalidFile)) + None => Err(Box::new(ErrorTypes::InvalidFile)), } } - pub async fn get_user_sounds(user_id: u64, db_pool: MySqlPool) -> Result, Box> { + pub async fn get_user_sounds( + user_id: u64, + db_pool: MySqlPool, + ) -> Result, Box> { let sounds = sqlx::query_as_unchecked!( Sound, " @@ -296,12 +334,17 @@ SELECT name, id, plays, public, server_id, uploader_id WHERE uploader_id = ? ", user_id - ).fetch_all(&db_pool).await?; + ) + .fetch_all(&db_pool) + .await?; Ok(sounds) } - pub async fn get_guild_sounds(guild_id: u64, db_pool: MySqlPool) -> Result, Box> { + pub async fn get_guild_sounds( + guild_id: u64, + db_pool: MySqlPool, + ) -> Result, Box> { let sounds = sqlx::query_as_unchecked!( Sound, " @@ -310,7 +353,9 @@ SELECT name, id, plays, public, server_id, uploader_id WHERE server_id = ? ", guild_id - ).fetch_all(&db_pool).await?; + ) + .fetch_all(&db_pool) + .await?; Ok(sounds) }