diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 249c895..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,79 +0,0 @@ -name: Continuous Integration - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - ci: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - name: - - ubuntu-latest-stable -# - ubuntu-latest-nightly - - windows-latest-stable -# - windows-latest-nightly - include: - - name: ubuntu-latest-stable - os: ubuntu-latest - rust: stable - target: x86_64-unknown-linux-gnu - rustflags: -D warnings -# - name: ubuntu-latest-nightly -# os: ubuntu-latest -# rust: nightly -# target: x86_64-unknown-linux-gnu -# rustflags: -D warnings - - name: windows-latest-stable - os: windows-latest - rust: stable - target: x86_64-pc-windows-msvc - rustflags: -D warnings - # - name: windows-latest-nightly - # os: windows-latest - # rust: nightly - # target: x86_64-pc-windows-msvc - # rustflags: -D warnings - - steps: - - uses: actions/checkout@v2 - - - uses: actions/cache@v2 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ matrix.name }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - target: ${{ matrix.target }} - override: true - components: clippy - - - uses: actions-rs/cargo@v1 - with: - command: build - args: --all --all-targets --target=${{ matrix.target }} - env: - RUSTFLAGS: ${{ matrix.rustflags }} - - - uses: actions-rs/cargo@v1 - with: - command: test - -# - uses: actions-rs/cargo@v1 -# with: -# command: clippy -# args: --all --all-targets --target=${{ matrix.target }} -- -D warnings -p discord-compiler-bot -# env: -# RUSTFLAGS: ${{ matrix.rustflags }} diff --git a/.rusty-hook.toml b/.rusty-hook.toml new file mode 100644 index 0000000..e7cda60 --- /dev/null +++ b/.rusty-hook.toml @@ -0,0 +1,5 @@ +[hooks] +pre-commit = "cargo fmt && cargo clippy -- -Dwarnings && cargo test" + +[logging] +verbose = true \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 6bf3127..87fd3b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,9 @@ authors = ["Michael Flaherty (Headline#9999)"] edition = "2018" build = "src/build.rs" +[dev-dependencies] +rusty-hook = "0.11.2" + [dependencies] tokio = { version = "1", features = ["full"] } reqwest = { version = "0.11" } diff --git a/src/apis/mod.rs b/src/apis/mod.rs index d559ac9..0f1cd3d 100644 --- a/src/apis/mod.rs +++ b/src/apis/mod.rs @@ -1 +1 @@ -pub mod dbl; \ No newline at end of file +pub mod dbl; diff --git a/src/boilerplate/c.rs b/src/boilerplate/c.rs index cf92d2f..2ccdea4 100644 --- a/src/boilerplate/c.rs +++ b/src/boilerplate/c.rs @@ -2,33 +2,27 @@ use crate::boilerplate::generator::BoilerPlateGenerator; use crate::utls::constants::C_LIKE_MAIN_REGEX; pub struct CGenerator { - input : String + input: String, } impl BoilerPlateGenerator for CGenerator { fn new(input: &str) -> Self { let mut formated = input.to_string(); - formated = formated.replace(";", ";\n"); // separate lines by ; + formated = formated.replace(';', ";\n"); // separate lines by ; - Self { - input : formated - } + Self { input: formated } } fn generate(&self) -> String { let mut main_body = String::default(); let mut header = String::default(); - let mut lines = self.input.split("\n"); - while let Some(line) = lines.next() { + let lines = self.input.split('\n'); + for line in lines { let trimmed = line.trim(); - if trimmed.starts_with("using") { - header.push_str(&format!("{}\n", trimmed)); - } - else if trimmed.starts_with("#i") { + if trimmed.starts_with("using") || trimmed.starts_with("#i") { header.push_str(&format!("{}\n", trimmed)); - } - else { + } else { main_body.push_str(&format!("{}\n", trimmed)) } } @@ -41,10 +35,10 @@ impl BoilerPlateGenerator for CGenerator { fn needs_boilerplate(&self) -> bool { for m in C_LIKE_MAIN_REGEX.captures_iter(&self.input) { - if let Some(_) = m.name("main") { + if m.name("main").is_some() { return false; } } - return true; + true } -} \ No newline at end of file +} diff --git a/src/boilerplate/cpp.rs b/src/boilerplate/cpp.rs index 5fe812f..2a242a2 100644 --- a/src/boilerplate/cpp.rs +++ b/src/boilerplate/cpp.rs @@ -2,33 +2,27 @@ use crate::boilerplate::generator::BoilerPlateGenerator; use crate::utls::constants::C_LIKE_MAIN_REGEX; pub struct CppGenerator { - input : String + input: String, } impl BoilerPlateGenerator for CppGenerator { fn new(input: &str) -> Self { let mut formated = input.to_string(); - formated = formated.replace(";", ";\n"); // separate lines by ; + formated = formated.replace(';', ";\n"); // separate lines by ; - Self { - input : formated - } + Self { input: formated } } fn generate(&self) -> String { let mut main_body = String::default(); let mut header = String::default(); - let mut lines = self.input.split("\n"); - while let Some(line) = lines.next() { + let lines = self.input.split('\n'); + for line in lines { let trimmed = line.trim(); - if trimmed.starts_with("using") { - header.push_str(&format!("{}\n", trimmed)); - } - else if trimmed.starts_with("#i") { + if trimmed.starts_with("using") || trimmed.starts_with("#i") { header.push_str(&format!("{}\n", trimmed)); - } - else { + } else { main_body.push_str(&format!("{}\n", trimmed)) } } @@ -42,10 +36,10 @@ impl BoilerPlateGenerator for CppGenerator { fn needs_boilerplate(&self) -> bool { for m in C_LIKE_MAIN_REGEX.captures_iter(&self.input) { - if let Some(_) = m.name("main") { + if m.name("main").is_some() { return false; } } - return true; + true } -} \ No newline at end of file +} diff --git a/src/boilerplate/csharp.rs b/src/boilerplate/csharp.rs index 56b5b68..bb63a16 100644 --- a/src/boilerplate/csharp.rs +++ b/src/boilerplate/csharp.rs @@ -2,30 +2,27 @@ use crate::boilerplate::generator::BoilerPlateGenerator; use crate::utls::constants::CSHARP_MAIN_REGEX; pub struct CSharpGenerator { - input : String + input: String, } impl BoilerPlateGenerator for CSharpGenerator { fn new(input: &str) -> Self { let mut formated = input.to_string(); - formated = formated.replace(";", ";\n"); // separate lines by ; + formated = formated.replace(';', ";\n"); // separate lines by ; - Self { - input : formated - } + Self { input: formated } } fn generate(&self) -> String { let mut main_body = String::default(); let mut header = String::default(); - let mut lines = self.input.split("\n"); - while let Some(line) = lines.next() { + let lines = self.input.split('\n'); + for line in lines { let trimmed = line.trim(); if trimmed.starts_with("using") { header.push_str(&format!("{}\n", trimmed)); - } - else { + } else { main_body.push_str(&format!("{}\n", trimmed)) } } @@ -34,15 +31,18 @@ impl BoilerPlateGenerator for CSharpGenerator { if header.is_empty() { header.push_str("using System;"); } - format!("{}\nnamespace Main{{\nclass Program {{\n static void Main(string[] args) {{\n{}}}}}}}", header, main_body) + format!( + "{}\nnamespace Main{{\nclass Program {{\n static void Main(string[] args) {{\n{}}}}}}}", + header, main_body + ) } fn needs_boilerplate(&self) -> bool { for m in CSHARP_MAIN_REGEX.captures_iter(&self.input) { - if let Some(_) = m.name("main") { + if m.name("main").is_some() { return false; } } - return true; + true } -} \ No newline at end of file +} diff --git a/src/boilerplate/generator.rs b/src/boilerplate/generator.rs index 6dfb83f..93d0b3b 100644 --- a/src/boilerplate/generator.rs +++ b/src/boilerplate/generator.rs @@ -4,20 +4,26 @@ use crate::boilerplate::csharp::CSharpGenerator; use crate::boilerplate::java::JavaGenerator; pub trait BoilerPlateGenerator { - fn new(input : &str) -> Self where Self: Sized; + fn new(input: &str) -> Self + where + Self: Sized; fn generate(&self) -> String; fn needs_boilerplate(&self) -> bool; } -pub struct BoilerPlate where T: BoilerPlateGenerator { - generator : Box +pub struct BoilerPlate +where + T: BoilerPlateGenerator, +{ + generator: Box, } -impl BoilerPlate where T: BoilerPlateGenerator{ - pub fn new(generator : Box) -> Self { - Self { - generator - } +impl BoilerPlate +where + T: BoilerPlateGenerator, +{ + pub fn new(generator: Box) -> Self { + Self { generator } } pub fn generate(&self) -> String { @@ -44,25 +50,14 @@ impl BoilerPlateGenerator for Null { } } -pub fn boilerplate_factory(language : &str, code : &str) - -> BoilerPlate { - return match language { - "c++" => { - BoilerPlate::new(Box::new(CppGenerator::new(code))) - } - "c" => { - BoilerPlate::new(Box::new(CGenerator::new(code))) - } - "java" => { - BoilerPlate::new(Box::new(JavaGenerator::new(code))) - } - "c#" => { - BoilerPlate::new(Box::new(CSharpGenerator::new(code))) - } +pub fn boilerplate_factory(language: &str, code: &str) -> BoilerPlate { + match language { + "c++" => BoilerPlate::new(Box::new(CppGenerator::new(code))), + "c" => BoilerPlate::new(Box::new(CGenerator::new(code))), + "java" => BoilerPlate::new(Box::new(JavaGenerator::new(code))), + "c#" => BoilerPlate::new(Box::new(CSharpGenerator::new(code))), // since all compilations go through this path, we have a Null generator whose // needs_boilerplate() always returns false. - _ => { - BoilerPlate::new(Box::new(Null::new(code))) - } + _ => BoilerPlate::new(Box::new(Null::new(code))), } -} \ No newline at end of file +} diff --git a/src/boilerplate/java.rs b/src/boilerplate/java.rs index b7ec645..e023531 100644 --- a/src/boilerplate/java.rs +++ b/src/boilerplate/java.rs @@ -2,43 +2,43 @@ use crate::boilerplate::generator::BoilerPlateGenerator; use crate::utls::constants::JAVA_MAIN_REGEX; pub struct JavaGenerator { - input : String + input: String, } impl BoilerPlateGenerator for JavaGenerator { fn new(input: &str) -> Self { let mut formated = input.to_string(); - formated = formated.replace(";", ";\n"); // separate lines by ; + formated = formated.replace(';', ";\n"); // separate lines by ; - Self { - input : formated - } + Self { input: formated } } fn generate(&self) -> String { let mut main_body = String::default(); let mut header = String::default(); - let mut lines = self.input.split("\n"); - while let Some(line) = lines.next() { + let lines = self.input.split('\n'); + for line in lines { let trimmed = line.trim(); if trimmed.starts_with("import") { header.push_str(&format!("{}\n", trimmed)); - } - else { + } else { main_body.push_str(&format!("{}\n", trimmed)) } } - format!("{}\nclass Main{{\npublic static void main(String[] args) {{\n{}}}}}", header, main_body) + format!( + "{}\nclass Main{{\npublic static void main(String[] args) {{\n{}}}}}", + header, main_body + ) } fn needs_boilerplate(&self) -> bool { for m in JAVA_MAIN_REGEX.captures_iter(&self.input) { - if let Some(_) = m.name("main") { + if m.name("main").is_some() { return false; } } - return true; + true } -} \ No newline at end of file +} diff --git a/src/boilerplate/mod.rs b/src/boilerplate/mod.rs index f26bc37..1b02b64 100644 --- a/src/boilerplate/mod.rs +++ b/src/boilerplate/mod.rs @@ -1,5 +1,5 @@ -pub mod generator; +pub mod c; pub mod cpp; -pub mod java; pub mod csharp; -pub mod c; \ No newline at end of file +pub mod generator; +pub mod java; diff --git a/src/cache.rs b/src/cache.rs index b4fc5ed..e418277 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,21 +1,21 @@ use std::collections::HashMap; use std::env; -use std::sync::Arc; use std::error::Error; +use std::sync::Arc; -use tokio::sync::RwLock; use tokio::sync::Mutex; +use tokio::sync::RwLock; -use serenity::prelude::{TypeMap, TypeMapKey}; use serenity::client::bridge::gateway::ShardManager; +use serenity::prelude::{TypeMap, TypeMapKey}; use crate::managers::stats::StatsManager; use crate::utls::blocklist::Blocklist; -use lru_cache::LruCache; -use serenity::model::channel::Message; use crate::managers::command::CommandManager; use crate::managers::compilation::CompilationManager; +use lru_cache::LruCache; +use serenity::model::channel::Message; /** Caching **/ @@ -56,19 +56,18 @@ impl TypeMapKey for ShardManagerCache { } pub struct MessageCacheEntry { - pub our_msg : Message, - pub original_msg : Message + pub our_msg: Message, + pub original_msg: Message, } impl MessageCacheEntry { - pub fn new(our_msg : Message, original_msg : Message) -> Self { + pub fn new(our_msg: Message, original_msg: Message) -> Self { MessageCacheEntry { our_msg, - original_msg + original_msg, } } } - /// Message cache to interact with our own messages after they are dispatched pub struct MessageCache; impl TypeMapKey for MessageCache { @@ -85,7 +84,7 @@ pub async fn fill( data: Arc>, prefix: &str, id: u64, - shard_manager: Arc> + shard_manager: Arc>, ) -> Result<(), Box> { let mut data = data.write().await; @@ -93,8 +92,15 @@ pub async fn fill( let mut map = HashMap::<&str, String>::new(); // optional additions - let emoji_identifiers = ["SUCCESS_EMOJI_ID", "SUCCESS_EMOJI_NAME", "LOADING_EMOJI_ID", "LOADING_EMOJI_NAME", "LOGO_EMOJI_NAME", "LOGO_EMOJI_ID"]; - for id in &emoji_identifiers{ + let emoji_identifiers = [ + "SUCCESS_EMOJI_ID", + "SUCCESS_EMOJI_NAME", + "LOADING_EMOJI_ID", + "LOADING_EMOJI_NAME", + "LOGO_EMOJI_NAME", + "LOGO_EMOJI_ID", + ]; + for id in &emoji_identifiers { if let Ok(envvar) = env::var(id) { if !envvar.is_empty() { map.insert(id, envvar); diff --git a/src/commands/asm.rs b/src/commands/asm.rs index b78d720..a0b85ce 100644 --- a/src/commands/asm.rs +++ b/src/commands/asm.rs @@ -3,16 +3,16 @@ use serenity::{ framework::standard::{macros::command, Args, CommandError, CommandResult}, }; -use crate::cache::{ConfigCache, MessageCache, CompilerCache, MessageCacheEntry}; +use crate::cache::{CompilerCache, ConfigCache, MessageCache, MessageCacheEntry}; use crate::utls::constants::*; -use crate::utls::{discordhelpers}; +use crate::utls::discordhelpers; use crate::utls::discordhelpers::embeds; -use serenity::builder::{CreateEmbed}; +use serenity::builder::CreateEmbed; use serenity::model::channel::{Message, ReactionType}; use serenity::model::user::User; -use crate::utls::{parser}; +use crate::utls::parser; #[command] #[bucket = "nospam"] @@ -30,21 +30,31 @@ pub async fn asm(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let data_read = ctx.data.read().await; let mut message_cache = data_read.get::().unwrap().lock().await; - message_cache.insert(msg.id.0, MessageCacheEntry::new(asm_embed.clone(), msg.clone())); + message_cache.insert( + msg.id.0, + MessageCacheEntry::new(asm_embed.clone(), msg.clone()), + ); debug!("Command executed"); Ok(()) } -pub async fn handle_request(ctx : Context, mut content : String, author : User, msg : &Message) -> Result { +pub async fn handle_request( + ctx: Context, + mut content: String, + author: User, + msg: &Message, +) -> Result { let data_read = ctx.data.read().await; let loading_reaction = { let botinfo_lock = data_read.get::().unwrap(); let botinfo = botinfo_lock.read().await; if let Some(loading_id) = botinfo.get("LOADING_EMOJI_ID") { - let loading_name = botinfo.get("LOADING_EMOJI_NAME").expect("Unable to find loading emoji name").clone(); + let loading_name = botinfo + .get("LOADING_EMOJI_NAME") + .expect("Unable to find loading emoji name") + .clone(); discordhelpers::build_reaction(loading_id.parse::()?, &loading_name) - } - else { + } else { ReactionType::Unicode(String::from("⏳")) } }; @@ -57,16 +67,25 @@ pub async fn handle_request(ctx : Context, mut content : String, author : User, // parse user input let comp_mngr = data_read.get::().unwrap(); - let result = match parser::get_components(&content, &author, Some(comp_mngr), &msg.referenced_message).await { - Ok(r) => r, - Err(e) => { - return Err(CommandError::from(format!("{}", e))); - } - }; + let result = + match parser::get_components(&content, &author, Some(comp_mngr), &msg.referenced_message) + .await + { + Ok(r) => r, + Err(e) => { + return Err(CommandError::from(format!("{}", e))); + } + }; // send out loading emote - if let Err(_) = msg.react(&ctx.http, loading_reaction.clone()).await { - return Err(CommandError::from("Unable to react to message, am I missing permissions to react or use external emoji?\n{}")) + if msg + .react(&ctx.http, loading_reaction.clone()) + .await + .is_err() + { + return Err(CommandError::from( + "Unable to react to message, am I missing permissions to react or use external emoji?", + )); } let comp_mngr_lock = comp_mngr.read().await; diff --git a/src/commands/block.rs b/src/commands/block.rs index 493ab9b..40a15f0 100644 --- a/src/commands/block.rs +++ b/src/commands/block.rs @@ -1,4 +1,4 @@ -use serenity::framework::standard::{macros::command, Args, CommandResult, CommandError}; +use serenity::framework::standard::{macros::command, Args, CommandError, CommandResult}; use serenity::model::prelude::*; use serenity::prelude::*; @@ -18,6 +18,8 @@ pub async fn block(ctx: &Context, msg: &Message, args: Args) -> CommandResult { blocklist.block(arg); - msg.channel_id.say(&ctx.http, format!("Blocked snowflake `{}`", &arg)).await?; + msg.channel_id + .say(&ctx.http, format!("Blocked snowflake `{}`", &arg)) + .await?; Ok(()) } diff --git a/src/commands/botinfo.rs b/src/commands/botinfo.rs index 6ee7b92..9a44b73 100644 --- a/src/commands/botinfo.rs +++ b/src/commands/botinfo.rs @@ -56,8 +56,10 @@ pub async fn botinfo(ctx: &Context, msg: &Message, _args: Args) -> CommandResult e.thumbnail(avatar); e.color(COLOR_OKAY); - let str = format!("Built from commit [{}]({}{}{})", - hash_short, github, "/commit/", hash_long); + let str = format!( + "Built from commit [{}]({}{}{})", + hash_short, github, "/commit/", hash_long + ); e.fields(vec![ ("Language", "Rust 2018", false), diff --git a/src/commands/compile.rs b/src/commands/compile.rs index 81c7922..ea99fb3 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -1,19 +1,19 @@ use serenity::framework::standard::{macros::command, Args, CommandResult}; use crate::cache::{MessageCache, MessageCacheEntry}; -use crate::utls::{parser, discordhelpers}; use crate::utls::constants::COLOR_OKAY; use crate::utls::discordhelpers::{embeds, is_success_embed}; +use crate::utls::{discordhelpers, parser}; use tokio::sync::RwLockReadGuard; -use serenity::framework::standard::CommandError; -use serenity::builder::{CreateEmbed}; +use serenity::builder::CreateEmbed; use serenity::client::Context; +use serenity::framework::standard::CommandError; use serenity::model::channel::{Message, ReactionType}; use serenity::model::user::User; -use crate::cache::{ConfigCache, StatsManagerCache, CompilerCache}; +use crate::cache::{CompilerCache, ConfigCache, StatsManagerCache}; use crate::managers::compilation::CompilationManager; #[command] @@ -36,21 +36,31 @@ pub async fn compile(ctx: &Context, msg: &Message, _args: Args) -> CommandResult discordhelpers::send_completion_react(ctx, &compilation_embed, compilation_successful).await?; let mut delete_cache = data_read.get::().unwrap().lock().await; - delete_cache.insert(msg.id.0, MessageCacheEntry::new(compilation_embed, msg.clone())); + delete_cache.insert( + msg.id.0, + MessageCacheEntry::new(compilation_embed, msg.clone()), + ); debug!("Command executed"); Ok(()) } -pub async fn handle_request(ctx : Context, mut content : String, author : User, msg : &Message) -> Result { +pub async fn handle_request( + ctx: Context, + mut content: String, + author: User, + msg: &Message, +) -> Result { let data_read = ctx.data.read().await; let loading_reaction = { let botinfo_lock = data_read.get::().unwrap(); let botinfo = botinfo_lock.read().await; if let Some(loading_id) = botinfo.get("LOADING_EMOJI_ID") { - let loading_name = botinfo.get("LOADING_EMOJI_NAME").expect("Unable to find loading emoji name").clone(); + let loading_name = botinfo + .get("LOADING_EMOJI_NAME") + .expect("Unable to find loading emoji name") + .clone(); discordhelpers::build_reaction(loading_id.parse::()?, &loading_name) - } - else { + } else { ReactionType::Unicode(String::from("⏳")) } }; @@ -63,16 +73,31 @@ pub async fn handle_request(ctx : Context, mut content : String, author : User, // parse user input let compilation_manager = data_read.get::().unwrap(); - let parse_result = parser::get_components(&content, &author, Some(&compilation_manager), &msg.referenced_message).await?; + let parse_result = parser::get_components( + &content, + &author, + Some(compilation_manager), + &msg.referenced_message, + ) + .await?; // send out loading emote - if let Err(_) = msg.react(&ctx.http, loading_reaction.clone()).await { - return Err(CommandError::from("Unable to react to message, am I missing permissions to react or use external emoji?\n{}")); + if msg + .react(&ctx.http, loading_reaction.clone()) + .await + .is_err() + { + return Err(CommandError::from( + "Unable to react to message, am I missing permissions to react or use external emoji?", + )); } // dispatch our req - let compilation_manager_lock : RwLockReadGuard = compilation_manager.read().await; - let awd = compilation_manager_lock.compile(&parse_result, &author).await; + let compilation_manager_lock: RwLockReadGuard = + compilation_manager.read().await; + let awd = compilation_manager_lock + .compile(&parse_result, &author) + .await; let result = match awd { Ok(r) => r, Err(e) => { @@ -82,9 +107,9 @@ pub async fn handle_request(ctx : Context, mut content : String, author : User, return Err(CommandError::from(format!("{}", e))); } }; - + // remove our loading emote - let _ = discordhelpers::delete_bot_reacts(&ctx, &msg, loading_reaction).await; + let _ = discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await; let is_success = is_success_embed(&result.1); let stats = data_read.get::().unwrap().lock().await; @@ -98,7 +123,11 @@ pub async fn handle_request(ctx : Context, mut content : String, author : User, if let Some(log) = config_lock.get("COMPILE_LOG") { if let Ok(id) = log.parse::() { - let guild = if msg.guild_id.is_some() {msg.guild_id.unwrap().0.to_string()} else {"<>".to_owned()}; + let guild = if msg.guild_id.is_some() { + msg.guild_id.unwrap().0.to_string() + } else { + "<>".to_owned() + }; let emb = embeds::build_complog_embed( is_success, &parse_result.code, @@ -112,4 +141,4 @@ pub async fn handle_request(ctx : Context, mut content : String, author : User, } Ok(result.1) -} \ No newline at end of file +} diff --git a/src/commands/compilers.rs b/src/commands/compilers.rs index a12dbd5..b74bbd2 100644 --- a/src/commands/compilers.rs +++ b/src/commands/compilers.rs @@ -2,11 +2,11 @@ use serenity::framework::standard::{macros::command, Args, CommandError, Command use serenity::model::prelude::*; use serenity::prelude::*; -use crate::cache::{ConfigCache, CompilerCache}; -use crate::utls::discordhelpers; -use crate::utls::parser::shortname_to_qualified; +use crate::cache::{CompilerCache, ConfigCache}; use crate::managers::compilation::RequestHandler; +use crate::utls::discordhelpers; use crate::utls::discordhelpers::menu::Menu; +use crate::utls::parser::shortname_to_qualified; #[command] pub async fn compilers(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { @@ -40,27 +40,31 @@ pub async fn compilers(ctx: &Context, msg: &Message, _args: Args) -> CommandResu } } RequestHandler::WandBox => { - match compiler_manager.wbox.get_compilers(&shortname_to_qualified(&language)) { - Some(s) => { + match compiler_manager + .wbox + .get_compilers(shortname_to_qualified(language)) + { + Some(s) => { for lang in s { langs.push(lang.name); } - }, + } None => { - return Err(CommandError::from( - format!("Unable to find compilers for target '{}'.", language), - )); + return Err(CommandError::from(format!( + "Unable to find compilers for target '{}'.", + language + ))); } }; } RequestHandler::None => { - return Err(CommandError::from( - format!("Unable to find compilers for target '{}'.", language), - )); + return Err(CommandError::from(format!( + "Unable to find compilers for target '{}'.", + language + ))); } } - let avatar = { let data_read = ctx.data.read().await; let botinfo_lock = data_read @@ -77,7 +81,7 @@ pub async fn compilers(ctx: &Context, msg: &Message, _args: Args) -> CommandResu "Supported Compilers", &avatar, &msg.author.tag(), - "" + "", ); let mut menu = Menu::new(ctx, msg, &pages); menu.run().await?; diff --git a/src/commands/cpp.rs b/src/commands/cpp.rs index b52a749..0a7c088 100644 --- a/src/commands/cpp.rs +++ b/src/commands/cpp.rs @@ -1,17 +1,17 @@ use serenity::{ - framework::standard::{macros::command, Args, CommandResult, CommandError}, + builder::CreateEmbed, + framework::standard::{macros::command, Args, CommandError, CommandResult}, model::prelude::*, prelude::*, - builder::CreateEmbed }; use crate::{ - utls::discordhelpers::embeds, - cache::{MessageCache, CompilerCache, ConfigCache, MessageCacheEntry}, - utls::discordhelpers, + cache::{CompilerCache, ConfigCache, MessageCache, MessageCacheEntry}, cppeval::eval::CppEval, + utls::discordhelpers, + utls::discordhelpers::embeds, + utls::discordhelpers::embeds::ToEmbed, utls::parser::ParserResult, - utls::discordhelpers::embeds::ToEmbed }; #[command] @@ -30,36 +30,52 @@ pub async fn cpp(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { // add delete cache let data_read = ctx.data.read().await; let mut delete_cache = data_read.get::().unwrap().lock().await; - delete_cache.insert(msg.id.0, MessageCacheEntry::new(compilation_embed, msg.clone())); + delete_cache.insert( + msg.id.0, + MessageCacheEntry::new(compilation_embed, msg.clone()), + ); Ok(()) } -pub async fn handle_request(ctx : Context, content : String, author : User, msg : &Message) -> std::result::Result { +pub async fn handle_request( + ctx: Context, + content: String, + author: User, + msg: &Message, +) -> std::result::Result { let loading_reaction = { let data_read = ctx.data.read().await; let botinfo_lock = data_read.get::().unwrap(); let botinfo = botinfo_lock.read().await; if let Some(loading_id) = botinfo.get("LOADING_EMOJI_ID") { - let loading_name = botinfo.get("LOADING_EMOJI_NAME").expect("Unable to find loading emoji name").clone(); + let loading_name = botinfo + .get("LOADING_EMOJI_NAME") + .expect("Unable to find loading emoji name") + .clone(); discordhelpers::build_reaction(loading_id.parse::()?, &loading_name) - } - else { + } else { ReactionType::Unicode(String::from("⏳")) } }; let start = content.find(' '); if start.is_none() { - return Err(CommandError::from("Invalid usage. View `;help cpp`")) + return Err(CommandError::from("Invalid usage. View `;help cpp`")); } let mut eval = CppEval::new(content.split_at(start.unwrap()).1); let out = eval.evaluate()?; // send out loading emote - if let Err(_) = msg.react(&ctx.http, loading_reaction.clone()).await { - return Err(CommandError::from("Unable to react to message, am I missing permissions to react or use external emoji?\n{}")) + if msg + .react(&ctx.http, loading_reaction.clone()) + .await + .is_err() + { + return Err(CommandError::from( + "Unable to react to message, am I missing permissions to react or use external emoji?", + )); } let fake_parse = ParserResult { @@ -68,7 +84,7 @@ pub async fn handle_request(ctx : Context, content : String, author : User, msg target: "g101".to_string(), code: out, options: vec![String::from("-O2"), String::from("-std=gnu++2a")], - args: vec![] + args: vec![], }; let data_read = ctx.data.read().await; @@ -79,12 +95,12 @@ pub async fn handle_request(ctx : Context, content : String, author : User, msg // we failed, lets remove the loading react so it doesn't seem like we're still processing discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction.clone()).await?; - return Err(CommandError::from(format!("{}", e))) + return Err(CommandError::from(format!("{}", e))); } }; // remove our loading emote discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await?; - return Ok(result.1.to_embed(&author, false)); -} \ No newline at end of file + Ok(result.1.to_embed(&author, false)) +} diff --git a/src/commands/format.rs b/src/commands/format.rs index d6e750f..c72f27d 100644 --- a/src/commands/format.rs +++ b/src/commands/format.rs @@ -1,20 +1,22 @@ -use serenity::framework::standard::{macros::command, Args, CommandResult, CommandError, Delimiter}; +use crate::cache::CompilerCache; +use crate::utls::parser::{get_message_attachment, ParserResult}; +use godbolt::Godbolt; +use serenity::framework::standard::{ + macros::command, Args, CommandError, CommandResult, Delimiter, +}; use serenity::model::prelude::*; use serenity::prelude::*; -use crate::cache::{CompilerCache}; -use crate::utls::parser::{ParserResult, get_message_attachment}; -use godbolt::Godbolt; use std::io::Write; #[command] -pub async fn format(ctx: &Context, msg: &Message, mut args : Args) -> CommandResult { +pub async fn format(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let mut fmt = String::from("clangformat"); let mut style = String::from("webkit"); if !args.is_empty() { // do not include ``` codeblocks into arg parsing.. lets just substr and replace args - let idx = msg.content.find("`"); + let idx = msg.content.find('`'); if let Some(idx) = idx { - let substr : String = msg.content.chars().take(idx).collect(); + let substr: String = msg.content.chars().take(idx).collect(); args = Args::new(&substr, &[Delimiter::Single(' ')]); args.advance(); } @@ -36,15 +38,19 @@ pub async fn format(ctx: &Context, msg: &Message, mut args : Args) -> CommandRes // validate user input for format in &gbolt.formats { - if format.format_type.to_ascii_lowercase().contains(&fmt.to_ascii_lowercase()) { + if format + .format_type + .to_ascii_lowercase() + .contains(&fmt.to_ascii_lowercase()) + { // fmt is now valid - lets ensure case correctness fmt = format.format_type.clone(); // if fmt has no styles - lets just empty the style string if format.styles.is_empty() { style = String::default(); - } - else { // fmt does have styles - validate result if possible + } else { + // fmt does have styles - validate result if possible for fmtstyle in &format.styles { if fmtstyle.to_ascii_lowercase().contains(&style) { style = fmtstyle.to_string(); @@ -60,52 +66,50 @@ pub async fn format(ctx: &Context, msg: &Message, mut args : Args) -> CommandRes if let Some(msgref) = &msg.referenced_message { let mut result = ParserResult::default(); - if crate::utls::parser::find_code_block(& mut result, &msgref.content, &msg.author).await? { + if crate::utls::parser::find_code_block(&mut result, &msgref.content, &msg.author).await? { lang_code = result.target.clone(); code = result.code - } - else { - if msgref.attachments.len() > 0 { - attachment_name = msgref.attachments[0].filename.clone(); - let (program_code, _) = get_message_attachment(&msgref.attachments).await?; - code = program_code; - } - else { - return Err(CommandError::from("Referenced message has no code or attachment")); - } - } - } - else { - if !msg.attachments.is_empty() { - attachment_name = msg.attachments[0].filename.clone(); - let (program_code, _) = get_message_attachment(&msg.attachments).await?; + } else if !msgref.attachments.is_empty() { + attachment_name = msgref.attachments[0].filename.clone(); + let (program_code, _) = get_message_attachment(&msgref.attachments).await?; code = program_code; } else { - let mut result = ParserResult::default(); - if crate::utls::parser::find_code_block(& mut result, &msg.content, &msg.author).await? { - lang_code = result.target.clone(); - code = result.code - } - else { - return Err(CommandError::from("Unable to find code to format!\n\nPlease reply to a message when executing this command or supply the code yourself in a code block or message attachment.")); - } + return Err(CommandError::from( + "Referenced message has no code or attachment", + )); + } + } else if !msg.attachments.is_empty() { + attachment_name = msg.attachments[0].filename.clone(); + let (program_code, _) = get_message_attachment(&msg.attachments).await?; + code = program_code; + } else { + let mut result = ParserResult::default(); + if crate::utls::parser::find_code_block(&mut result, &msg.content, &msg.author).await? { + lang_code = result.target.clone(); + code = result.code + } else { + return Err(CommandError::from("Unable to find code to format!\n\nPlease reply to a message when executing this command or supply the code yourself in a code block or message attachment.")); } } - let answer; { let result = Godbolt::format_code(&fmt, &style, &code, false, 4).await; match result { Ok(res) => { if res.exit != 0 { - return Err(CommandError::from("Formatter returned a non-zero exit code")); + return Err(CommandError::from( + "Formatter returned a non-zero exit code", + )); } else { answer = res.answer; } } Err(err) => { - return Err(CommandError::from(format!("An error occurred while formatting code: `{}`", err))); + return Err(CommandError::from(format!( + "An error occurred while formatting code: `{}`", + err + ))); } } } @@ -117,11 +121,22 @@ pub async fn format(ctx: &Context, msg: &Message, mut args : Args) -> CommandRes let _ = file.write_all(answer.as_bytes()); let _ = file.flush(); - msg.channel_id.send_message(&ctx.http, |msg| msg.add_file(path.as_str()).content("Powered by godbolt.org")).await?; + msg.channel_id + .send_message(&ctx.http, |msg| { + msg.add_file(path.as_str()) + .content("Powered by godbolt.org") + }) + .await?; let _ = std::fs::remove_file(&path); - } - else { - msg.reply(&ctx.http, format!("\n```{}\n{}```\n*Powered by godbolt.org*", lang_code, answer)).await?; + } else { + msg.reply( + &ctx.http, + format!( + "\n```{}\n{}```\n*Powered by godbolt.org*", + lang_code, answer + ), + ) + .await?; } Ok(()) -} \ No newline at end of file +} diff --git a/src/commands/formats.rs b/src/commands/formats.rs index 325abae..7d0867d 100644 --- a/src/commands/formats.rs +++ b/src/commands/formats.rs @@ -1,14 +1,14 @@ +use crate::cache::{CompilerCache, ConfigCache}; +use serenity::builder::CreateEmbed; use serenity::framework::standard::{macros::command, Args, CommandResult}; use serenity::model::prelude::*; use serenity::prelude::*; -use crate::cache::{ConfigCache, CompilerCache}; -use serenity::builder::CreateEmbed; -use crate::utls::constants::{ICON_HELP, COLOR_OKAY}; +use crate::utls::constants::{COLOR_OKAY, ICON_HELP}; use crate::utls::discordhelpers::embeds; #[command] -pub async fn formats(ctx: &Context, msg: &Message, _args : Args) -> CommandResult { +pub async fn formats(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let data = ctx.data.read().await; let prefix = { let botinfo_lock = data @@ -44,4 +44,4 @@ pub async fn formats(ctx: &Context, msg: &Message, _args : Args) -> CommandResul .await?; return Ok(()); -} \ No newline at end of file +} diff --git a/src/commands/help.rs b/src/commands/help.rs index 64ccaed..97647b7 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -53,9 +53,24 @@ pub async fn help(ctx: &Context, msg: &Message, args: Args) -> CommandResult { "cpp" | "c++" => { emb.title("c++/cpp command"); - emb.field("Example 1", format!("{}cpp {{ int a = 4; if (a > 3) {{ cout << \"true\"; }} }}", prefix), false); - emb.field("Example 2", format!("{}cpp << (4*12) << \"Hello world!\"", prefix), false); - emb.field("Example 3", format!("{}cpp << f(2); int f(int a) {{ return a*12; }}", prefix), false); + emb.field( + "Example 1", + format!( + "{}cpp {{ int a = 4; if (a > 3) {{ cout << \"true\"; }} }}", + prefix + ), + false, + ); + emb.field( + "Example 2", + format!("{}cpp << (4*12) << \"Hello world!\"", prefix), + false, + ); + emb.field( + "Example 3", + format!("{}cpp << f(2); int f(int a) {{ return a*12; }}", prefix), + false, + ); emb.field("Example 4", format!("{}cpp int main() {{ cout << \"Main\"; f(); }} void f() {{ cout << \"f()\"; }}", prefix), false); emb.field("Example 5", format!("*You may also use in-line code blocks if discord makes you escape some chars*\n{}cpp `<< (4*12) << \"\\\"Hello world!\\\"\"`", prefix), false); "Allows you to quickly compile and execute c++ snippets using geordi-like syntax.\nSee section 2.1 of http://eel.is/geordi/#syntax" diff --git a/src/commands/invite.rs b/src/commands/invite.rs index 2eb9b04..e4f600b 100644 --- a/src/commands/invite.rs +++ b/src/commands/invite.rs @@ -13,7 +13,9 @@ pub async fn invite(ctx: &Context, msg: &Message, _: Args) -> CommandResult { let emb = embeds::build_invite_embed(&invite); let mut emb_msg = embeds::embed_message(emb); - msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await?; + msg.channel_id + .send_message(&ctx.http, |_| &mut emb_msg) + .await?; Ok(()) } diff --git a/src/commands/languages.rs b/src/commands/languages.rs index f488ca9..b6486a3 100644 --- a/src/commands/languages.rs +++ b/src/commands/languages.rs @@ -2,7 +2,7 @@ use serenity::framework::standard::{macros::command, Args, CommandResult}; use serenity::model::prelude::*; use serenity::prelude::*; -use crate::cache::{ConfigCache, CompilerCache}; +use crate::cache::{CompilerCache, ConfigCache}; use crate::utls::discordhelpers; use crate::utls::discordhelpers::menu::Menu; @@ -34,7 +34,7 @@ pub async fn languages(ctx: &Context, msg: &Message, _args: Args) -> CommandResu botinfo.get("BOT_AVATAR").unwrap().clone() }; - let mut items_vec : Vec = items.into_iter().collect(); + let mut items_vec: Vec = items.into_iter().collect(); items_vec.sort(); let pages = discordhelpers::build_menu_items( @@ -43,7 +43,7 @@ pub async fn languages(ctx: &Context, msg: &Message, _args: Args) -> CommandResu "Supported Languages", &avatar, &msg.author.tag(), - "*\\* = supports assembly output*" + "*\\* = supports assembly output*", ); let mut menu = Menu::new(ctx, msg, &pages); menu.run().await?; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 0363f78..44ea35d 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,13 +1,13 @@ pub mod asm; +pub mod block; pub mod botinfo; pub mod compile; pub mod compilers; +pub mod cpp; +pub mod format; +pub mod formats; pub mod help; +pub mod invite; pub mod languages; pub mod ping; -pub mod block; pub mod unblock; -pub mod invite; -pub mod cpp; -pub mod format; -pub mod formats; diff --git a/src/commands/ping.rs b/src/commands/ping.rs index b0dc80f..9a958e9 100644 --- a/src/commands/ping.rs +++ b/src/commands/ping.rs @@ -6,12 +6,14 @@ use std::time::Instant; #[command] pub async fn ping(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let old = Instant::now(); let mut m = msg.channel_id.say(&ctx.http, "🏓 Pong!\n...").await?; let new = Instant::now(); - m.edit(ctx, |m| m.content(format!("🏓 Pong!\n{} ms", (new - old).as_millis()))).await?; + m.edit(ctx, |m| { + m.content(format!("🏓 Pong!\n{} ms", (new - old).as_millis())) + }) + .await?; debug!("Command executed"); Ok(()) } diff --git a/src/commands/unblock.rs b/src/commands/unblock.rs index ef3a38c..ca1ed96 100644 --- a/src/commands/unblock.rs +++ b/src/commands/unblock.rs @@ -1,4 +1,4 @@ -use serenity::framework::standard::{macros::command, Args, CommandResult, CommandError}; +use serenity::framework::standard::{macros::command, Args, CommandError, CommandResult}; use serenity::model::prelude::*; use serenity::prelude::*; @@ -18,6 +18,8 @@ pub async fn unblock(ctx: &Context, msg: &Message, args: Args) -> CommandResult blocklist.unblock(arg); - msg.channel_id.say(&ctx.http, format!("Unblocked snowflake `{}`", &arg)).await?; + msg.channel_id + .say(&ctx.http, format!("Unblocked snowflake `{}`", &arg)) + .await?; Ok(()) } diff --git a/src/cppeval/eval.rs b/src/cppeval/eval.rs index 530a4bf..f86bb5f 100644 --- a/src/cppeval/eval.rs +++ b/src/cppeval/eval.rs @@ -2,16 +2,18 @@ use core::fmt; #[derive(Debug, Clone)] pub struct EvalError { - details: String + details: String, } impl EvalError { fn new(msg: &str) -> EvalError { - EvalError{details: msg.to_string()} + EvalError { + details: msg.to_string(), + } } } impl fmt::Display for EvalError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f,"{}",self.details) + write!(f, "{}", self.details) } } impl std::error::Error for EvalError { @@ -21,48 +23,48 @@ impl std::error::Error for EvalError { } pub struct CppEval { - input : String, - output : String + input: String, + output: String, } impl CppEval { - pub fn new(input : &str) -> CppEval { + pub fn new(input: &str) -> CppEval { CppEval { input: input.trim().to_owned(), - output : String::default() + output: String::default(), } } - pub fn evaluate(& mut self) -> Result { + pub fn evaluate(&mut self) -> Result { // allow inline code if self.input.starts_with('`') && self.input.ends_with('`') { self.input.remove(0); - self.input.remove(self.input.len()-1); + self.input.remove(self.input.len() - 1); self.input = self.input.trim().to_string(); } // add bits we need for every request self.add_headers(); - if self.input.starts_with('{') { // parsing a statement here + if self.input.starts_with('{') { + // parsing a statement here if let Err(e) = self.do_statements() { - return Err(e) + return Err(e); } - } - else if self.input.starts_with("<<") { // just outputting + } else if self.input.starts_with("<<") { + // just outputting self.do_prints(); - } - else { // they're handling their own main + } else { + // they're handling their own main if let Err(e) = self.do_user_handled() { - return Err(e) + return Err(e); } } - Ok(self.output.clone()) } - fn do_user_handled(& mut self) -> Result<(), EvalError> { + fn do_user_handled(&mut self) -> Result<(), EvalError> { let re = regex::Regex::new(r"(([a-zA-Z]*?)[\s]+main\((.*?)\)[\s]+\{[\s\S]*?\})").unwrap(); if let Some(capture) = re.captures_iter(&self.input).next() { let main = capture[1].trim().to_string(); @@ -70,22 +72,22 @@ impl CppEval { self.output.push_str(&format!("{}\n", rest)); self.output.push_str(&format!("{}\n", main)); - } - else { - return Err(EvalError::new("No main() specified. Invalid request")) - + } else { + return Err(EvalError::new("No main() specified. Invalid request")); } Ok(()) } - fn do_statements(& mut self) -> Result<(), EvalError> { + fn do_statements(&mut self) -> Result<(), EvalError> { let end = self.get_statement_end(); if end == 0 { - return Err(EvalError::new("Parsing failure, detected unbalanced curly-brackets.")) + return Err(EvalError::new( + "Parsing failure, detected unbalanced curly-brackets.", + )); } - self.do_rest(end+1); + self.do_rest(end + 1); let statements = self.input[1..end].to_owned(); self.build_main(&statements); @@ -151,39 +153,38 @@ impl CppEval { stop_idx } - fn do_rest(& mut self, start_idx : usize) { + fn do_rest(&mut self, start_idx: usize) { let rest = &self.input[start_idx..]; self.output.push_str(rest.trim()); } - fn do_prints(& mut self) { - let input; - if let Some(statement_end) = self.input.find(';') { - self.do_rest(statement_end+1); + fn do_prints(&mut self) { + let input = if let Some(statement_end) = self.input.find(';') { + self.do_rest(statement_end + 1); + + self.input[..statement_end].to_owned() + } else { + self.input.clone() + }; - input = self.input[..statement_end].to_owned(); - } - else { - input = self.input.clone(); - } self.build_main(&format!("cout {};", input)); } - fn add_headers(& mut self) { + fn add_headers(&mut self) { self.output.push_str("#include \n"); self.output.push_str("using namespace std;\n"); //self.add_ostreaming(); } - fn build_main(& mut self, statements : &str) { - self.output.push_str(&format!("\nint main (void) {{\n{}\n}}", statements)); - + fn build_main(&mut self, statements: &str) { + self.output + .push_str(&format!("\nint main (void) {{\n{}\n}}", statements)); } -/* fn add_ostreaming(& mut self) { - let vec_print = include_str!("more_ostreaming.in"); - self.output.push_str(vec_print); - self.output.push_str("\n\n"); - } -*/ -} \ No newline at end of file + /* fn add_ostreaming(& mut self) { + let vec_print = include_str!("more_ostreaming.in"); + self.output.push_str(vec_print); + self.output.push_str("\n\n"); + } + */ +} diff --git a/src/cppeval/mod.rs b/src/cppeval/mod.rs index 448d5f6..4fe81ed 100644 --- a/src/cppeval/mod.rs +++ b/src/cppeval/mod.rs @@ -1 +1 @@ -pub mod eval; \ No newline at end of file +pub mod eval; diff --git a/src/events.rs b/src/events.rs index 9706d3d..9d0cc68 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,48 +1,48 @@ +use serenity::model::prelude::UnavailableGuild; use serenity::{ - framework::standard::DispatchError, - framework::standard::CommandResult, - framework::standard::macros::hook, - async_trait, - model::channel::Message, - model::guild::Guild, - model::id::ChannelId, - model::id::MessageId, - model::gateway::Ready, - prelude::*, - model::id::{GuildId}, - model::event::{MessageUpdateEvent}, - model::channel::{ReactionType}, - collector::CollectReaction, - model::interactions::{Interaction} + async_trait, collector::CollectReaction, framework::standard::macros::hook, + framework::standard::CommandResult, framework::standard::DispatchError, + model::channel::Message, model::channel::ReactionType, model::event::MessageUpdateEvent, + model::gateway::Ready, model::guild::Guild, model::id::ChannelId, model::id::GuildId, + model::id::MessageId, model::interactions::Interaction, prelude::*, }; -use serenity::model::prelude::UnavailableGuild; use tokio::sync::MutexGuard; use chrono::{DateTime, Utc}; use crate::{ - utls::discordhelpers::embeds, cache::*, - utls::discordhelpers, - managers::stats::StatsManager, - managers::compilation::RequestHandler, commands::compile::handle_request, + managers::compilation::RequestHandler, + managers::stats::StatsManager, + utls::discordhelpers, + utls::discordhelpers::embeds, utls::discordhelpers::embeds::embed_message, utls::discordhelpers::interactions::send_error_msg, - utls::parser::{get_message_attachment, shortname_to_qualified} + utls::parser::{get_message_attachment, shortname_to_qualified}, }; pub struct Handler; // event handler for serenity #[async_trait] trait ShardsReadyHandler { - async fn all_shards_ready(&self, ctx: &Context, stats: & mut MutexGuard<'_, StatsManager>, ready : &Ready); + async fn all_shards_ready( + &self, + ctx: &Context, + stats: &mut MutexGuard<'_, StatsManager>, + ready: &Ready, + ); } #[async_trait] impl ShardsReadyHandler for Handler { - async fn all_shards_ready(&self, ctx: &Context, stats: & mut MutexGuard<'_, StatsManager>, ready : &Ready) { + async fn all_shards_ready( + &self, + ctx: &Context, + stats: &mut MutexGuard<'_, StatsManager>, + ready: &Ready, + ) { let data = ctx.data.read().await; let mut info = data.get::().unwrap().write().await; info.insert("BOT_AVATAR", ready.user.avatar_url().unwrap()); @@ -84,11 +84,10 @@ impl EventHandler for Handler { stats.new_server().await; // ensure we're actually loaded in before we start posting our server counts - if stats.server_count() > 0 - { + if stats.server_count() > 0 { let new_stats = dbl::types::ShardStats::Cumulative { server_count: stats.server_count(), - shard_count: Some(stats.shard_count()) + shard_count: Some(stats.shard_count()), }; let dbl = data.get::().unwrap().read().await; @@ -105,7 +104,9 @@ impl EventHandler for Handler { if let Some(system_channel) = guild.system_channel_id { let mut message = embeds::embed_message(embeds::build_welcome_embed()); - let _ = system_channel.send_message(&ctx.http, |_| &mut message).await; + let _ = system_channel + .send_message(&ctx.http, |_| &mut message) + .await; } } } @@ -128,11 +129,10 @@ impl EventHandler for Handler { stats.leave_server().await; // ensure we're actually loaded in before we start posting our server counts - if stats.server_count() > 0 - { + if stats.server_count() > 0 { let new_stats = dbl::types::ShardStats::Cumulative { server_count: stats.server_count(), - shard_count: Some(stats.shard_count()) + shard_count: Some(stats.shard_count()), }; let dbl = data.get::().unwrap().read().await; @@ -157,58 +157,84 @@ impl EventHandler for Handler { cm.resolve_target(shortname_to_qualified(&language)) }; - if !matches!(target, RequestHandler::None) { + if !matches!(target, RequestHandler::None) { let reaction = { let botinfo = data.get::().unwrap().read().await; if let Some(id) = botinfo.get("LOGO_EMOJI_ID") { - let name = botinfo.get("LOGO_EMOJI_NAME").expect("Unable to find loading emoji name").clone(); + let name = botinfo + .get("LOGO_EMOJI_NAME") + .expect("Unable to find loading emoji name") + .clone(); discordhelpers::build_reaction(id.parse::().unwrap(), &name) - } - else { + } else { ReactionType::Unicode(String::from("💻")) } }; - if let Err(_) = new_message.react(&ctx.http, reaction.clone()).await { + if new_message + .react(&ctx.http, reaction.clone()) + .await + .is_err() + { return; } let collector = CollectReaction::new(ctx.clone()) .message_id(new_message.id) .timeout(core::time::Duration::new(30, 0)) - .filter(move |r| r.emoji.eq(&reaction)).await; + .filter(move |r| r.emoji.eq(&reaction)) + .await; let _ = new_message.delete_reactions(&ctx.http).await; - if let Some(_) = collector { - let emb = match handle_request(ctx.clone(), format!(";compile\n```{}\n{}\n```", language, code), new_message.author.clone(), &new_message).await { + if collector.is_some() { + let emb = match handle_request( + ctx.clone(), + format!(";compile\n```{}\n{}\n```", language, code), + new_message.author.clone(), + &new_message, + ) + .await + { Ok(emb) => emb, Err(e) => { - let emb = embeds::build_fail_embed(&new_message.author, &format!("{}", e)); + let emb = embeds::build_fail_embed( + &new_message.author, + &format!("{}", e), + ); let mut emb_msg = embeds::embed_message(emb); if let Ok(sent) = new_message .channel_id .send_message(&ctx.http, |_| &mut emb_msg) .await { - let mut message_cache = data.get::().unwrap().lock().await; - message_cache.insert(new_message.id.0, MessageCacheEntry::new(sent, new_message)); + let mut message_cache = + data.get::().unwrap().lock().await; + message_cache.insert( + new_message.id.0, + MessageCacheEntry::new(sent, new_message), + ); } return; } }; let mut emb_msg = embed_message(emb); emb_msg.reference_message(&new_message); - let _= new_message + let _ = new_message .channel_id .send_message(&ctx.http, |_| &mut emb_msg) .await; - } } } } } - async fn message_delete(&self, ctx: Context, _channel_id: ChannelId, id: MessageId, _guild_id: Option) { + async fn message_delete( + &self, + ctx: Context, + _channel_id: ChannelId, + id: MessageId, + _guild_id: Option, + ) { let data = ctx.data.read().await; let mut message_cache = data.get::().unwrap().lock().await; if let Some(msg) = message_cache.get_mut(id.as_u64()) { @@ -224,8 +250,15 @@ impl EventHandler for Handler { let mut message_cache = data.get::().unwrap().lock().await; if let Some(msg) = message_cache.get_mut(&new_data.id.0) { if let Some(new_msg) = new_data.content { - if let Some (author) = new_data.author { - discordhelpers::handle_edit(&ctx, new_msg, author, msg.our_msg.clone(), msg.original_msg.clone()).await; + if let Some(author) = new_data.author { + discordhelpers::handle_edit( + &ctx, + new_msg, + author, + msg.our_msg.clone(), + msg.original_msg.clone(), + ) + .await; } } } @@ -269,7 +302,10 @@ impl EventHandler for Handler { // send an edit, and if that fails we'll pivot to create a new interaction // response let fail_embed = embeds::build_fail_embed(&command.user, &e.to_string()); - if let Err(_) = send_error_msg(&ctx, &command, false, fail_embed.clone()).await { + if send_error_msg(&ctx, &command, false, fail_embed.clone()) + .await + .is_err() + { warn!("Sending new integration for error: {}", e); let _ = send_error_msg(&ctx, &command, true, fail_embed.clone()).await; } @@ -280,7 +316,7 @@ impl EventHandler for Handler { } #[hook] -pub async fn before(ctx: &Context, msg : &Message, _: &str) -> bool { +pub async fn before(ctx: &Context, msg: &Message, _: &str) -> bool { let data = ctx.data.read().await; { let stats = data.get::().unwrap().lock().await; @@ -302,17 +338,23 @@ pub async fn before(ctx: &Context, msg : &Message, _: &str) -> bool { let guild_blocklisted = blocklist.contains(guild_id); if author_blocklisted || guild_blocklisted { - let emb = embeds::build_fail_embed(&msg.author, - "This server or your user is blocked from executing commands. + let emb = embeds::build_fail_embed( + &msg.author, + "This server or your user is blocked from executing commands. This may have happened due to abuse, spam, or other reasons. - If you feel that this has been done in error, request an unban in the support server."); + If you feel that this has been done in error, request an unban in the support server.", + ); let mut emb_msg = embeds::embed_message(emb); - if msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await.is_ok() { + if msg + .channel_id + .send_message(&ctx.http, |_| &mut emb_msg) + .await + .is_ok() + { if author_blocklisted { warn!("Blocked user {} [{}]", msg.author.tag(), msg.author.id.0); - } - else { + } else { warn!("Blocked guild {}", guild_id); } } @@ -345,7 +387,6 @@ pub async fn after( } } - // push command executed to api let stats = data.get::().unwrap().lock().await; if stats.should_track() { @@ -356,8 +397,7 @@ pub async fn after( #[hook] pub async fn dispatch_error(ctx: &Context, msg: &Message, error: DispatchError, _: &str) { if let DispatchError::Ratelimited(_) = error { - let emb = - embeds::build_fail_embed(&msg.author, "You are sending requests too fast!"); + let emb = embeds::build_fail_embed(&msg.author, "You are sending requests too fast!"); let mut emb_msg = embeds::embed_message(emb); if msg .channel_id diff --git a/src/main.rs b/src/main.rs index 5559acf..0af4f40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,25 +1,23 @@ -#![type_length_limit="1146253"] +#![type_length_limit = "1146253"] mod apis; +mod boilerplate; mod cache; mod commands; -mod events; -mod stats; -mod utls; mod cppeval; +mod events; mod managers; -mod tests; mod slashcmds; -mod boilerplate; +mod stats; +mod tests; +mod utls; -use serenity::{ - framework::{standard::macros::group, StandardFramework}, -}; +use serenity::framework::{standard::macros::group, StandardFramework}; -use std::{env, error::Error}; -use std::collections::HashSet; use serenity::http::Http; use serenity::prelude::GatewayIntents; +use std::collections::HashSet; +use std::{env, error::Error}; use crate::apis::dbl::BotsListApi; @@ -29,13 +27,15 @@ extern crate pretty_env_logger; /** Command Registration **/ use crate::commands::{ - asm::*, botinfo::*, compile::*, compilers::*, - help::*, languages::*, ping::*, block::*, unblock::*, - invite::*, cpp::*, format::*, formats::* + asm::*, block::*, botinfo::*, compile::*, compilers::*, cpp::*, format::*, formats::*, help::*, + invite::*, languages::*, ping::*, unblock::*, }; #[group] -#[commands(botinfo, compile, languages, compilers, ping, help, asm, block, unblock, invite, cpp, formats, format)] +#[commands( + botinfo, compile, languages, compilers, ping, help, asm, block, unblock, invite, cpp, formats, + format +)] struct General; /** Spawn bot **/ @@ -47,8 +47,7 @@ async fn main() -> Result<(), Box> { pretty_env_logger::init(); - let token = env::var("BOT_TOKEN") - .expect("Expected bot token in .env file"); + let token = env::var("BOT_TOKEN").expect("Expected bot token in .env file"); let http = Http::new(&token); let (owners, bot_id) = match http.get_current_application_info().await { @@ -68,12 +67,10 @@ async fn main() -> Result<(), Box> { Err(why) => { warn!("Could not access application info: {:?}", why); warn!("Trying environment variable for bot id..."); - let id = env::var("BOT_ID") - .expect("Unable to find BOT_ID environment variable"); - let bot_id = id.parse::() - .expect("Invalid bot id"); + let id = env::var("BOT_ID").expect("Unable to find BOT_ID environment variable"); + let bot_id = id.parse::().expect("Invalid bot id"); (HashSet::new(), serenity::model::id::ApplicationId(bot_id)) - }, + } }; info!( @@ -85,10 +82,8 @@ async fn main() -> Result<(), Box> { .join(", ") ); - let prefix = env::var("BOT_PREFIX") - .expect("Expected bot prefix in .env file"); - let app_id = env::var("APPLICATION_ID") - .expect("Expected application id in .env file"); + let prefix = env::var("BOT_PREFIX").expect("Expected bot prefix in .env file"); + let app_id = env::var("APPLICATION_ID").expect("Expected application id in .env file"); let framework = StandardFramework::new() .before(events::before) .after(events::after) @@ -103,13 +98,19 @@ async fn main() -> Result<(), Box> { | GatewayIntents::GUILD_INTEGRATIONS | GatewayIntents::GUILD_MESSAGE_REACTIONS | GatewayIntents::GUILD_MESSAGES; - let mut client = serenity::Client::builder(token,intents) + let mut client = serenity::Client::builder(token, intents) .framework(framework) .event_handler(events::Handler) .application_id(app_id.parse::().unwrap()) .await?; - cache::fill(client.data.clone(), &prefix, bot_id.0, client.shard_manager.clone()).await?; + cache::fill( + client.data.clone(), + &prefix, + bot_id.0, + client.shard_manager.clone(), + ) + .await?; let dbl = BotsListApi::new(); if dbl.should_spawn() { diff --git a/src/managers/command.rs b/src/managers/command.rs index 4da1d4a..1de5fe2 100644 --- a/src/managers/command.rs +++ b/src/managers/command.rs @@ -1,57 +1,51 @@ -use std::collections::HashMap; +use crate::cache::StatsManagerCache; +use crate::{cache::CompilerCache, slashcmds}; +use serenity::model::interactions::application_command::ApplicationCommand; use serenity::{ client::Context, framework::standard::CommandResult, - model::interactions::application_command::{ApplicationCommandInteraction, ApplicationCommandOptionType, ApplicationCommandType} + model::interactions::application_command::{ + ApplicationCommandInteraction, ApplicationCommandOptionType, ApplicationCommandType, + }, }; -use serenity::model::interactions::application_command::ApplicationCommand; -use crate::{cache::{CompilerCache}, slashcmds}; -use crate::cache::StatsManagerCache; +use std::collections::HashMap; pub struct CommandManager { - commands_registered : bool + commands_registered: bool, } impl CommandManager { pub fn new() -> Self { CommandManager { - commands_registered: false + commands_registered: false, } } - pub async fn on_command(&self, ctx: &Context, command: &ApplicationCommandInteraction) -> CommandResult { + pub async fn on_command( + &self, + ctx: &Context, + command: &ApplicationCommandInteraction, + ) -> CommandResult { let command_name = command.data.name.to_lowercase(); // push command executed to api { let data = ctx.data.read().await; let stats = data.get::().unwrap().lock().await; if stats.should_track() { - stats.command_executed(&command_name, command.guild_id).await; + stats + .command_executed(&command_name, command.guild_id) + .await; } } match command_name.as_str() { - "compile" => { - slashcmds::compile::compile(ctx, command).await - }, - "assembly" => { - slashcmds::asm::asm(ctx, command).await - }, - "ping" => { - slashcmds::ping::ping(ctx, command).await - }, - "help" => { - slashcmds::help::help(ctx, command).await - }, - "cpp" => { - slashcmds::cpp::cpp(ctx, command).await - }, - "invite" => { - slashcmds::invite::invite(ctx, command).await - } - "format" => { - slashcmds::format::format(ctx, command).await - } + "compile" => slashcmds::compile::compile(ctx, command).await, + "assembly" => slashcmds::asm::asm(ctx, command).await, + "ping" => slashcmds::ping::ping(ctx, command).await, + "help" => slashcmds::help::help(ctx, command).await, + "cpp" => slashcmds::cpp::cpp(ctx, command).await, + "invite" => slashcmds::invite::invite(ctx, command).await, + "format" => slashcmds::format::format(ctx, command).await, e => { println!("OTHER: {}", e); Ok(()) @@ -61,7 +55,7 @@ impl CommandManager { pub async fn register_commands(&mut self, ctx: &Context) { if self.commands_registered { - return + return; } self.commands_registered = true; @@ -71,43 +65,55 @@ impl CommandManager { let mut godbolt_dict = HashMap::new(); for cache_entry in &compiler_manager.gbolt.cache { - godbolt_dict.insert(cache_entry.language.name.clone(), cache_entry.language.id.clone()); + godbolt_dict.insert( + cache_entry.language.name.clone(), + cache_entry.language.id.clone(), + ); } - if let Err(err) = ApplicationCommand::set_global_application_commands(&ctx.http, |builder| { - builder.create_application_command(|cmd| { - cmd.kind(ApplicationCommandType::Message).name("Compile") - }); - builder.create_application_command(|cmd| { - cmd.kind(ApplicationCommandType::Message).name("Assembly") - }); - builder.create_application_command(|cmd| { - cmd.kind(ApplicationCommandType::Message).name("Format") - }); - builder.create_application_command(|cmd| { - cmd.kind(ApplicationCommandType::ChatInput).name("help").description("Information on how to use the compiler") - }); - builder.create_application_command(|cmd| { - cmd.kind(ApplicationCommandType::ChatInput).name("invite").description("Grab my invite link to invite me to your server") - }); - builder.create_application_command(|cmd| { - cmd.kind(ApplicationCommandType::ChatInput).name("ping").description("Test my ping to Discord's endpoint") - }); - builder.create_application_command(|cmd| { - cmd.kind(ApplicationCommandType::ChatInput) - .name("cpp") - .description("Shorthand C++ compilation using geordi-like syntax") - .create_option(|opt| { - opt.required(false) - .name("input") - .kind(ApplicationCommandOptionType::String) - .description("Geordi-like input") - }) - }); + if let Err(err) = + ApplicationCommand::set_global_application_commands(&ctx.http, |builder| { + builder.create_application_command(|cmd| { + cmd.kind(ApplicationCommandType::Message).name("Compile") + }); + builder.create_application_command(|cmd| { + cmd.kind(ApplicationCommandType::Message).name("Assembly") + }); + builder.create_application_command(|cmd| { + cmd.kind(ApplicationCommandType::Message).name("Format") + }); + builder.create_application_command(|cmd| { + cmd.kind(ApplicationCommandType::ChatInput) + .name("help") + .description("Information on how to use the compiler") + }); + builder.create_application_command(|cmd| { + cmd.kind(ApplicationCommandType::ChatInput) + .name("invite") + .description("Grab my invite link to invite me to your server") + }); + builder.create_application_command(|cmd| { + cmd.kind(ApplicationCommandType::ChatInput) + .name("ping") + .description("Test my ping to Discord's endpoint") + }); + builder.create_application_command(|cmd| { + cmd.kind(ApplicationCommandType::ChatInput) + .name("cpp") + .description("Shorthand C++ compilation using geordi-like syntax") + .create_option(|opt| { + opt.required(false) + .name("input") + .kind(ApplicationCommandOptionType::String) + .description("Geordi-like input") + }) + }); - builder - }).await { + builder + }) + .await + { error!("Unable to create application commands: {}", err); } info!("Registered application commands"); } -} \ No newline at end of file +} diff --git a/src/managers/compilation.rs b/src/managers/compilation.rs index 25504fa..cd00620 100644 --- a/src/managers/compilation.rs +++ b/src/managers/compilation.rs @@ -1,29 +1,29 @@ use std::error::Error; +use serenity::builder::CreateEmbed; use serenity::framework::standard::CommandError; use serenity::model::user::User; -use serenity::builder::CreateEmbed; -use wandbox::{Wandbox, CompilationBuilder, WandboxError}; -use godbolt::{Godbolt, GodboltError, CompilationFilters, RequestOptions, CompilerOptions}; -use crate::boilerplate::generator::{boilerplate_factory}; +use crate::boilerplate::generator::boilerplate_factory; +use godbolt::{CompilationFilters, CompilerOptions, Godbolt, GodboltError, RequestOptions}; +use wandbox::{CompilationBuilder, Wandbox, WandboxError}; -use crate::utls::parser::ParserResult; -use crate::utls::discordhelpers::embeds::ToEmbed; use crate::utls::constants::USER_AGENT; +use crate::utls::discordhelpers::embeds::ToEmbed; +use crate::utls::parser::ParserResult; //Traits for compiler lookup pub trait LanguageResolvable { - fn resolve(&self, language : &str) -> bool; + fn resolve(&self, language: &str) -> bool; } impl LanguageResolvable for wandbox::Wandbox { - fn resolve(&self, language : &str) -> bool { + fn resolve(&self, language: &str) -> bool { self.is_valid_language(language) || self.is_valid_compiler_str(language) } } impl LanguageResolvable for godbolt::Godbolt { - fn resolve(&self, language : &str) -> bool { + fn resolve(&self, language: &str) -> bool { self.resolve(language).is_some() } } @@ -31,7 +31,7 @@ impl LanguageResolvable for godbolt::Godbolt { pub enum RequestHandler { None, WandBox, - CompilerExplorer + CompilerExplorer, } /// An abstraction for wandbox and godbolt. This object serves as the main interface between @@ -39,8 +39,8 @@ pub enum RequestHandler { /// works is: if the language supported is owned by Compiler Explorer-, we will use them. Otherwise, /// we fallback on to WandBox to see if they can fulfill the request pub struct CompilationManager { - pub wbox : Wandbox, - pub gbolt : Godbolt + pub wbox: Wandbox, + pub gbolt: Godbolt, } impl CompilationManager { @@ -53,29 +53,36 @@ impl CompilationManager { Ok(CompilationManager { wbox: wandbox::Wandbox::new(Some(broken_compilers), Some(broken_languages)).await?, - gbolt: Godbolt::new().await? + gbolt: Godbolt::new().await?, }) } - pub async fn compile(&self, parser_result : &ParserResult, author : &User) -> Result<(String, CreateEmbed), CommandError> { + pub async fn compile( + &self, + parser_result: &ParserResult, + author: &User, + ) -> Result<(String, CreateEmbed), CommandError> { return match self.resolve_target(&parser_result.target) { RequestHandler::CompilerExplorer => { let result = self.compiler_explorer(parser_result).await?; Ok((result.0, result.1.to_embed(author, false))) } RequestHandler::WandBox => { - let result = self.wandbox(&parser_result).await?; - Ok((result.0, result.1.to_embed(&author, false))) - } - RequestHandler::None => { - Err(CommandError::from( - format!("Unable to find compiler or language for target '{}'.", &parser_result.target), - )) + let result = self.wandbox(parser_result).await?; + Ok((result.0, result.1.to_embed(author, false))) } - } + RequestHandler::None => Err(CommandError::from(format!( + "Unable to find compiler or language for target '{}'.", + &parser_result.target + ))), + }; } - pub async fn assembly(&self, parse_result : &ParserResult, author : &User) -> Result<(String, CreateEmbed), CommandError> { + pub async fn assembly( + &self, + parse_result: &ParserResult, + author: &User, + ) -> Result<(String, CreateEmbed), CommandError> { let filters = CompilationFilters { binary: None, comment_only: Some(true), @@ -92,13 +99,17 @@ impl CompilationManager { user_arguments: parse_result.options.join(" "), compiler_options: CompilerOptions { skip_asm: false, - executor_request: false + executor_request: false, }, execute_parameters: Default::default(), - filters + filters, }; - let target = if parse_result.target == "haskell" { "ghc901" } else { &parse_result.target }; + let target = if parse_result.target == "haskell" { + "ghc901" + } else { + &parse_result.target + }; let resolution_result = self.gbolt.resolve(target); return match resolution_result { None => { @@ -108,10 +119,13 @@ impl CompilationManager { let response = Godbolt::send_request(&compiler, &parse_result.code, options, USER_AGENT).await?; Ok((compiler.lang, response.to_embed(author, true))) } - } + }; } - pub async fn compiler_explorer(&self, parse_result : &ParserResult) -> Result<(String, godbolt::GodboltResponse), GodboltError> { + pub async fn compiler_explorer( + &self, + parse_result: &ParserResult, + ) -> Result<(String, godbolt::GodboltResponse), GodboltError> { let filters = CompilationFilters { binary: None, comment_only: Some(true), @@ -128,16 +142,20 @@ impl CompilationManager { user_arguments: parse_result.options.join(" "), compiler_options: CompilerOptions { skip_asm: true, - executor_request: true + executor_request: true, }, execute_parameters: godbolt::ExecuteParameters { args: parse_result.args.clone(), - stdin: parse_result.stdin.clone() + stdin: parse_result.stdin.clone(), }, - filters + filters, }; - let target = if parse_result.target == "haskell" { "ghc901" } else { &parse_result.target }; + let target = if parse_result.target == "haskell" { + "ghc901" + } else { + &parse_result.target + }; let compiler = self.gbolt.resolve(target).unwrap(); // replace boilerplate code if needed @@ -148,30 +166,28 @@ impl CompilationManager { code = generator.generate(); } } - let response = Godbolt::send_request(&compiler, &code, options, USER_AGENT).await?; + let response = Godbolt::send_request(&compiler, &code, options, USER_AGENT).await?; Ok((compiler.lang, response)) } - pub fn resolve_target(&self, target : &str) -> RequestHandler { - if target == "scala" { - return RequestHandler::WandBox - } - else if target == "nim" { - return RequestHandler::WandBox + pub fn resolve_target(&self, target: &str) -> RequestHandler { + if target == "scala" || target == "nim" { + return RequestHandler::WandBox; } if self.gbolt.resolve(target).is_some() { RequestHandler::CompilerExplorer - } - else if self.wbox.resolve(target) { + } else if self.wbox.resolve(target) { RequestHandler::WandBox - } - else { + } else { RequestHandler::None } } - pub async fn wandbox(&self, parse_result : &ParserResult) -> Result<(String, wandbox::CompilationResult), WandboxError> { + pub async fn wandbox( + &self, + parse_result: &ParserResult, + ) -> Result<(String, wandbox::CompilationResult), WandboxError> { let lang = { let mut found = String::default(); for lang in self.wbox.get_languages() { @@ -210,9 +226,21 @@ impl CompilationManager { } pub fn slash_cmd_langs() -> [&'static str; 11] { - ["Python", "C++", "Javascript", "C", "Java", "Bash", "Lua", "C#", "Rust", "Php", "Perl"] + [ + "Python", + "C++", + "Javascript", + "C", + "Java", + "Bash", + "Lua", + "C#", + "Rust", + "Php", + "Perl", + ] } pub fn slash_cmd_langs_asm() -> [&'static str; 7] { ["C++", "C", "Haskell", "Java", "Python", "Rust", "Zig"] } -} \ No newline at end of file +} diff --git a/src/managers/mod.rs b/src/managers/mod.rs index 53c630a..1d8390b 100644 --- a/src/managers/mod.rs +++ b/src/managers/mod.rs @@ -1,3 +1,3 @@ +pub mod command; pub mod compilation; pub mod stats; -pub mod command; \ No newline at end of file diff --git a/src/managers/stats.rs b/src/managers/stats.rs index 19b9888..b2ce921 100644 --- a/src/managers/stats.rs +++ b/src/managers/stats.rs @@ -13,7 +13,7 @@ pub struct StatsManager { shards: u64, boot_count: Vec, leave_queue: u64, - join_queue: u64 + join_queue: u64, } impl StatsManager { @@ -26,7 +26,7 @@ impl StatsManager { leave_queue: 0, join_queue: 0, shards: 0, - boot_count: Vec::new() + boot_count: Vec::new(), } } @@ -67,9 +67,10 @@ impl StatsManager { } pub async fn new_server(&mut self) { - if self.servers < 1 { // not all shards have loaded in yet - queue the join for post_servers + if self.servers < 1 { + // not all shards have loaded in yet - queue the join for post_servers self.join_queue += 1; - return + return; } self.servers += 1; @@ -80,9 +81,10 @@ impl StatsManager { } pub async fn leave_server(&mut self) { - if self.servers < 1 { // not loaded in - queue leave for post_servers + if self.servers < 1 { + // not loaded in - queue leave for post_servers self.leave_queue += 1; - return + return; } self.servers -= 1; @@ -105,7 +107,7 @@ impl StatsManager { self.shards } - pub fn add_shard(& mut self, server_count : u64) { + pub fn add_shard(&mut self, server_count: u64) { self.shards += 1; self.boot_count.push(server_count); } diff --git a/src/slashcmds/asm.rs b/src/slashcmds/asm.rs index d6af38c..b7405b4 100644 --- a/src/slashcmds/asm.rs +++ b/src/slashcmds/asm.rs @@ -1,28 +1,36 @@ use serenity::{ client::Context, framework::standard::{CommandError, CommandResult}, - model::interactions::application_command::ApplicationCommandInteraction + model::interactions::application_command::ApplicationCommandInteraction, }; use crate::{ - cache::{CompilerCache}, - utls::discordhelpers::{interactions}, - managers::compilation::CompilationManager + cache::CompilerCache, managers::compilation::CompilationManager, + utls::discordhelpers::interactions, }; pub async fn asm(ctx: &Context, command: &ApplicationCommandInteraction) -> CommandResult { - interactions::handle_asm_or_compile_request(ctx, command, &CompilationManager::slash_cmd_langs_asm(), true, |parse_result| async move { - let data = ctx.data.read().await; - let compilation_manager= data.get::().unwrap(); - let compilation_manager_lock = compilation_manager.read().await; - let compilation_res = compilation_manager_lock.assembly(&parse_result, &command.user).await; - let result = match compilation_res { - Ok(r) => r, - Err(e) => { - return Err(CommandError::from(format!("{}", e))); - } - }; - Ok(result.1) - }).await?; + interactions::handle_asm_or_compile_request( + ctx, + command, + &CompilationManager::slash_cmd_langs_asm(), + true, + |parse_result| async move { + let data = ctx.data.read().await; + let compilation_manager = data.get::().unwrap(); + let compilation_manager_lock = compilation_manager.read().await; + let compilation_res = compilation_manager_lock + .assembly(&parse_result, &command.user) + .await; + let result = match compilation_res { + Ok(r) => r, + Err(e) => { + return Err(CommandError::from(format!("{}", e))); + } + }; + Ok(result.1) + }, + ) + .await?; Ok(()) -} \ No newline at end of file +} diff --git a/src/slashcmds/compile.rs b/src/slashcmds/compile.rs index 32aa3ad..2de6585 100644 --- a/src/slashcmds/compile.rs +++ b/src/slashcmds/compile.rs @@ -1,31 +1,38 @@ use serenity::{ - framework::standard::{CommandResult}, - framework::standard::CommandError, - client::Context, + client::Context, framework::standard::CommandError, framework::standard::CommandResult, model::interactions::application_command::ApplicationCommandInteraction, }; use tokio::sync::RwLockReadGuard; use crate::{ - managers::compilation::{CompilationManager}, - cache::{CompilerCache}, - utls::discordhelpers::{interactions}, + cache::CompilerCache, managers::compilation::CompilationManager, + utls::discordhelpers::interactions, }; -pub async fn compile(ctx: &Context, command : &ApplicationCommandInteraction) -> CommandResult { - interactions::handle_asm_or_compile_request(ctx, command, &CompilationManager::slash_cmd_langs(), false, |parse_result| async move { - let data = ctx.data.read().await; - let compilation_manager= data.get::().unwrap(); - let compilation_manager_lock : RwLockReadGuard = compilation_manager.read().await; - let compilation_res = compilation_manager_lock.compile(&parse_result, &command.user).await; - let result = match compilation_res { - Ok(r) => r, - Err(e) => { - return Err(CommandError::from(format!("{}", e))); - } - }; - Ok(result.1) - }).await?; +pub async fn compile(ctx: &Context, command: &ApplicationCommandInteraction) -> CommandResult { + interactions::handle_asm_or_compile_request( + ctx, + command, + &CompilationManager::slash_cmd_langs(), + false, + |parse_result| async move { + let data = ctx.data.read().await; + let compilation_manager = data.get::().unwrap(); + let compilation_manager_lock: RwLockReadGuard = + compilation_manager.read().await; + let compilation_res = compilation_manager_lock + .compile(&parse_result, &command.user) + .await; + let result = match compilation_res { + Ok(r) => r, + Err(e) => { + return Err(CommandError::from(format!("{}", e))); + } + }; + Ok(result.1) + }, + ) + .await?; Ok(()) -} \ No newline at end of file +} diff --git a/src/slashcmds/cpp.rs b/src/slashcmds/cpp.rs index 35130d1..13259fa 100644 --- a/src/slashcmds/cpp.rs +++ b/src/slashcmds/cpp.rs @@ -1,17 +1,13 @@ use serenity::{ - framework::standard::{CommandResult}, - model::prelude::*, - prelude::*, + framework::standard::CommandResult, model::interactions::application_command::ApplicationCommandInteraction, - model::interactions::application_command::ApplicationCommandInteractionDataOptionValue + model::interactions::application_command::ApplicationCommandInteractionDataOptionValue, + model::prelude::*, prelude::*, }; use crate::{ - cache::{CompilerCache}, - cppeval::eval::CppEval, - utls::parser::ParserResult, - utls::constants::{COLOR_OKAY}, - utls::discordhelpers::embeds::ToEmbed + cache::CompilerCache, cppeval::eval::CppEval, utls::constants::COLOR_OKAY, + utls::discordhelpers::embeds::ToEmbed, utls::parser::ParserResult, }; pub async fn cpp(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandResult { @@ -31,13 +27,16 @@ pub async fn cpp(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandR }) }) }).await?; - return Ok(()) + return Ok(()); } msg.create_interaction_response(&ctx.http, |resp| { resp.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }).await?; + }) + .await?; - let geordi_input = msg.data.options + let geordi_input = msg + .data + .options .get(0) .expect("Expected interaction option 0") .resolved @@ -54,7 +53,7 @@ pub async fn cpp(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandR target: "g101".to_string(), code: out, options: vec![String::from("-O2"), String::from("-std=gnu++2a")], - args: vec![] + args: vec![], }; let data_read = ctx.data.read().await; @@ -63,7 +62,8 @@ pub async fn cpp(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandR msg.edit_original_interaction_response(&ctx.http, |resp| { resp.add_embed(result.1.to_embed(&msg.user, false)) - }).await?; + }) + .await?; } Ok(()) -} \ No newline at end of file +} diff --git a/src/slashcmds/format.rs b/src/slashcmds/format.rs index 7420139..7ae1765 100644 --- a/src/slashcmds/format.rs +++ b/src/slashcmds/format.rs @@ -1,45 +1,45 @@ +use crate::{ + cache::CompilerCache, utls::constants::COLOR_WARN, utls::discordhelpers::interactions, + utls::parser, utls::parser::ParserResult, +}; +use futures_util::StreamExt; +use godbolt::{Format, Godbolt}; use serenity::{ - framework::standard::{CommandResult, CommandError}, - model::prelude::*, - prelude::*, builder::{CreateInteractionResponse, EditInteractionResponse}, + framework::standard::{CommandError, CommandResult}, model::interactions::application_command::ApplicationCommandInteraction, - model::interactions::message_component::ButtonStyle -}; -use crate::{ - cache::{CompilerCache}, - utls::parser::{ParserResult}, - utls::constants::COLOR_WARN, - utls::discordhelpers::interactions, - utls::parser + model::interactions::message_component::ButtonStyle, + model::prelude::*, + prelude::*, }; -use godbolt::{Format, Godbolt}; use std::time::Duration; -use futures_util::StreamExt; pub async fn format(ctx: &Context, command: &ApplicationCommandInteraction) -> CommandResult { let mut msg = None; let mut parse_result = ParserResult::default(); - for (_, value) in &command.data.resolved.messages { - if !parser::find_code_block(& mut parse_result, &value.content, &command.user).await? { - return Err(CommandError::from("Unable to find a codeblock to format!")) + if let Some((_, value)) = command.data.resolved.messages.iter().next() { + if !parser::find_code_block(&mut parse_result, &value.content, &command.user).await? { + return Err(CommandError::from("Unable to find a codeblock to format!")); } msg = Some(value); - break; } let data = ctx.data.read().await; let comp_mgr = data.get::().unwrap().read().await; let gbolt = &comp_mgr.gbolt; - command.create_interaction_response(&ctx.http, |response| { - create_formats_interaction(response, &gbolt.formats) - }).await?; + command + .create_interaction_response(&ctx.http, |response| { + create_formats_interaction(response, &gbolt.formats) + }) + .await?; // Handle response from select menu / button interactions let resp = command.get_interaction_response(&ctx.http).await?; - let mut cib = resp.await_component_interactions(&ctx.shard).timeout(Duration::from_secs(30)); + let mut cib = resp + .await_component_interactions(&ctx.shard) + .timeout(Duration::from_secs(30)); let mut cic = cib.build(); let mut formatter = String::from("clangformat"); let mut selected = false; @@ -63,16 +63,25 @@ pub async fn format(ctx: &Context, command: &ApplicationCommandInteraction) -> C // interaction expired... if !selected { - return Ok(()) + return Ok(()); } - let styles = &gbolt.formats.iter().find(|p| p.format_type == formatter).unwrap().styles; - command.edit_original_interaction_response(&ctx.http, |resp| { - create_styles_interaction(resp, styles) - }).await?; + let styles = &gbolt + .formats + .iter() + .find(|p| p.format_type == formatter) + .unwrap() + .styles; + command + .edit_original_interaction_response(&ctx.http, |resp| { + create_styles_interaction(resp, styles) + }) + .await?; let resp = command.get_interaction_response(&ctx.http).await?; - cib = resp.await_component_interactions(&ctx.shard).timeout(Duration::from_secs(30)); + cib = resp + .await_component_interactions(&ctx.shard) + .timeout(Duration::from_secs(30)); cic = cib.build(); selected = false; let mut style = String::from("WebKit"); @@ -95,111 +104,122 @@ pub async fn format(ctx: &Context, command: &ApplicationCommandInteraction) -> C // they let this expire if !selected { - return Ok(()) + return Ok(()); } - command.edit_original_interaction_response(&ctx.http, |resp| { - interactions::create_think_interaction(resp) - }).await.unwrap(); + command + .edit_original_interaction_response(&ctx.http, |resp| { + interactions::create_think_interaction(resp) + }) + .await + .unwrap(); let result = match Godbolt::format_code(&formatter, &style, &parse_result.code, true, 4).await { Ok(r) => r, - Err(e) => { - return Err(CommandError::from(format!("{}", e))) - } + Err(e) => return Err(CommandError::from(format!("{}", e))), }; - - command.edit_original_interaction_response(&ctx.http, |resp| { - resp - .set_embeds(Vec::new()) - .embed(|emb| { - emb.color(COLOR_WARN) - .description("Interaction completed, you may safely dismiss this message.") - }) - .components(|components| { - components.set_action_rows(Vec::new()) - }) - }).await.unwrap(); + command + .edit_original_interaction_response(&ctx.http, |resp| { + resp.set_embeds(Vec::new()) + .embed(|emb| { + emb.color(COLOR_WARN) + .description("Interaction completed, you may safely dismiss this message.") + }) + .components(|components| components.set_action_rows(Vec::new())) + }) + .await + .unwrap(); // dispatch final response - msg.unwrap().channel_id.send_message(&ctx.http, |new_msg| { - new_msg - .allowed_mentions(|mentions| { - mentions.replied_user(false) - }) - .reference_message(msg.unwrap()) - .content(format!("```{}\n{}\n```Requested by: {}", if parse_result.target.is_empty() {""} else {&parse_result.target}, result.answer, command.user.tag())) - }).await?; + msg.unwrap() + .channel_id + .send_message(&ctx.http, |new_msg| { + new_msg + .allowed_mentions(|mentions| mentions.replied_user(false)) + .reference_message(msg.unwrap()) + .content(format!( + "```{}\n{}\n```Requested by: {}", + if parse_result.target.is_empty() { + "" + } else { + &parse_result.target + }, + result.answer, + command.user.tag() + )) + }) + .await?; Ok(()) } -fn create_styles_interaction<'a>(response: &'a mut EditInteractionResponse, styles: &Vec) -> &'a mut EditInteractionResponse { - response - .content("Select a style:") - .components(|cmps| { - cmps.create_action_row(|row| { - row.create_select_menu(|menu| { - menu.custom_id("style") - .options(|opts| { - for style in styles { - opts.create_option(|opt| { - opt.label(style) - .value(style); - if style == "WebKit" { - opt.default_selection(true); - } - opt - }); +fn create_styles_interaction<'a>( + response: &'a mut EditInteractionResponse, + styles: &Vec, +) -> &'a mut EditInteractionResponse { + response.content("Select a style:").components(|cmps| { + cmps.create_action_row(|row| { + row.create_select_menu(|menu| { + menu.custom_id("style").options(|opts| { + for style in styles { + opts.create_option(|opt| { + opt.label(style).value(style); + if style == "WebKit" { + opt.default_selection(true); } - opts - }) + opt + }); + } + opts }) }) - .create_action_row(|row| { - row.create_button(|btn| { - btn.custom_id("select") - .label("Select") - .style(ButtonStyle::Primary) - }) - }) }) + .create_action_row(|row| { + row.create_button(|btn| { + btn.custom_id("select") + .label("Select") + .style(ButtonStyle::Primary) + }) + }) + }) } -fn create_formats_interaction<'this,'a>(response: &'this mut CreateInteractionResponse<'a>, formats: &Vec) -> &'this mut CreateInteractionResponse<'a> { +fn create_formats_interaction<'this, 'a>( + response: &'this mut CreateInteractionResponse<'a>, + formats: &Vec, +) -> &'this mut CreateInteractionResponse<'a> { response .kind(InteractionResponseType::ChannelMessageWithSource) .interaction_response_data(|data| { data.content("Select a formatter to use:") - .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) - .components(|cmps| { - cmps.create_action_row(|row| { - row.create_select_menu(|menu| { - menu.custom_id("formatter") - .options(|opts| { - for format in formats { - opts.create_option(|opt| { - opt.label(&format.name) - .value(&format.format_type) - .description(&format.exe); - if format.format_type == "clangformat" { - opt.default_selection(true); - } - opt - }); - } - opts + .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + .components(|cmps| { + cmps.create_action_row(|row| { + row.create_select_menu(|menu| { + menu.custom_id("formatter").options(|opts| { + for format in formats { + opts.create_option(|opt| { + opt.label(&format.name) + .value(&format.format_type) + .description(&format.exe); + if format.format_type == "clangformat" { + opt.default_selection(true); + } + opt + }); + } + opts + }) }) }) - }) - .create_action_row(|row| { - row.create_button(|btn| { - btn.custom_id("select") - .label("Select") - .style(ButtonStyle::Primary) + .create_action_row(|row| { + row.create_button(|btn| { + btn.custom_id("select") + .label("Select") + .style(ButtonStyle::Primary) + }) }) }) - }) - }) -} \ No newline at end of file + }) +} diff --git a/src/slashcmds/help.rs b/src/slashcmds/help.rs index d3cc83a..dde2d71 100644 --- a/src/slashcmds/help.rs +++ b/src/slashcmds/help.rs @@ -1,14 +1,10 @@ use serenity::{ - framework::standard::{CommandResult}, - client::Context, + client::Context, framework::standard::CommandResult, model::interactions::application_command::ApplicationCommandInteraction, - model::prelude::message_component::ButtonStyle + model::prelude::message_component::ButtonStyle, }; -use crate::{ - cache::ConfigCache, - utls::constants::* -}; +use crate::{cache::ConfigCache, utls::constants::*}; pub async fn help(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandResult { let data = ctx.data.read().await; @@ -19,50 +15,49 @@ pub async fn help(ctx: &Context, msg: &ApplicationCommandInteraction) -> Command let stats_link = botinfo.get("STATS_LINK").unwrap(); msg.create_interaction_response(&ctx.http, |resp| { resp.interaction_response_data(|data| { - data - .embed(|emb| { + data.embed(|emb| { emb.color(COLOR_OKAY) - .description("Hello! I can compile code for you. To compile code, \ + .description( + "Hello! I can compile code for you. To compile code, \ first post a code block containing code, right click the message, \ - go to the Apps dropdown, and select the Compile option!") + go to the Apps dropdown, and select the Compile option!", + ) .thumbnail(ICON_HELP) - }) - - .embed(|emb| { - emb.color(COLOR_WARN) - .description("If you are unfamiliar with Markdown, codeblocks can be created by \ + }) + .embed(|emb| { + emb.color(COLOR_WARN).description( + "If you are unfamiliar with Markdown, codeblocks can be created by \ formatting your message as the following.\n\ \\`\\`\\`\n\ \n\ - \\`\\`\\`") - }) - .components(|components| { - components.create_action_row(|row| { - row - .create_button(|btn| { - btn.label("Invite me") - .style(ButtonStyle::Link) - .url(invite_link) - }) - .create_button(|btn| { - btn.label("Vote for us") - .style(ButtonStyle::Link) - .url(dbl_link) - }) - .create_button(|btn| { - btn.label("GitHub") - .style(ButtonStyle::Link) - .url(github_link) - }) - .create_button(|btn| { - btn.label("Stats") - .style(ButtonStyle::Link) - .url(stats_link) - }) + \\`\\`\\`", + ) + }) + .components(|components| { + components.create_action_row(|row| { + row.create_button(|btn| { + btn.label("Invite me") + .style(ButtonStyle::Link) + .url(invite_link) + }) + .create_button(|btn| { + btn.label("Vote for us") + .style(ButtonStyle::Link) + .url(dbl_link) + }) + .create_button(|btn| { + btn.label("GitHub") + .style(ButtonStyle::Link) + .url(github_link) + }) + .create_button(|btn| { + btn.label("Stats").style(ButtonStyle::Link).url(stats_link) }) }) + }) }) - }).await?; + }) + .await?; debug!("Command executed"); Ok(()) } diff --git a/src/slashcmds/invite.rs b/src/slashcmds/invite.rs index 8950233..651b7ae 100644 --- a/src/slashcmds/invite.rs +++ b/src/slashcmds/invite.rs @@ -1,19 +1,15 @@ use serenity::{ - framework::standard::{CommandResult}, - model::prelude::*, + framework::standard::CommandResult, + model::interactions::application_command::ApplicationCommandInteraction, model::prelude::*, prelude::*, - model::interactions::application_command::ApplicationCommandInteraction }; -use crate::{ - cache::ConfigCache, - utls::discordhelpers::embeds -}; +use crate::{cache::ConfigCache, utls::discordhelpers::embeds}; pub async fn invite(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandResult { let invite_link = { let data = ctx.data.read().await; - let config= data.get::().unwrap(); + let config = data.get::().unwrap(); let config_cache = config.read().await; config_cache.get("INVITE_LINK").unwrap().clone() }; @@ -22,10 +18,9 @@ pub async fn invite(ctx: &Context, msg: &ApplicationCommandInteraction) -> Comma msg.create_interaction_response(&ctx.http, |resp| { resp.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|data| { - data.add_embed(emb) - }) - }).await?; + .interaction_response_data(|data| data.add_embed(emb)) + }) + .await?; Ok(()) } diff --git a/src/slashcmds/mod.rs b/src/slashcmds/mod.rs index 0a36e3e..9297183 100644 --- a/src/slashcmds/mod.rs +++ b/src/slashcmds/mod.rs @@ -1,7 +1,7 @@ pub mod asm; pub mod compile; -pub mod help; -pub mod ping; -pub mod invite; pub mod cpp; pub mod format; +pub mod help; +pub mod invite; +pub mod ping; diff --git a/src/slashcmds/ping.rs b/src/slashcmds/ping.rs index e9cf644..b3faa6b 100644 --- a/src/slashcmds/ping.rs +++ b/src/slashcmds/ping.rs @@ -1,25 +1,23 @@ use serenity::{ - framework::standard::{CommandResult}, - model::prelude::*, + framework::standard::CommandResult, + model::interactions::application_command::ApplicationCommandInteraction, model::prelude::*, prelude::*, - model::interactions::application_command::ApplicationCommandInteraction }; use std::time::Instant; pub async fn ping(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandResult { - let old = Instant::now(); msg.create_interaction_response(&ctx.http, |resp| { resp.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|data| { - data.content("🏓 Pong!\n...") - }) - }).await?; + .interaction_response_data(|data| data.content("🏓 Pong!\n...")) + }) + .await?; let new = Instant::now(); msg.edit_original_interaction_response(&ctx.http, |resp| { resp.content(format!("🏓 Pong!\n{} ms", (new - old).as_millis())) - }).await?; + }) + .await?; Ok(()) } diff --git a/src/stats/structures.rs b/src/stats/structures.rs index fcde6cb..1e8e41e 100644 --- a/src/stats/structures.rs +++ b/src/stats/structures.rs @@ -2,8 +2,8 @@ use reqwest::header::{ACCEPT, USER_AGENT}; use reqwest::Response; use serde::*; use serenity::async_trait; -use std::sync::Arc; use serenity::model::id::GuildId; +use std::sync::Arc; #[async_trait] pub trait Sendable: Serialize { @@ -41,7 +41,7 @@ impl CommandRequest { CommandRequest { key: String::from(""), command: String::from(command), - guild: guild_str + guild: guild_str, } } } @@ -91,12 +91,11 @@ pub struct LegacyRequest { } impl LegacyRequest { pub fn new(amount: Option) -> LegacyRequest { - let request_type; - if amount.is_some() { - request_type = "servers" + let request_type = if amount.is_some() { + "servers" } else { - request_type = "request" - } + "request" + }; LegacyRequest { key: String::from(""), diff --git a/src/tests/boilerplate/cpp.rs b/src/tests/boilerplate/cpp.rs index 2f366fc..4d4c671 100644 --- a/src/tests/boilerplate/cpp.rs +++ b/src/tests/boilerplate/cpp.rs @@ -1,51 +1,60 @@ #[cfg(test)] - use crate::boilerplate::cpp::CppGenerator; use crate::boilerplate::generator::BoilerPlateGenerator; #[tokio::test] async fn standard_needs_boilerplate() { - let gen = CppGenerator::new("std::string str = \"test\";\n\ - std::cout << str;"); + let gen = CppGenerator::new( + "std::string str = \"test\";\n\ + std::cout << str;", + ); assert!(gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate() { - let gen = CppGenerator::new("int main() {\n\ + let gen = CppGenerator::new( + "int main() {\n\ std::string str = \"test\";\n\ std::cout << str;\n\ return 0;\n\ - }"); + }", + ); assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate2() { - let gen = CppGenerator::new("int main ( ) {\n\ + let gen = CppGenerator::new( + "int main ( ) {\n\ std::string str = \"test\";\n\ std::cout << str;\n\ return 0;\n\ - }"); + }", + ); assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate3() { - let gen = CppGenerator::new("int main\n(void) {\n\ + let gen = CppGenerator::new( + "int main\n(void) {\n\ std::string str = \"test\";\n\ std::cout << str;\n\ return 0;\n\ - }"); + }", + ); assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate4() { - let gen = CppGenerator::new("void main (void) {\n\ + let gen = CppGenerator::new( + "void main (void) {\n\ std::string str = \"test\";\n\ std::cout << str;\n\ return 0;\n\ - }"); + }", + ); assert!(!gen.needs_boilerplate()); -} \ No newline at end of file +} diff --git a/src/tests/boilerplate/java.rs b/src/tests/boilerplate/java.rs index eaf00ca..8f7a77e 100644 --- a/src/tests/boilerplate/java.rs +++ b/src/tests/boilerplate/java.rs @@ -1,56 +1,67 @@ +use crate::boilerplate::generator::BoilerPlateGenerator; #[cfg(test)] - use crate::boilerplate::java::JavaGenerator; -use crate::boilerplate::generator::BoilerPlateGenerator; #[tokio::test] async fn standard_needs_boilerplate() { - let gen = JavaGenerator::new("String str = \"test\";\n\ - System.out.println(str)"); + let gen = JavaGenerator::new( + "String str = \"test\";\n\ + System.out.println(str)", + ); assert!(gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate() { - let gen = JavaGenerator::new("class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ public static void main(String[] args) {\n\ }\n\ - }\n"); + }\n", + ); assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate2() { - let gen = JavaGenerator::new("class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ public static void main (String[] args) {\n\ }\n\ - }\n"); + }\n", + ); assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate3() { - let gen = JavaGenerator::new("class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ public static void main\t(String[] args) {\n\ }\n\ - }\n"); + }\n", + ); assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate4() { - let gen = JavaGenerator::new("class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ public static void main (String[] args) {\n\ }\n\ - }\n"); + }\n", + ); assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate5() { - let gen = JavaGenerator::new("class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ public static final void main (String[] args) {\n\ }\n\ - }\n"); + }\n", + ); assert!(!gen.needs_boilerplate()); -} \ No newline at end of file +} diff --git a/src/tests/boilerplate/mod.rs b/src/tests/boilerplate/mod.rs index 449cb89..9ce6c6d 100644 --- a/src/tests/boilerplate/mod.rs +++ b/src/tests/boilerplate/mod.rs @@ -1,4 +1,4 @@ #[cfg(test)] pub mod cpp; #[cfg(test)] -pub mod java; \ No newline at end of file +pub mod java; diff --git a/src/tests/cpp.rs b/src/tests/cpp.rs index 5eae63d..53a417d 100644 --- a/src/tests/cpp.rs +++ b/src/tests/cpp.rs @@ -1,5 +1,4 @@ #[cfg(test)] - use crate::cppeval::eval::CppEval; #[tokio::test] diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 21af0ae..ad70db2 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,5 +1,5 @@ +pub mod boilerplate; +pub mod cpp; #[cfg(test)] - +#[cfg_attr(feature = "cargo-clippy", allow(clippy))] pub mod parser; -pub mod cpp; -pub mod boilerplate; \ No newline at end of file diff --git a/src/tests/parser.rs b/src/tests/parser.rs index 1003071..aa72493 100644 --- a/src/tests/parser.rs +++ b/src/tests/parser.rs @@ -1,10 +1,9 @@ +use crate::managers::compilation::CompilationManager; +use crate::utls::parser::get_components; #[cfg(test)] - use serenity::model::user::User; -use crate::utls::parser::get_components; -use crate::managers::compilation::CompilationManager; -use tokio::sync::RwLock; use std::sync::Arc; +use tokio::sync::RwLock; #[tokio::test] async fn standard_parse() { @@ -18,7 +17,7 @@ async fn standard_parse() { let reply = None; let result = get_components(input, &dummy_user, None, &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -43,7 +42,7 @@ async fn standard_parse_args() { let reply = None; let result = get_components(input, &dummy_user, None, &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -59,13 +58,11 @@ async fn standard_parse_args() { #[tokio::test] async fn standard_parse_url() { let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile c++ < https://pastebin.com/raw/ERqDRZva" - ); + let input = indoc::indoc!(";compile c++ < https://pastebin.com/raw/ERqDRZva"); let reply = None; let result = get_components(input, &dummy_user, None, &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -81,13 +78,11 @@ async fn standard_parse_url() { #[tokio::test] async fn standard_parse_url_args() { let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile c++ < https://pastebin.com/raw/ERqDRZva\ntest1 test2" - ); + let input = indoc::indoc!(";compile c++ < https://pastebin.com/raw/ERqDRZva\ntest1 test2"); let reply = None; let result = get_components(input, &dummy_user, None, &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -113,7 +108,7 @@ async fn standard_parse_stdin() { let reply = None; let result = get_components(input, &dummy_user, None, &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -141,7 +136,7 @@ async fn standard_parse_block_stdin() { let reply = None; let result = get_components(input, &dummy_user, None, &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -167,7 +162,7 @@ async fn standard_parse_deduce_compiler() { let reply = None; let cm = Arc::new(RwLock::new(CompilationManager::new().await.unwrap())); let result = get_components(input, &dummy_user, Some(&cm), &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -193,7 +188,7 @@ async fn standard_parse_deduce_compiler_upper_case() { let reply = None; let cm = Arc::new(RwLock::new(CompilationManager::new().await.unwrap())); let result = get_components(input, &dummy_user, Some(&cm), &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -219,7 +214,7 @@ async fn standard_parse_late_deduce_compiler() { let reply = None; let cm = Arc::new(RwLock::new(CompilationManager::new().await.unwrap())); let result = get_components(input, &dummy_user, Some(&cm), &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -248,7 +243,7 @@ async fn standard_parse_late_deduce_compiler_block_stdin() { let reply = None; let cm = Arc::new(RwLock::new(CompilationManager::new().await.unwrap())); let result = get_components(input, &dummy_user, Some(&cm), &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -264,14 +259,12 @@ async fn standard_parse_late_deduce_compiler_block_stdin() { #[tokio::test] async fn standard_parse_one_line() { let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile js ```console.log(\"beehee\");```" - ); + let input = indoc::indoc!(";compile js ```console.log(\"beehee\");```"); let reply = None; let cm = Arc::new(RwLock::new(CompilationManager::new().await.unwrap())); let result = get_components(input, &dummy_user, Some(&cm), &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -287,13 +280,11 @@ async fn standard_parse_one_line() { #[tokio::test] async fn standard_parse_args_one_line() { let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile c -O3```int main() {return 232;}```" - ); + let input = indoc::indoc!(";compile c -O3```int main() {return 232;}```"); let reply = None; let result = get_components(input, &dummy_user, None, &reply).await; - if let Err(_) = &result { + if result.is_err() { assert!(false, "Parser failed."); } @@ -304,4 +295,4 @@ async fn standard_parse_args_one_line() { assert_eq!(parser_result.stdin, ""); assert_eq!(parser_result.url, ""); assert_eq!(parser_result.code, "int main() {return 232;}"); -} \ No newline at end of file +} diff --git a/src/utls/blocklist.rs b/src/utls/blocklist.rs index 590b5b4..d24d3b1 100644 --- a/src/utls/blocklist.rs +++ b/src/utls/blocklist.rs @@ -4,7 +4,7 @@ use serde::*; #[derive(Serialize, Deserialize, Default)] pub struct Blocklist { - list : Vec + list: Vec, } impl Blocklist { @@ -14,44 +14,40 @@ impl Blocklist { return Blocklist::create_blocklist(); } - let json = fs::read_to_string(path) - .expect("Unable to read blocklist.json"); + let json = fs::read_to_string(path).expect("Unable to read blocklist.json"); - let list : Blocklist = serde_json::from_str(&json) - .expect("Unable to deserialize blocklist.json"); + let list: Blocklist = + serde_json::from_str(&json).expect("Unable to deserialize blocklist.json"); list } - pub fn contains(&self, snowflake : u64) -> bool { + pub fn contains(&self, snowflake: u64) -> bool { self.list.contains(&snowflake.to_string()) } - pub fn block(&mut self, snowflake : u64) { + pub fn block(&mut self, snowflake: u64) { let snowflake = snowflake.to_string(); self.list.push(snowflake); self.write(); } - pub fn unblock(&mut self, snowflake : u64) { + pub fn unblock(&mut self, snowflake: u64) { let snowflake = snowflake.to_string(); self.list.retain(|x| *x != snowflake); self.write(); } pub fn write(&self) { - let json = serde_json::to_string(self) - .expect("Unable to serialize blocklist.json"); + let json = serde_json::to_string(self).expect("Unable to serialize blocklist.json"); - fs::write("blocklist.json", json) - .expect("Unable to create blocklist.json!"); + fs::write("blocklist.json", json).expect("Unable to create blocklist.json!"); } fn create_blocklist() -> Blocklist { let list = Blocklist { - list : Default::default() + list: Default::default(), }; list.write(); list } } - diff --git a/src/utls/constants.rs b/src/utls/constants.rs index 9d39997..8793d32 100644 --- a/src/utls/constants.rs +++ b/src/utls/constants.rs @@ -10,26 +10,35 @@ pub const ICON_VOTE: &str = "https://i.imgur.com/VXbdwSQ.png"; pub const ICON_HELP: &str = "https://i.imgur.com/TNzxfMB.png"; pub const ICON_INVITE: &str = "https://i.imgur.com/CZFt69d.png"; pub const COMPILER_ICON: &str = "http://i.michaelwflaherty.com/u/XedLoQWCVc.png"; -pub const USER_AGENT : &str = const_format::formatcp!("discord-compiler-bot/{}", env!("CARGO_PKG_VERSION")); -pub const URL_ALLOW_LIST : [&str; 4] = ["pastebin.com", "gist.githubusercontent.com", "hastebin.com", "raw.githubusercontent.com"]; +pub const USER_AGENT: &str = + const_format::formatcp!("discord-compiler-bot/{}", env!("CARGO_PKG_VERSION")); +pub const URL_ALLOW_LIST: [&str; 4] = [ + "pastebin.com", + "gist.githubusercontent.com", + "hastebin.com", + "raw.githubusercontent.com", +]; pub const MAX_OUTPUT_LEN: usize = 250; pub const MAX_ERROR_LEN: usize = 997; - // Boilerplate Regexes lazy_static! { - pub static ref JAVA_MAIN_REGEX: Regex = Regex::new("\"[^\"]+\"|(?P
void[\\s]+?main[\\s]*?\\()").unwrap(); - pub static ref C_LIKE_MAIN_REGEX: Regex = Regex::new("\"[^\"]+\"|(?P
main[\\s]*?\\()").unwrap(); - pub static ref CSHARP_MAIN_REGEX: Regex = Regex::new("\"[^\"]+\"|(?P
static[\\s]+?void[\\s]+?Main[\\s]*?\\()").unwrap(); + pub static ref JAVA_MAIN_REGEX: Regex = + Regex::new("\"[^\"]+\"|(?P
void[\\s]+?main[\\s]*?\\()").unwrap(); + pub static ref C_LIKE_MAIN_REGEX: Regex = + Regex::new("\"[^\"]+\"|(?P
main[\\s]*?\\()").unwrap(); + pub static ref CSHARP_MAIN_REGEX: Regex = + Regex::new("\"[^\"]+\"|(?P
static[\\s]+?void[\\s]+?Main[\\s]*?\\()").unwrap(); } + /* - Discord limits the size of the amount of compilers we can display to users, for some languages - we'll just grab the first 25 from our API, for C & C++ we will create a curated list manually. + Discord limits the size of the amount of compilers we can display to users, for some languages + we'll just grab the first 25 from our API, for C & C++ we will create a curated list manually. - If you'd like to see a change here feel free to pr to remove one, but justify it's removal. - */ -pub const CPP_ASM_COMPILERS : [[&str; 2]; 25] = [ + If you'd like to see a change here feel free to pr to remove one, but justify it's removal. +*/ +pub const CPP_ASM_COMPILERS: [[&str; 2]; 25] = [ ["x86-64 clang (trunk)", "clang_trunk"], ["x86-64 clang 13.0.1", "clang1301"], ["x86-64 clang 14.0.0", "clang1400"], @@ -57,7 +66,7 @@ pub const CPP_ASM_COMPILERS : [[&str; 2]; 25] = [ ["mips64 gcc 11.2.0", "mips112064"], ]; -pub const CPP_EXEC_COMPILERS : [[&str; 2]; 18] = [ +pub const CPP_EXEC_COMPILERS: [[&str; 2]; 18] = [ ["x86-64 clang (trunk)", "clang_trunk"], ["x86-64 clang 13.0.1", "clang1301"], ["x86-64 clang 14.0.0", "clang1400"], @@ -78,7 +87,7 @@ pub const CPP_EXEC_COMPILERS : [[&str; 2]; 18] = [ ["x64 msvc v19.31", "vcpp_v19_31_x64"], ]; -pub const C_ASM_COMPILERS : [[&str; 2]; 23] = [ +pub const C_ASM_COMPILERS: [[&str; 2]; 23] = [ ["x86-64 clang (trunk)", "cclang_trunk"], ["x86-64 clang 13.0.1", "cclang1301"], ["x86-64 clang 14.0.0", "cclang1400"], @@ -104,7 +113,7 @@ pub const C_ASM_COMPILERS : [[&str; 2]; 23] = [ ["mips64 gcc 11.2.0", "cmips112064"], ]; -pub const C_EXEC_COMPILERS : [[&str; 2]; 23] = [ +pub const C_EXEC_COMPILERS: [[&str; 2]; 23] = [ ["x86-64 clang (trunk)", "cclang_trunk"], ["x86-64 clang 13.0.1", "cclang1301"], ["x86-64 clang 14.0.0", "cclang1400"], @@ -128,4 +137,4 @@ pub const C_EXEC_COMPILERS : [[&str; 2]; 23] = [ ["armv8-a clang 10.0.1", "armv8-cclang1001"], ["mips gcc 11.2.0", "cmips1120"], ["mips64 gcc 11.2.0", "cmips112064"], -]; \ No newline at end of file +]; diff --git a/src/utls/discordhelpers/embeds.rs b/src/utls/discordhelpers/embeds.rs index 87693f4..c1bf35a 100644 --- a/src/utls/discordhelpers/embeds.rs +++ b/src/utls/discordhelpers/embeds.rs @@ -2,21 +2,21 @@ use std::str; use serenity::{ builder::{CreateEmbed, CreateMessage}, + client::Context, model::prelude::*, - client::Context }; use wandbox::*; use crate::utls::constants::*; -use crate::utls::{discordhelpers}; +use crate::utls::discordhelpers; pub trait ToEmbed { - fn to_embed(self, author : &User, any : T) -> CreateEmbed; + fn to_embed(self, author: &User, any: T) -> CreateEmbed; } impl ToEmbed for wandbox::CompilationResult { - fn to_embed(self, author: &User, _ : bool) -> CreateEmbed { + fn to_embed(self, author: &User, _: bool) -> CreateEmbed { let mut embed = CreateEmbed::default(); if !self.status.is_empty() { @@ -35,7 +35,7 @@ impl ToEmbed for wandbox::CompilationResult { embed.color(COLOR_OKAY); } if !self.compiler_all.is_empty() { - let str = discordhelpers::conform_external_str(&self.compiler_all, MAX_ERROR_LEN); + let str = discordhelpers::conform_external_str(&self.compiler_all, MAX_ERROR_LEN); embed.field("Compiler Output", format!("```{}\n```", str), false); } if !self.program_all.is_empty() { @@ -46,25 +46,18 @@ impl ToEmbed for wandbox::CompilationResult { embed.field("URL", &self.url, false); } - embed.footer(|f| { - f.text(format!( - "{} | wandbox.org", - author.tag() - )) - }); + embed.footer(|f| f.text(format!("{} | wandbox.org", author.tag()))); embed } } - impl ToEmbed for godbolt::GodboltResponse { - fn to_embed(self, author: &User, assembly : bool) -> CreateEmbed { + fn to_embed(self, author: &User, assembly: bool) -> CreateEmbed { let mut embed = CreateEmbed::default(); if self.code == 0 { embed.color(COLOR_OKAY); - } - else { + } else { embed.color(COLOR_FAIL); // if it's an assembly request let's just handle the error case here. @@ -109,12 +102,12 @@ impl ToEmbed for godbolt::GodboltResponse { i += 1; } if !append.is_empty() { - let title; - if i > 1 { - title = format!("Assembly Output Pt. {}", i); + let title = if i > 1 { + format!("Assembly Output Pt. {}", i) } else { - title = String::from("Assembly Output") - } + String::from("Assembly Output") + }; + embed.field(&title, format!("```x86asm\n{}\n```", &append), false); output = true; } @@ -123,9 +116,7 @@ impl ToEmbed for godbolt::GodboltResponse { embed.title("Compilation successful"); embed.description("No assembly generated."); } - - } - else { + } else { let mut output = String::default(); for line in &self.stdout { output.push_str(&format!("{}\n", line.text)); @@ -145,7 +136,7 @@ impl ToEmbed for godbolt::GodboltResponse { let stderr = errs.trim(); let mut output = false; if !stdout.is_empty() { - let str = discordhelpers::conform_external_str(stdout, MAX_OUTPUT_LEN); + let str = discordhelpers::conform_external_str(stdout, MAX_OUTPUT_LEN); embed.field("Program Output", format!("```\n{}\n```", str), false); output = true; } @@ -170,28 +161,25 @@ impl ToEmbed for godbolt::GodboltResponse { appendstr = format!(" | {} ms", time); } - embed.footer(|f| { - f.text(format!( - "{} | godbolt.org{}", - author.tag(), appendstr - )) - }); + embed.footer(|f| f.text(format!("{} | godbolt.org{}", author.tag(), appendstr))); embed } } -pub async fn edit_message_embed(ctx : &Context, old : & mut Message, emb : CreateEmbed) { - let _ = old.edit(ctx, |m| { - m.embed(|e| { - e.0 = emb.0; - e - }); - m - }).await; +pub async fn edit_message_embed(ctx: &Context, old: &mut Message, emb: CreateEmbed) { + let _ = old + .edit(ctx, |m| { + m.embed(|e| { + e.0 = emb.0; + e + }); + m + }) + .await; } #[allow(dead_code)] -pub fn build_small_compilation_embed(author: &User, res: & mut CompilationResult) -> CreateEmbed { +pub fn build_small_compilation_embed(author: &User, res: &mut CompilationResult) -> CreateEmbed { let mut embed = CreateEmbed::default(); if res.status != "0" { embed.color(COLOR_FAIL); @@ -241,19 +229,30 @@ pub fn build_welcome_embed() -> CreateEmbed { embed.thumbnail(COMPILER_ICON); embed.description("Thanks for inviting me to your discord server!"); embed.field("Introduction", "I can take code that you give me and execute it, display generated assembly, or format it!", true); - embed.field("Example Request", ";compile python\n```py\nprint('hello world')\n```", true); + embed.field( + "Example Request", + ";compile python\n```py\nprint('hello world')\n```", + true, + ); embed.field("Learning Time!", "If you like reading the manuals of things, read our [getting started](https://github.com/Headline/discord-compiler-bot/wiki/Getting-Started) wiki or if you are confident type `;help` to view all commands.", false); embed.field("Support", "If you ever run into any issues please stop by our [support server](https://discord.com/invite/nNNEZ6s) and we'll give you a hand.", true); - embed.footer(|f| f.text("powered by godbolt.org & wandbox.org // created by Michael Flaherty (Headline#9999)")); + embed.footer(|f| { + f.text( + "powered by godbolt.org & wandbox.org // created by Michael Flaherty (Headline#9999)", + ) + }); embed } -pub fn build_invite_embed(invite_link : &str) -> CreateEmbed { +pub fn build_invite_embed(invite_link: &str) -> CreateEmbed { let mut embed = CreateEmbed::default(); embed.title("Invite Link"); embed.color(COLOR_OKAY); embed.thumbnail(ICON_INVITE); - let description = format!("Click the link below to invite me to your server!\n\n[Invite me!]({})", invite_link); + let description = format!( + "Click the link below to invite me to your server!\n\n[Invite me!]({})", + invite_link + ); embed.description(description); embed } @@ -320,10 +319,10 @@ pub fn build_fail_embed(author: &User, err: &str) -> CreateEmbed { pub fn build_publish_embed() -> CreateEmbed { let mut embed = CreateEmbed::default(); - embed - .color(COLOR_WARN) - .description("This result will not be visible to others until you click the publish button.\n\n \ + embed.color(COLOR_WARN).description( + "This result will not be visible to others until you click the publish button.\n\n \ If you are unhappy with your results please start a new compilation request \ - and dismiss this message."); + and dismiss this message.", + ); embed } diff --git a/src/utls/discordhelpers/interactions.rs b/src/utls/discordhelpers/interactions.rs index 8f26938..8741f95 100644 --- a/src/utls/discordhelpers/interactions.rs +++ b/src/utls/discordhelpers/interactions.rs @@ -1,97 +1,109 @@ -use std::{ - sync::Arc, - future::Future, - time::Duration -}; +use std::{future::Future, sync::Arc, time::Duration}; use futures_util::StreamExt; use serenity::{ + builder::EditInteractionResponse, builder::{CreateComponents, CreateEmbed, CreateInteractionResponse, CreateSelectMenuOption}, client::Context, framework::standard::CommandError, - model::interactions::{InteractionApplicationCommandCallbackDataFlags, InteractionResponseType}, + model::interactions::application_command::ApplicationCommandInteraction, model::interactions::message_component::{ActionRowComponent, ButtonStyle, InputTextStyle}, + model::interactions::{ + InteractionApplicationCommandCallbackDataFlags, InteractionResponseType, + }, model::prelude::message_component::MessageComponentInteraction, model::prelude::modal::ModalSubmitInteraction, - builder::EditInteractionResponse, - model::interactions::application_command::ApplicationCommandInteraction, }; +use crate::cache::ConfigCache; use crate::{ - utls::discordhelpers::embeds, - utls::constants::{C_ASM_COMPILERS, C_EXEC_COMPILERS, COLOR_OKAY, CPP_ASM_COMPILERS, CPP_EXEC_COMPILERS}, - cache::StatsManagerCache, - managers::compilation::{RequestHandler}, cache::CompilerCache, + cache::StatsManagerCache, + managers::compilation::RequestHandler, utls::constants::COLOR_WARN, - utls::parser::{ParserResult}, + utls::constants::{ + COLOR_OKAY, CPP_ASM_COMPILERS, CPP_EXEC_COMPILERS, C_ASM_COMPILERS, C_EXEC_COMPILERS, + }, + utls::discordhelpers::embeds, utls::discordhelpers::embeds::build_publish_embed, - utls::{discordhelpers, parser} + utls::parser::ParserResult, + utls::{discordhelpers, parser}, }; -use crate::cache::ConfigCache; -pub fn create_compile_panel(compiler_options : Vec) -> CreateComponents { +pub fn create_compile_panel(compiler_options: Vec) -> CreateComponents { let mut components = CreateComponents::default(); - components.create_action_row(|row| { - row.create_select_menu(|menu| { - menu.custom_id("compiler_select").options(|opts| { - opts.set_options(compiler_options) + components + .create_action_row(|row| { + row.create_select_menu(|menu| { + menu.custom_id("compiler_select") + .options(|opts| opts.set_options(compiler_options)) }) }) - }) - .create_action_row(|row3| { - row3.create_button(|button| { - button.label("Compile").style(ButtonStyle::Primary).custom_id(1) - }) - .create_button(|button| { - button.label("More options").style(ButtonStyle::Secondary).custom_id(2) - }) - }); + .create_action_row(|row3| { + row3.create_button(|button| { + button + .label("Compile") + .style(ButtonStyle::Primary) + .custom_id(1) + }) + .create_button(|button| { + button + .label("More options") + .style(ButtonStyle::Secondary) + .custom_id(2) + }) + }); components } -pub async fn create_more_options_panel(ctx: &Context, interaction : Arc, parse_result: &mut ParserResult) -> Result>, CommandError> { - interaction.create_interaction_response(&ctx.http, |resp| { - resp.kind(InteractionResponseType::Modal) - .interaction_response_data(|data| { - data - .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) - .custom_id("more_options_panel") - .content("Select a compiler:") - .title("More options") - .components(|components| { - components.create_action_row(|row2| { - row2.create_input_text(|txt| { - txt - .custom_id("compiler_options") - .label("Compiler options") - .style(InputTextStyle::Short) - .placeholder("-Wall -O3 etc.") - .required(false) - }) - }).create_action_row(|row2| { - row2.create_input_text(|txt| { - txt - .custom_id("cmdlineargs") - .label("Command line arguments") - .style(InputTextStyle::Short) - .placeholder("") - .required(false) - }) - }).create_action_row(|row2| { - row2.create_input_text(|txt| { - txt - .custom_id("stdin") - .label("Standard input") - .style(InputTextStyle::Paragraph) - .placeholder("stdin") - .required(false) - }) +pub async fn create_more_options_panel( + ctx: &Context, + interaction: Arc, + parse_result: &mut ParserResult, +) -> Result>, CommandError> { + interaction + .create_interaction_response(&ctx.http, |resp| { + resp.kind(InteractionResponseType::Modal) + .interaction_response_data(|data| { + data.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + .custom_id("more_options_panel") + .content("Select a compiler:") + .title("More options") + .components(|components| { + components + .create_action_row(|row2| { + row2.create_input_text(|txt| { + txt.custom_id("compiler_options") + .label("Compiler options") + .style(InputTextStyle::Short) + .placeholder("-Wall -O3 etc.") + .required(false) + }) + }) + .create_action_row(|row2| { + row2.create_input_text(|txt| { + txt.custom_id("cmdlineargs") + .label("Command line arguments") + .style(InputTextStyle::Short) + .placeholder("") + .required(false) + }) + }) + .create_action_row(|row2| { + row2.create_input_text(|txt| { + txt.custom_id("stdin") + .label("Standard input") + .style(InputTextStyle::Paragraph) + .placeholder("stdin") + .required(false) + }) + }) }) - }) - }) - }).await.unwrap(); + }) + }) + .await + .unwrap(); println!("awaiting response..."); let msg = interaction.get_interaction_response(&ctx.http).await?; @@ -99,23 +111,31 @@ pub async fn create_more_options_panel(ctx: &Context, interaction : Arc Result, CommandError> { +pub async fn create_compiler_options( + ctx: &Context, + language: &str, + is_assembly: bool, +) -> Result, CommandError> { let mut options = Vec::new(); let data = ctx.data.read().await; @@ -123,12 +143,19 @@ pub async fn create_compiler_options(ctx: &Context, language: &str, is_assembly let target = compilers.resolve_target(language); match target { RequestHandler::None => { - return Err(CommandError::from(format!("Unsupported language: {}", language))) + return Err(CommandError::from(format!( + "Unsupported language: {}", + language + ))) } RequestHandler::WandBox => { let compilers = compilers.wbox.get_compilers(language).unwrap(); let mut first = true; - for compiler in if compilers.len() > 25 {&compilers[..24]} else {&compilers}{ + for compiler in if compilers.len() > 25 { + &compilers[..24] + } else { + &compilers + } { let mut option = CreateSelectMenuOption::default(); if first { option.default_selection(true); @@ -148,7 +175,13 @@ pub async fn create_compiler_options(ctx: &Context, language: &str, is_assembly for cache in &compilers.gbolt.cache { if cache.language.name.to_lowercase() == language { - list = Some(cache.compilers.iter().map(|c| [c.name.as_str(), c.id.as_str()]).collect()); + list = Some( + cache + .compilers + .iter() + .map(|c| [c.name.as_str(), c.id.as_str()]) + .collect(), + ); default = Some(&cache.language.default_compiler) } } @@ -157,16 +190,13 @@ pub async fn create_compiler_options(ctx: &Context, language: &str, is_assembly if language == "c++" { if is_assembly { list = Some(CPP_ASM_COMPILERS.to_vec()); - } - else { + } else { list = Some(CPP_EXEC_COMPILERS.to_vec()); } - } - else if language == "c" { + } else if language == "c" { if is_assembly { list = Some(C_ASM_COMPILERS.to_vec()); - } - else { + } else { list = Some(C_EXEC_COMPILERS.to_vec()); } } @@ -192,15 +222,17 @@ pub async fn create_compiler_options(ctx: &Context, language: &str, is_assembly Ok(options) } -pub fn edit_to_confirmation_interaction<'a>(result: &CreateEmbed, resp: &'a mut EditInteractionResponse) -> &'a mut EditInteractionResponse { - resp - .set_embeds(Vec::from([build_publish_embed(), result.clone()])) +pub fn edit_to_confirmation_interaction<'a>( + result: &CreateEmbed, + resp: &'a mut EditInteractionResponse, +) -> &'a mut EditInteractionResponse { + resp.set_embeds(Vec::from([build_publish_embed(), result.clone()])) .components(|components| { - components.set_action_rows(Vec::new()) + components + .set_action_rows(Vec::new()) .create_action_row(|row| { row.create_button(|btn| { - btn - .custom_id("publish") + btn.custom_id("publish") .label("Publish") .style(ButtonStyle::Primary) }) @@ -208,18 +240,18 @@ pub fn edit_to_confirmation_interaction<'a>(result: &CreateEmbed, resp: &'a mut }) } -pub fn create_language_interaction<'this, 'a>(resp : &'this mut CreateInteractionResponse<'a>, languages : & [&str]) -> &'this mut CreateInteractionResponse<'a> { - resp - .kind(InteractionResponseType::ChannelMessageWithSource) +pub fn create_language_interaction<'this, 'a>( + resp: &'this mut CreateInteractionResponse<'a>, + languages: &[&str], +) -> &'this mut CreateInteractionResponse<'a> { + resp.kind(InteractionResponseType::ChannelMessageWithSource) .interaction_response_data(|data| { - data - .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + data.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) .content("Select a language:") .components(|components| { components.create_action_row(|row| { row.create_select_menu(|menu| { menu.custom_id("language_select").options(|opts| { - for language in languages { let mut option = CreateSelectMenuOption::default(); option.value(language); @@ -235,35 +267,35 @@ pub fn create_language_interaction<'this, 'a>(resp : &'this mut CreateInteractio }) } -pub fn create_dismiss_response<'this, 'a>(resp: &'this mut CreateInteractionResponse<'a>) -> &'this mut CreateInteractionResponse<'a> { - resp - .kind(InteractionResponseType::UpdateMessage) +pub fn create_dismiss_response<'this, 'a>( + resp: &'this mut CreateInteractionResponse<'a>, +) -> &'this mut CreateInteractionResponse<'a> { + resp.kind(InteractionResponseType::UpdateMessage) .interaction_response_data(|data| { - data - .set_embeds(Vec::new()) + data.set_embeds(Vec::new()) .embed(|emb| { emb.color(COLOR_WARN) .description("Interaction completed, you may safely dismiss this message.") }) - .components(|components| { - components.set_action_rows(Vec::new()) - }) + .components(|components| components.set_action_rows(Vec::new())) }) } -pub(crate) fn create_think_interaction(resp: &mut EditInteractionResponse) -> &mut EditInteractionResponse { - resp - .content("") - .components(|cmps| { - cmps.set_action_rows(Vec::new()) - }) - .embed(|emb| { - emb.color(COLOR_WARN) - .description("Processing request...") - }) +pub(crate) fn create_think_interaction( + resp: &mut EditInteractionResponse, +) -> &mut EditInteractionResponse { + resp.content("") + .components(|cmps| cmps.set_action_rows(Vec::new())) + .embed(|emb| emb.color(COLOR_WARN).description("Processing request...")) } -pub(crate) async fn handle_asm_or_compile_request(ctx: &Context, command: &ApplicationCommandInteraction, languages: &[&str], is_asm: bool, get_result: F) -> Result<(), CommandError> +pub(crate) async fn handle_asm_or_compile_request( + ctx: &Context, + command: &ApplicationCommandInteraction, + languages: &[&str], + is_asm: bool, + get_result: F, +) -> Result<(), CommandError> where F: FnOnce(ParserResult) -> Fut, Fut: Future>, @@ -271,34 +303,38 @@ where let mut parse_result = ParserResult::default(); let mut msg = None; - for (_, value) in &command.data.resolved.messages { - if !parser::find_code_block(& mut parse_result, &value.content, &command.user).await? { - command.create_interaction_response(&ctx.http, |resp| { - resp.kind(InteractionResponseType::DeferredChannelMessageWithSource) - .interaction_response_data(|data| { - data.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) - }) - }).await?; - return Err(CommandError::from("Unable to find a codeblock to compile!")) + if let Some((_, value)) = command.data.resolved.messages.iter().next() { + if !parser::find_code_block(&mut parse_result, &value.content, &command.user).await? { + command + .create_interaction_response(&ctx.http, |resp| { + resp.kind(InteractionResponseType::DeferredChannelMessageWithSource) + .interaction_response_data(|data| { + data.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + }) + }) + .await?; + return Err(CommandError::from("Unable to find a codeblock to compile!")); } msg = Some(value); - break; } // We never got a target from the codeblock, let's have them manually select a language let mut sent_interaction = false; if parse_result.target.is_empty() { - command.create_interaction_response(&ctx.http, |response| { - create_language_interaction(response, &languages) - }).await?; + command + .create_interaction_response(&ctx.http, |response| { + create_language_interaction(response, languages) + }) + .await?; let resp = command.get_interaction_response(&ctx.http).await?; - let selection = match resp.await_component_interaction(ctx) - .timeout(Duration::from_secs(30)).await { + let selection = match resp + .await_component_interaction(ctx) + .timeout(Duration::from_secs(30)) + .await + { Some(s) => s, - None => { - return Ok(()) - } + None => return Ok(()), }; sent_interaction = true; @@ -310,32 +346,34 @@ where let options = create_compiler_options(ctx, &language, is_asm).await?; if !sent_interaction { - command.create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|data| { - let compile_components = create_compile_panel(options); + command + .create_interaction_response(&ctx.http, |response| { + response + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|data| { + let compile_components = create_compile_panel(options); - data - .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) - .content("Select a compiler:") - .set_components(compile_components) - }) - }).await?; - } - else { - command.edit_original_interaction_response(&ctx.http, |response| { - response - .content("Select a compiler:") - .components(|c| { + data.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + .content("Select a compiler:") + .set_components(compile_components) + }) + }) + .await?; + } else { + command + .edit_original_interaction_response(&ctx.http, |response| { + response.content("Select a compiler:").components(|c| { *c = create_compile_panel(options); c }) - }).await?; + }) + .await?; } let resp = command.get_interaction_response(&ctx.http).await?; - let cib = resp.await_component_interactions(&ctx.shard).timeout(Duration::from_secs(30)); + let cib = resp + .await_component_interactions(&ctx.shard) + .timeout(Duration::from_secs(30)); let mut cic = cib.build(); // collect compiler into var @@ -351,19 +389,21 @@ where interaction.defer(&ctx.http).await?; } "2" => { - command.edit_original_interaction_response(&ctx.http, |resp| { - resp.components(|cmps| { - cmps.set_action_rows(Vec::new()) + command + .edit_original_interaction_response(&ctx.http, |resp| { + resp.components(|cmps| cmps.set_action_rows(Vec::new())) + .set_embeds(Vec::new()) + .embed(|emb| { + emb.color(COLOR_WARN).description( + "Awaiting completion of modal interaction, \ + if you have cancelled the menu you may safely dismiss the message", + ) + }) }) - .set_embeds(Vec::new()) - .embed(|emb| { - emb.color(COLOR_WARN) - .description("Awaiting completion of modal interaction, \ - if you have cancelled the menu you may safely dismiss the message") - }) - }).await?; + .await?; - more_options_response = create_more_options_panel(ctx, interaction.clone(), & mut parse_result).await?; + more_options_response = + create_more_options_panel(ctx, interaction.clone(), &mut parse_result).await?; if more_options_response.is_some() { cic.stop(); break; @@ -381,12 +421,13 @@ where // exit, they let this expire if last_interaction.is_none() && more_options_response.is_none() { - return Ok(()) + return Ok(()); } - command.edit_original_interaction_response(&ctx.http, |resp| { - create_think_interaction(resp) - }).await.unwrap(); + command + .edit_original_interaction_response(&ctx.http, create_think_interaction) + .await + .unwrap(); let data = ctx.data.read().await; let config = data.get::().unwrap(); @@ -405,7 +446,11 @@ where if let Some(log) = comp_log_id { if let Ok(id) = log.parse::() { - let guild = if command.guild_id.is_some() {command.guild_id.unwrap().0.to_string()} else {"<>".to_owned()}; + let guild = if command.guild_id.is_some() { + command.guild_id.unwrap().0.to_string() + } else { + "<>".to_owned() + }; let emb = embeds::build_complog_embed( is_success, &parse_result.code, @@ -418,52 +463,59 @@ where } } - command.edit_original_interaction_response(&ctx.http, |resp| { - edit_to_confirmation_interaction(&result, resp) - }).await.unwrap(); + command + .edit_original_interaction_response(&ctx.http, |resp| { + edit_to_confirmation_interaction(&result, resp) + }) + .await + .unwrap(); let int_resp = command.get_interaction_response(&ctx.http).await?; if let Some(int) = int_resp.await_component_interaction(&ctx.shard).await { - int.create_interaction_response(&ctx.http, |resp| { - create_dismiss_response(resp) - }).await?; + int.create_interaction_response(&ctx.http, create_dismiss_response) + .await?; // dispatch final response - msg.unwrap().channel_id.send_message(&ctx.http, |new_msg| { - new_msg - .allowed_mentions(|mentions| { - mentions.replied_user(false) - }) - .reference_message(msg.unwrap()) - .set_embed(result) - }).await?; + msg.unwrap() + .channel_id + .send_message(&ctx.http, |new_msg| { + new_msg + .allowed_mentions(|mentions| mentions.replied_user(false)) + .reference_message(msg.unwrap()) + .set_embed(result) + }) + .await?; } Ok(()) } -pub async fn send_error_msg(ctx : &Context, command : &ApplicationCommandInteraction, edit : bool, fail_embed : CreateEmbed) -> serenity::Result<()> { +pub async fn send_error_msg( + ctx: &Context, + command: &ApplicationCommandInteraction, + edit: bool, + fail_embed: CreateEmbed, +) -> serenity::Result<()> { if edit { - command.edit_original_interaction_response(&ctx.http, |rsp| { - rsp.content("") - .set_embeds(Vec::new()) - .components(|cmps| { - cmps.set_action_rows(Vec::new()) - }) - .set_embed(fail_embed) - }).await?; + command + .edit_original_interaction_response(&ctx.http, |rsp| { + rsp.content("") + .set_embeds(Vec::new()) + .components(|cmps| cmps.set_action_rows(Vec::new())) + .set_embed(fail_embed) + }) + .await?; Ok(()) + } else { + command + .create_interaction_response(&ctx.http, |resp| { + resp.kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|d| { + d.content("") + .set_embeds(Vec::new()) + .components(|cmps| cmps.set_action_rows(Vec::new())) + .set_embed(fail_embed) + }) + }) + .await } - else { - command.create_interaction_response(&ctx.http, |resp| { - resp.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|d| { - d.content("") - .set_embeds(Vec::new()) - .components(|cmps| { - cmps.set_action_rows(Vec::new()) - }) - .set_embed(fail_embed) - }) - }).await - } -} \ No newline at end of file +} diff --git a/src/utls/discordhelpers/menu.rs b/src/utls/discordhelpers/menu.rs index 6384edd..104409c 100644 --- a/src/utls/discordhelpers/menu.rs +++ b/src/utls/discordhelpers/menu.rs @@ -1,37 +1,42 @@ -use std::time::Duration; use futures_util::StreamExt; use serenity::builder::{CreateComponents, CreateEmbed}; use serenity::client::Context; use serenity::framework::standard::CommandError; use serenity::model::channel::Message; use serenity::model::interactions::message_component::ButtonStyle; +use std::time::Duration; pub struct Menu { - ctx : Context, - msg : Message, - pages : Vec, - page : usize, - components : CreateComponents + ctx: Context, + msg: Message, + pages: Vec, + page: usize, + components: CreateComponents, } impl Menu { - pub fn new(ctx: &Context, msg: &Message, items: & Vec) -> Menu { + pub fn new(ctx: &Context, msg: &Message, items: &[CreateEmbed]) -> Menu { Menu { ctx: ctx.clone(), msg: msg.clone(), - pages: items.clone(), + pages: Vec::from(items), page: 0, components: Menu::build_components(), } } pub async fn run(&mut self) -> Result<(), CommandError> { - let mut m = self.msg.channel_id.send_message(&self.ctx.http, |msg| { - msg.set_embed(self.pages[self.page].clone()) - .set_components(self.components.clone()) - }).await?; + let mut m = self + .msg + .channel_id + .send_message(&self.ctx.http, |msg| { + msg.set_embed(self.pages[self.page].clone()) + .set_components(self.components.clone()) + }) + .await?; - let cib = m.await_component_interactions(&self.ctx.shard) + let cib = m + .await_component_interactions(&self.ctx.shard) .timeout(Duration::from_secs(60)); let mut cic = cib.build(); while let Some(int) = cic.next().await { @@ -39,8 +44,7 @@ impl Menu { "left" => { if self.page > 0 { self.page -= 1; - } - else { + } else { self.page = self.pages.len() - 1; } } @@ -57,29 +61,24 @@ impl Menu { } m.edit(&self.ctx.http, |edit| { - edit.components(|cmps| { - cmps.set_action_rows(Vec::new()) - }) - }).await?; + edit.components(|cmps| cmps.set_action_rows(Vec::new())) + }) + .await?; Ok(()) } - async fn update_msg(&self, msg : &mut Message) -> serenity::Result<()> { + async fn update_msg(&self, msg: &mut Message) -> serenity::Result<()> { msg.edit(&self.ctx.http, |m| { m.set_embed(self.pages[self.page].clone()) - }).await + }) + .await } fn build_components() -> CreateComponents { let mut c = CreateComponents::default(); c.create_action_row(|row| { - row - .create_button(|btn| { - btn.style(ButtonStyle::Primary) - .label("⬅") - .custom_id("left") - }) + row.create_button(|btn| btn.style(ButtonStyle::Primary).label("⬅").custom_id("left")) .create_button(|btn| { btn.style(ButtonStyle::Primary) .label("➡") @@ -88,4 +87,4 @@ impl Menu { }); c } -} \ No newline at end of file +} diff --git a/src/utls/discordhelpers/mod.rs b/src/utls/discordhelpers/mod.rs index 5a8e485..77ce9f5 100644 --- a/src/utls/discordhelpers/mod.rs +++ b/src/utls/discordhelpers/mod.rs @@ -5,19 +5,15 @@ pub mod menu; use std::str; use std::sync::Arc; -use serenity::{ - builder::{CreateEmbed}, - http::Http, - model::prelude::*, -}; +use serenity::{builder::CreateEmbed, http::Http, model::prelude::*}; +use crate::cache::ConfigCache; use crate::utls::constants::*; use crate::utls::discordhelpers; -use tokio::sync::MutexGuard; use serenity::client::bridge::gateway::ShardManager; -use crate::cache::ConfigCache; use serenity::client::Context; use serenity::framework::standard::CommandResult; +use tokio::sync::MutexGuard; pub fn build_menu_items( items: Vec, @@ -25,7 +21,7 @@ pub fn build_menu_items( title: &str, avatar: &str, author: &str, - desc: &str + desc: &str, ) -> Vec { let mut pages = Vec::new(); let num_pages = items.len() / items_per_page; @@ -78,7 +74,13 @@ pub fn build_reaction(emoji_id: u64, emoji_name: &str) -> ReactionType { } } -pub async fn handle_edit(ctx : &Context, content : String, author : User, mut old : Message, original_message: Message) { +pub async fn handle_edit( + ctx: &Context, + content: String, + author: User, + mut old: Message, + original_message: Message, +) { let prefix = { let data = ctx.data.read().await; let info = data.get::().unwrap().read().await; @@ -89,64 +91,111 @@ pub async fn handle_edit(ctx : &Context, content : String, author : User, mut ol let _ = old.delete_reactions(&ctx).await; if content.starts_with(&format!("{}asm", prefix)) { - if let Err(e) = handle_edit_asm(&ctx, content, author.clone(), old.clone(), original_message.clone()).await { + if let Err(e) = handle_edit_asm( + ctx, + content, + author.clone(), + old.clone(), + original_message.clone(), + ) + .await + { let err = embeds::build_fail_embed(&author, &e.to_string()); - embeds::edit_message_embed(&ctx, & mut old, err).await; + embeds::edit_message_embed(ctx, &mut old, err).await; } - } - else if content.starts_with(&format!("{}compile", prefix)) { - if let Err(e) = handle_edit_compile(&ctx, content, author.clone(), old.clone(), original_message.clone()).await { + } else if content.starts_with(&format!("{}compile", prefix)) { + if let Err(e) = handle_edit_compile( + ctx, + content, + author.clone(), + old.clone(), + original_message.clone(), + ) + .await + { let err = embeds::build_fail_embed(&author, &e.to_string()); - embeds::edit_message_embed(&ctx, & mut old, err).await; + embeds::edit_message_embed(ctx, &mut old, err).await; } - } - else if content.starts_with(&format!("{}cpp", prefix)) { - if let Err(e) = handle_edit_cpp(&ctx, content, author.clone(), old.clone(), original_message.clone()).await { + } else if content.starts_with(&format!("{}cpp", prefix)) { + if let Err(e) = handle_edit_cpp( + ctx, + content, + author.clone(), + old.clone(), + original_message.clone(), + ) + .await + { let err = embeds::build_fail_embed(&author, &e.to_string()); - embeds::edit_message_embed(&ctx, & mut old, err).await; + embeds::edit_message_embed(ctx, &mut old, err).await; } - } - else { + } else { let err = embeds::build_fail_embed(&author, "Invalid command for edit functionality!"); - embeds::edit_message_embed(&ctx, & mut old, err).await; + embeds::edit_message_embed(ctx, &mut old, err).await; } } -pub async fn handle_edit_cpp(ctx : &Context, content : String, author : User, mut old : Message, original_msg: Message) -> CommandResult { - let embed = crate::commands::cpp::handle_request(ctx.clone(), content, author, &original_msg).await?; +pub async fn handle_edit_cpp( + ctx: &Context, + content: String, + author: User, + mut old: Message, + original_msg: Message, +) -> CommandResult { + let embed = + crate::commands::cpp::handle_request(ctx.clone(), content, author, &original_msg).await?; let compilation_successful = embed.0.get("color").unwrap() == COLOR_OKAY; discordhelpers::send_completion_react(ctx, &old, compilation_successful).await?; - embeds::edit_message_embed(&ctx, & mut old, embed).await; + embeds::edit_message_embed(ctx, &mut old, embed).await; Ok(()) } -pub async fn handle_edit_compile(ctx : &Context, content : String, author : User, mut old : Message, original_msg: Message) -> CommandResult { - let embed = crate::commands::compile::handle_request(ctx.clone(), content, author, &original_msg).await?; +pub async fn handle_edit_compile( + ctx: &Context, + content: String, + author: User, + mut old: Message, + original_msg: Message, +) -> CommandResult { + let embed = + crate::commands::compile::handle_request(ctx.clone(), content, author, &original_msg) + .await?; let compilation_successful = embed.0.get("color").unwrap() == COLOR_OKAY; discordhelpers::send_completion_react(ctx, &old, compilation_successful).await?; - embeds::edit_message_embed(&ctx, & mut old, embed).await; + embeds::edit_message_embed(ctx, &mut old, embed).await; Ok(()) } -pub async fn handle_edit_asm(ctx : &Context, content : String, author : User, mut old : Message, original_msg: Message) -> CommandResult { - let emb = crate::commands::asm::handle_request(ctx.clone(), content, author, &original_msg).await?; +pub async fn handle_edit_asm( + ctx: &Context, + content: String, + author: User, + mut old: Message, + original_msg: Message, +) -> CommandResult { + let emb = + crate::commands::asm::handle_request(ctx.clone(), content, author, &original_msg).await?; let success = emb.0.get("color").unwrap() == COLOR_OKAY; - embeds::edit_message_embed(&ctx, & mut old, emb).await; + embeds::edit_message_embed(ctx, &mut old, emb).await; send_completion_react(ctx, &old, success).await?; Ok(()) } -pub fn is_success_embed(embed : &CreateEmbed) -> bool { +pub fn is_success_embed(embed: &CreateEmbed) -> bool { embed.0.get("color").unwrap() == COLOR_OKAY } -pub async fn send_completion_react(ctx: &Context, msg: &Message, success: bool) -> Result { +pub async fn send_completion_react( + ctx: &Context, + msg: &Message, + success: bool, +) -> Result { let reaction; if success { { @@ -154,17 +203,22 @@ pub async fn send_completion_react(ctx: &Context, msg: &Message, success: bool) let botinfo_lock = data.get::().unwrap(); let botinfo = botinfo_lock.read().await; if let Some(success_id) = botinfo.get("SUCCESS_EMOJI_ID") { - let success_name = botinfo.get("SUCCESS_EMOJI_NAME").expect("Unable to find success emoji name").clone(); - reaction = discordhelpers::build_reaction(success_id.parse::().unwrap(), &success_name); - } - else { + let success_name = botinfo + .get("SUCCESS_EMOJI_NAME") + .expect("Unable to find success emoji name") + .clone(); + reaction = discordhelpers::build_reaction( + success_id.parse::().unwrap(), + &success_name, + ); + } else { reaction = ReactionType::Unicode(String::from("✅")); } } } else { reaction = ReactionType::Unicode(String::from("❌")); } - return msg.react(&ctx.http, reaction).await + return msg.react(&ctx.http, reaction).await; } // Certain compiler outputs use unicode control characters that @@ -173,18 +227,17 @@ pub async fn send_completion_react(ctx: &Context, msg: &Message, success: bool) // // Here we also limit the text to 1000 chars, this prevents discord from // rejecting our embeds for being to long if someone decides to spam. -pub fn conform_external_str(input: &str, max_len : usize) -> String { - let mut str: String; - if let Ok(vec) = strip_ansi_escapes::strip(input) { - str = String::from_utf8_lossy(&vec).to_string(); +pub fn conform_external_str(input: &str, max_len: usize) -> String { + let mut str = if let Ok(vec) = strip_ansi_escapes::strip(input) { + String::from_utf8_lossy(&vec).to_string() } else { - str = String::from(input); - } + String::from(input) + }; // while we're at it, we'll escape ` characters with a // zero-width space to prevent our embed from getting // messed up later - str = str.replace("`", "\u{200B}`"); + str = str.replace('`', "\u{200B}`"); // Conform our string. if str.len() > MAX_OUTPUT_LEN { @@ -218,16 +271,18 @@ pub async fn send_global_presence(shard_manager : &MutexGuard<'_, ShardManager>, format!("{:.1}k", sum/1000) } }; + // update shard guild count & presence let presence_str = format!("in {} servers | ;invite", server_count); let runners = shard_manager.runners.lock().await; for (_, v) in runners.iter() { - v.runner_tx.set_presence(Some(Activity::playing(&presence_str)), OnlineStatus::Online); + v.runner_tx + .set_presence(Some(Activity::playing(&presence_str)), OnlineStatus::Online); } } -pub async fn delete_bot_reacts(ctx : &Context, msg : &Message, react : ReactionType) -> CommandResult { +pub async fn delete_bot_reacts(ctx: &Context, msg: &Message, react: ReactionType) -> CommandResult { let bot_id = { let data_read = ctx.data.read().await; let botinfo_lock = data_read.get::().unwrap(); @@ -236,6 +291,8 @@ pub async fn delete_bot_reacts(ctx : &Context, msg : &Message, react : ReactionT UserId::from(id.parse::().unwrap()) }; - msg.channel_id.delete_reaction(&ctx.http, msg.id, Some(bot_id), react).await?; + msg.channel_id + .delete_reaction(&ctx.http, msg.id, Some(bot_id), react) + .await?; Ok(()) -} \ No newline at end of file +} diff --git a/src/utls/mod.rs b/src/utls/mod.rs index 4c851d0..15ff1ec 100644 --- a/src/utls/mod.rs +++ b/src/utls/mod.rs @@ -1,4 +1,4 @@ +pub mod blocklist; pub mod constants; pub mod discordhelpers; pub mod parser; -pub mod blocklist; diff --git a/src/utls/parser.rs b/src/utls/parser.rs index 24c1771..9db411d 100644 --- a/src/utls/parser.rs +++ b/src/utls/parser.rs @@ -1,18 +1,17 @@ use crate::utls::constants::URL_ALLOW_LIST; -use serenity::model::user::User; -use serenity::model::channel::{Message, Attachment}; use serenity::framework::standard::CommandError; +use serenity::model::channel::{Attachment, Message}; +use serenity::model::user::User; -use tokio::sync::RwLock; -use std::sync::Arc; use crate::managers::compilation::{CompilationManager, RequestHandler}; -use std::path::Path; use regex::Regex; - +use std::path::Path; +use std::sync::Arc; +use tokio::sync::RwLock; // Allows us to convert some common aliases to other programming languages -pub fn shortname_to_qualified(language : &str) -> &str { +pub fn shortname_to_qualified(language: &str) -> &str { match language { // Replace cpp with c++ since we removed the c pre-processor // support for wandbox. This is okay for godbolt requests, too. @@ -22,7 +21,7 @@ pub fn shortname_to_qualified(language : &str) -> &str { "csharp" => "c#", "cs" => "c#", "py" => "python", - _ => language + _ => language, } } @@ -37,20 +36,22 @@ pub struct ParserResult { } #[allow(clippy::while_let_on_iterator)] -pub async fn get_components(input: &str, author : &User, compilation_manager : Option<&Arc>>, reply : &Option>) -> Result { +pub async fn get_components( + input: &str, + author: &User, + compilation_manager: Option<&Arc>>, + reply: &Option>, +) -> Result { let mut result = ParserResult::default(); // Find the index for where we should stop parsing user input let mut end_point: usize = input.len(); - if let Some(parse_stop) = input.find("\n") { + if let Some(parse_stop) = input.find('\n') { end_point = parse_stop; } if let Some(index) = input.find('`') { - if end_point == 0 { - end_point = index; - } // if the ` character is found before \n we should use the ` as our parse stop point - else if index < end_point { + if end_point == 0 || index < end_point { end_point = index; } } @@ -65,13 +66,13 @@ pub async fn get_components(input: &str, author : &User, compilation_manager : O if let Some(param) = args.get(0) { let lower_param = param.trim().to_lowercase(); let language = shortname_to_qualified(&lower_param); - if !matches!(lang_lookup.resolve_target( language), RequestHandler::None) { + if !matches!(lang_lookup.resolve_target(language), RequestHandler::None) { args.remove(0); result.target = language.to_owned(); } } - } - else { // no compilation manager, just assume target is supplied + } else { + // no compilation manager, just assume target is supplied if let Some(param) = args.get(0) { let lower_param = param.trim().to_lowercase(); let language = shortname_to_qualified(&lower_param); @@ -83,7 +84,7 @@ pub async fn get_components(input: &str, author : &User, compilation_manager : O // looping every argument let mut iter = args.iter(); while let Some(c) = iter.next() { - if c.contains("\n") || c.contains("`") { + if c.contains('\n') || c.contains('`') { break; } @@ -115,25 +116,21 @@ pub async fn get_components(input: &str, author : &User, compilation_manager : O } let cmdline_args; - if let Some(codeblock_start) = input.find("`") { + if let Some(codeblock_start) = input.find('`') { if end_point < codeblock_start { cmdline_args = String::from(input[end_point..codeblock_start].trim()); - } - else { + } else { cmdline_args = String::default(); } - } - else { + } else { cmdline_args = String::from(input[end_point..].trim()); } result.args = shell_words::split(&cmdline_args)?; - if !result.url.is_empty() { let code = get_url_code(&result.url, author).await?; result.code = code; - } - else if find_code_block(&mut result, input, author).await? { + } else if find_code_block(&mut result, input, author).await? { // If we find a code block from our executor's message, and it's also a reply // let's assume we found the stdin and what they're replying to is the code. // Anything else probably doesn't make sense. @@ -147,8 +144,7 @@ pub async fn get_components(input: &str, author : &User, compilation_manager : O result.stdin = result.code; result.code = String::default(); result.code = attachment.0; - } - else { + } else { let mut fake_result = ParserResult::default(); if find_code_block(&mut fake_result, &replied_msg.content, author).await? { // we found a code block - lets assume the reply's codeblock is our actual code @@ -157,8 +153,7 @@ pub async fn get_components(input: &str, author : &User, compilation_manager : O } } } - } - else { + } else { // Unable to parse a code block from our executor's message, lets see if we have a // reply to grab some code from. if let Some(replied_msg) = reply { @@ -173,41 +168,44 @@ pub async fn get_components(input: &str, author : &User, compilation_manager : O else if !find_code_block(&mut result, &replied_msg.content, author).await? { return Err(CommandError::from( "You must attach a code-block containing code to your message or reply to a message that has one.", - )) + )); } - } - else { // We were really given nothing, lets fail now. + } else { + // We were really given nothing, lets fail now. return Err(CommandError::from( "You must attach a code-block containing code to your message or quote a message that has one.", - )) + )); } } if result.target.is_empty() { - return Err(CommandError::from("You must provide a valid language or compiler!\n\n;compile c++ \n\\`\\`\\`\nint main() {}\n\\`\\`\\`")) + return Err(CommandError::from("You must provide a valid language or compiler!\n\n;compile c++ \n\\`\\`\\`\nint main() {}\n\\`\\`\\`")); } //println!("Parse object: {:?}", result); Ok(result) } -async fn get_url_code(url : &str, author : &User) -> Result { +async fn get_url_code(url: &str, author: &User) -> Result { let url = match reqwest::Url::parse(url) { - Err(e) => { - return Err(CommandError::from(format!("Error parsing url: {}", e))) - }, - Ok(url) => url + Err(e) => return Err(CommandError::from(format!("Error parsing url: {}", e))), + Ok(url) => url, }; let host = url.host(); if host.is_none() { - return Err(CommandError::from("Unable to find host")) + return Err(CommandError::from("Unable to find host")); } let host_str = host.unwrap().to_string(); if !URL_ALLOW_LIST.contains(&host_str.as_str()) { - warn!("Blocked URL request to: {} by {} [{}]", host_str, author.id.0, author.tag()); - return Err(CommandError::from("Unknown paste service. Please use pastebin.com, hastebin.com, or GitHub gists.\n\nAlso please be sure to use a 'raw text' link")) + warn!( + "Blocked URL request to: {} by {} [{}]", + host_str, + author.id.0, + author.tag() + ); + return Err(CommandError::from("Unknown paste service. Please use pastebin.com, hastebin.com, or GitHub gists.\n\nAlso please be sure to use a 'raw text' link")); } let response = match reqwest::get(url).await { @@ -225,7 +223,11 @@ async fn get_url_code(url : &str, author : &User) -> Result Result { +pub async fn find_code_block( + result: &mut ParserResult, + haystack: &str, + author: &User, +) -> Result { let re = regex::Regex::new(r"```(?:(?P[^\s`]*)\r?\n)?(?P[\s\S]*?)```").unwrap(); let matches = re.captures_iter(haystack); @@ -249,19 +251,17 @@ pub async fn find_code_block(result: &mut ParserResult, haystack: &str, author : code_index = 0; } - _ => { - return Ok(false) - } + _ => return Ok(false), } let code_copy = result.code.clone(); let include_regex = Regex::new("\"[^\"]+\"|(?P#include\\s<(?P.+?)>)").unwrap(); let matches = include_regex.captures_iter(&code_copy).enumerate(); for (_, cap) in matches { - if let Some(statement) = cap.name("statement"){ + if let Some(statement) = cap.name("statement") { let include_stmt = statement.as_str(); let url = cap.name("url").unwrap().as_str(); - if let Ok(code) = get_url_code(url, &author).await { + if let Ok(code) = get_url_code(url, author).await { println!("Replacing {} with {}", include_stmt, &code); result.code = result.code.replace(include_stmt, &code); } @@ -278,15 +278,21 @@ pub async fn find_code_block(result: &mut ParserResult, haystack: &str, author : Ok(true) } -pub async fn get_message_attachment(attachments : & Vec) -> Result<(String, String), CommandError> { +pub async fn get_message_attachment( + attachments: &[Attachment], +) -> Result<(String, String), CommandError> { if !attachments.is_empty() { let attachment = attachments.get(0); if attachment.is_none() { return Ok((String::new(), String::new())); } let attached = attachment.unwrap(); - if attached.size > 512 * 1024 { // 512 KiB seems enough - return Err(CommandError::from(format!("Uploaded file too large: `{} MiB`", attached.size))); + if attached.size > 512 * 1024 { + // 512 KiB seems enough + return Err(CommandError::from(format!( + "Uploaded file too large: `{} MiB`", + attached.size + ))); } return match reqwest::get(&attached.url).await { Ok(r) => { @@ -304,15 +310,17 @@ pub async fn get_message_attachment(attachments : & Vec) -> Result<( } Ok((str, extension)) } - Err(e) => { - Err(CommandError::from(format!("UTF8 Error occured while parsing file: {}", e))) - } + Err(e) => Err(CommandError::from(format!( + "UTF8 Error occured while parsing file: {}", + e + ))), } } - Err(e) => { - Err(CommandError::from(format!("Failure when downloading attachment: {}", e))) - } - } + Err(e) => Err(CommandError::from(format!( + "Failure when downloading attachment: {}", + e + ))), + }; } - Ok((String::new(),String::new())) + Ok((String::new(), String::new())) }