From 77520f1e43bf27c3ab32b1dc29e16cb10e3450ef Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 28 Aug 2022 12:39:50 +0200 Subject: [PATCH 01/81] to conform `;` and `/` commands --- src/commands/format.rs | 10 +++------- src/managers/compilation.rs | 2 +- src/slashcmds/format.rs | 4 ++-- src/utls/discordhelpers/embeds.rs | 4 ++-- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/commands/format.rs b/src/commands/format.rs index 6a08f03..d6ffb24 100644 --- a/src/commands/format.rs +++ b/src/commands/format.rs @@ -11,7 +11,7 @@ use std::io::Write; #[command] pub async fn format(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let mut fmt = String::from("clangformat"); - let mut style = String::from("webkit"); + let mut style = String::from("google"); if !args.is_empty() { // do not include ``` codeblocks into arg parsing.. lets just substr and replace args let idx = msg.content.find('`'); @@ -129,18 +129,14 @@ pub async fn format(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu msg.channel_id .send_message(&ctx.http, |msg| { - msg.add_file(path.as_str()) - .content("Powered by godbolt.org") + msg.add_file(path.as_str()).content("Powered by MS Azure") }) .await?; let _ = std::fs::remove_file(&path); } else { msg.reply( &ctx.http, - format!( - "\n```{}\n{}```\n*Powered by godbolt.org*", - lang_code, answer - ), + format!("\n```{}\n{}```\n*Powered by MS Azure*", lang_code, answer), ) .await?; } diff --git a/src/managers/compilation.rs b/src/managers/compilation.rs index 176a64b..26424b9 100644 --- a/src/managers/compilation.rs +++ b/src/managers/compilation.rs @@ -148,7 +148,7 @@ impl CompilationManager { let resolution_result = gbolt.resolve(target); match resolution_result { None => { - Err(CommandError::from(format!("Target '{}' either does not produce assembly or is not currently supported on godbolt.org", target))) + Err(CommandError::from(format!("Target '{}' either does not produce assembly or is not currently supported on MS Azure", target))) } Some(compiler) => { let response = Godbolt::send_request(&compiler, &parse_result.code, options, USER_AGENT).await?; diff --git a/src/slashcmds/format.rs b/src/slashcmds/format.rs index c8d355a..6f1dd49 100644 --- a/src/slashcmds/format.rs +++ b/src/slashcmds/format.rs @@ -91,7 +91,7 @@ pub async fn format(ctx: &Context, command: &ApplicationCommandInteraction) -> C .timeout(Duration::from_secs(30)); cic = cib.build(); selected = false; - let mut style = String::from("WebKit"); + let mut style = String::from("Google"); while let Some(interaction) = &cic.next().await { match interaction.data.custom_id.as_str() { "style" => { @@ -172,7 +172,7 @@ fn create_styles_interaction<'a>( for style in styles { opts.create_option(|opt| { opt.label(style).value(style); - if style == "WebKit" { + if style == "Google" { opt.default_selection(true); } opt diff --git a/src/utls/discordhelpers/embeds.rs b/src/utls/discordhelpers/embeds.rs index 895a158..4e81e2c 100644 --- a/src/utls/discordhelpers/embeds.rs +++ b/src/utls/discordhelpers/embeds.rs @@ -73,7 +73,7 @@ impl ToEmbed for wandbox::CompilationResult { text = format!("{} | {}", text, options.compiler); } - text = format!("{} | wandbox.org", text); + text = format!("{} | CV NSight", text); f.text(text) }); embed @@ -200,7 +200,7 @@ impl ToEmbed for godbolt::GodboltResponse { appendstr = format!("{} | {}", appendstr, options.compiler); } - embed.footer(|f| f.text(format!("{} | godbolt.org", appendstr))); + embed.footer(|f| f.text(format!("{} | MS Azure", appendstr))); embed } } From 586eed10312c69ff454ca60a6f616f947af2374a Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 30 Aug 2022 11:35:34 +0200 Subject: [PATCH 02/81] better github workflow [clippy]+`v*` --- .github/workflows/main.yml | 25 ++++++++++++------------- .github/workflows/publish.yml | 2 +- .github/workflows/rust-clippy.yml | 13 +++++++------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 249c895..cecd3b9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,30 +16,30 @@ jobs: matrix: name: - ubuntu-latest-stable -# - ubuntu-latest-nightly + # - ubuntu-latest-nightly - windows-latest-stable -# - windows-latest-nightly + # - 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: 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 + # - name: windows-latest-nightly + # os: windows-latest + # rust: nightly + # target: x86_64-pc-windows-msvc + # rustflags: -D warnings steps: - uses: actions/checkout@v2 @@ -70,7 +70,6 @@ jobs: - uses: actions-rs/cargo@v1 with: command: test - # - uses: actions-rs/cargo@v1 # with: # command: clippy diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 3cf5e4a..135445b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -3,7 +3,7 @@ name: Publish on: push: tags: - - '*' + - "*" jobs: publish: diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml index 18eff51..051c1c7 100644 --- a/.github/workflows/rust-clippy.yml +++ b/.github/workflows/rust-clippy.yml @@ -4,19 +4,19 @@ # documentation. # rust-clippy is a tool that runs a bunch of lints to catch common # mistakes in your Rust code and help improve your Rust code. -# More details at https://github.com/rust-lang/rust-clippy +# More details at https://github.com/rust-lang/rust-clippy # and https://rust-lang.github.io/rust-clippy/ name: rust-clippy analyze on: push: - branches: [ master ] + branches: [master, v*] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [master] schedule: - - cron: '45 8 * * 4' + - cron: "45 8 * * 4" jobs: rust-clippy-analyze: @@ -41,10 +41,11 @@ jobs: run: cargo install clippy-sarif sarif-fmt - name: Run rust-clippy - run: - cargo clippy + run: cargo clippy + --all-targets --all-features --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt + -D clippy::all continue-on-error: true - name: Upload analysis results to GitHub From 98d00566952c1be1aa4bc77f2d5708c48768ab1c Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 30 Aug 2022 20:40:15 +0200 Subject: [PATCH 03/81] padding issue --- src/commands/help.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/help.rs b/src/commands/help.rs index d799c1a..3148b9a 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -140,10 +140,10 @@ pub async fn help(ctx: &Context, msg: &Message, args: Args) -> CommandResult { e.field("compile", "``` Compiles a script ```", false); e.field("compilers", "``` Displays the compilers for the specified language ```", false); e.field("languages", "``` Displays all supported languages ```", false); - e.field("asm", "```\nOutputs the assembly for the input code```", false); + e.field("asm", "``` Outputs the assembly for the input code ```", false); e.field("botinfo", "``` Displays information about the bot ```", false); e.field("cpp", format!("``` Executes c++ code using geordi-like syntax\n See {}help cpp for more info ```", prefix), false); - e.field("format", "``` Formats code using a code formatter (i.e. clang-format or rustfmt) ```", false); + e.field("format", "``` Formats code using a code formatter\n (i.e. clang-format or rustfmt) ```", false); e.field("formats", "``` Displays all formatting options & styles ```", false); e }) From d963ba2e1638a4cf1da5067bc6fe73e3e08e9b06 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 30 Aug 2022 20:43:35 +0200 Subject: [PATCH 04/81] fix unwrap --- src/utls/discordhelpers/embeds.rs | 5 +++-- src/utls/discordhelpers/interactions.rs | 17 +++++++++++++++-- src/utls/discordhelpers/mod.rs | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/utls/discordhelpers/embeds.rs b/src/utls/discordhelpers/embeds.rs index 4e81e2c..3eb4b01 100644 --- a/src/utls/discordhelpers/embeds.rs +++ b/src/utls/discordhelpers/embeds.rs @@ -1,5 +1,5 @@ use std::fmt::Write as _; -use std::str; +use std::{env, str}; use serenity::{ builder::{CreateEmbed, CreateMessage}, @@ -270,6 +270,7 @@ pub fn panic_embed(panic_info: String) -> CreateEmbed { pub fn build_welcome_embed() -> CreateEmbed { let mut embed = CreateEmbed::default(); + let prefix = env::var("BOT_PREFIX").expect("Bot prefix is not set!"); embed.title("Discord Compiler"); embed.color(COLOR_OKAY); embed.thumbnail(COMPILER_ICON); @@ -277,7 +278,7 @@ pub fn build_welcome_embed() -> CreateEmbed { 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```", + format!("{}compile python\n```py\nprint('hello world')\n```", prefix), true, ); embed.field("Learning Time!", "If you like reading the manuals of things, read our [getting started](https://github.com/ThomasByr/discord-compiler-bot/wiki) wiki or if you are confident type `;help` to view all commands.", false); diff --git a/src/utls/discordhelpers/interactions.rs b/src/utls/discordhelpers/interactions.rs index 22c00ec..8ef5c77 100644 --- a/src/utls/discordhelpers/interactions.rs +++ b/src/utls/discordhelpers/interactions.rs @@ -202,6 +202,14 @@ pub async fn create_compiler_options( } } + if list.is_none() { + warn!("No suitable compilers found for: {}", &language); + return Err(CommandError::from(format!( + "No suitable compilers found for: {}", + language + ))); + } + for compiler in list.unwrap() { let mut option = CreateSelectMenuOption::default(); option.label(compiler[0]); @@ -528,12 +536,17 @@ where } } - command + if let Err(err) = command .edit_original_interaction_response(&ctx.http, |resp| { edit_to_confirmation_interaction(&result, resp) }) .await - .unwrap(); + { + return Err(CommandError::from(format!( + "Unable to update response: {}", + err + ))); + } let int_resp = command.get_interaction_response(&ctx.http).await?; if let Some(int) = int_resp.await_component_interaction(&ctx.shard).await { diff --git a/src/utls/discordhelpers/mod.rs b/src/utls/discordhelpers/mod.rs index 181abb9..ba52789 100644 --- a/src/utls/discordhelpers/mod.rs +++ b/src/utls/discordhelpers/mod.rs @@ -296,7 +296,7 @@ pub async fn send_global_presence(shard_manager: &MutexGuard<'_, ShardManager>, }; // update shard guild count & presence - let presence_str = format!("in {} servers | ;invite", server_count); + let presence_str = format!("in {} servers | ;help", server_count); let runners = shard_manager.runners.lock().await; for (_, v) in runners.iter() { From 90040d89ccc8fb94d6ffb228c4c6ca3a936abd88 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 30 Aug 2022 20:43:49 +0200 Subject: [PATCH 05/81] assign avatar url at first shard ready --- src/events.rs | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/events.rs b/src/events.rs index a86db6f..9590939 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,3 +1,5 @@ +use std::env; + use serenity::{ async_trait, collector::CollectReaction, @@ -31,25 +33,13 @@ 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>); } #[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>) { let data = ctx.data.read().await; - let mut info = data.get::().unwrap().write().await; - info.insert("BOT_AVATAR", ready.user.avatar_url().unwrap()); let shard_manager = data.get::().unwrap().lock().await; let guild_count = stats.get_boot_vec_sum(); @@ -206,9 +196,11 @@ impl EventHandler for Handler { .await; let _ = new_message.delete_reactions(&ctx.http).await; if collector.is_some() { + let prefix = + env::var("BOT_PREFIX").expect("Expected bot prefix in .env file"); let emb = match handle_request( ctx.clone(), - format!(";compile\n```{}\n{}\n```", language, code), + format!("{}compile\n```{}\n{}\n```", prefix, language, code), new_message.author.clone(), &new_message, ) @@ -301,8 +293,14 @@ impl EventHandler for Handler { let guild_count = ready.guilds.len() as u64; stats.add_shard(guild_count); + // insert avatar at first opportunity + if stats.shard_count() == 1 { + let mut info = data.get::().unwrap().write().await; + info.insert("BOT_AVATAR", ready.user.avatar_url().unwrap()); + } + if stats.shard_count() == total_shards_to_spawn { - self.all_shards_ready(&ctx, &mut stats, &ready).await; + self.all_shards_ready(&ctx, &mut stats).await; } } } From 5731b8cb84a9033abdbccddd73d024370b2f0e9b Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 30 Aug 2022 20:44:09 +0200 Subject: [PATCH 06/81] to make the
section because i'm lazy --- scripts/details.py | 132 ++++++++++++++++++++++----------------------- 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/scripts/details.py b/scripts/details.py index 49f5866..78f359b 100644 --- a/scripts/details.py +++ b/scripts/details.py @@ -1,85 +1,83 @@ #!/usr/bin/env python3 +# -*- coding: utf-8 -*- """ Make the README file
section. - Usage: - `python3.9 ./scripts/details.py` + Usage: + `python3 ./scripts/details.py` """ -import sys +from typing import List -if (sys.version_info.major < 3) or (sys.version_info.minor < 9): - raise ValueError("Error [README make] : python version must be >=3.9") +def get_changes() -> List[str]: + """ + Gets the changes from the changelog.md file. -def get_changes() -> list[str]: - """ - Gets the changes from the changelog.md file. + Output format + ------------- + [version and message, first change, second change, ...] + """ + with open('changelog.md', 'r', encoding='utf-8') as f: + changelog = f.readlines() + changelog = [e.strip() for e in changelog] + chg: List[str] = [] + for e in reversed(changelog): + chg.append(e) + if len(e) > 0 and e[0] == '#': + break + return list(reversed(chg)) - Output format - ------------- - [version and message, first change, second change, ...] - """ - with open("changelog.md", "r", encoding="utf-8") as f: - changelog = f.readlines() - changelog = [e.strip() for e in changelog] - chg: list[str] = [] - for e in reversed(changelog): - chg.append(e) - if len(e) > 0 and e[0] == "#": - break - return list(reversed(chg)) +def update(chg: List[str]) -> None: + """ + Updates README.md with changelog -def update(chg: list[str]) -> None: - """ - Updates README.md with changelog - - Parameters - ---------- - ```py - chg : list[str] - changelog extract - ``` - """ - with open("README.md", "r", encoding="utf-8") as f: - readme = f.readlines() - index0: int = None - index1: int = None - seen = False - for i, e in enumerate(readme): - e.strip() - if seen: - if "
" in e: - index0 = i - elif "
" in e: - index1 = i - else: - if ("Changelog" in e or "changelog" in e) and "##" in e: - seen = True - if not seen: - raise ValueError("Error [README make] : no changelog subsection found") - if index0 is None or index1 is None: - raise ValueError("Error [README make] : bad format,
tag not found") + Parameters + ---------- + ```py + chg : list[str] + changelog extract + ``` + """ + with open('README.md', 'r', encoding='utf-8') as f: + readme = f.readlines() + index0: int = None + index1: int = None + seen = False + for i, e in enumerate(readme): + e.strip() + if seen: + if '
' in e: + index0 = i + elif '
' in e: + index1 = i + else: + if ('Changelog' in e or 'changelog' in e) and '##' in e: + seen = True + if not seen: + raise RuntimeError('Error [README make] : no changelog subsection found') + if index0 is None or index1 is None: + raise RuntimeError('Error [README make] : bad format,
tag not found') - head = chg[0] - index_of = head.find("#") - head = head[index_of::].strip("##") + head = chg[0] + index_of = head.find('#') + head = head[index_of::].strip('##') - details: list[str] = [] - details.append(f" {head} (click here to expand) ") - details.append("\n") - for e in chg[1:]: - details.append(f"{e}\n") - details.append("\n") + details: List[str] = [] + details.append(f' {head} (click here to expand) ') + details.append('\n') + for e in chg[1:]: + details.append(f'{e}\n') + details.append('\n') - readme = readme[:index0 + 1] + details + readme[index1:] - with open("README.md", "w", encoding="utf-8") as f: - f.writelines(readme) + readme = readme[:index0 + 1] + details + readme[index1:] + with open('README.md', 'w', encoding='utf-8') as f: + f.writelines(readme) -if __name__ == "__main__": - # it just works - update(get_changes()) - print("README.md updated") +if __name__ == '__main__': + # it just works + update(get_changes()) + print('README.md updated') From bf8bcb1b25175a72ed34e710ad943bede1a1bb59 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 30 Aug 2022 20:44:20 +0200 Subject: [PATCH 07/81] cargo stuff [arm] [x86_64] [version] --- Cargo.toml | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4ce321a..83846dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,39 +1,39 @@ [package] -name = "discord-compiler-bot" -description = "Discord bot to compile your spaghetti code." -version = "0.1.5" -authors = ["ThomasByr"] -edition = "2021" -build = "src/build.rs" +name = "discord-compiler-bot" +description = "๐Ÿค– discord bot to compile your spaghetti code" +version = "1.0.1" +authors = ["ThomasByr"] +edition = "2021" +build = "src/build.rs" [dev-dependencies] rusty-hook = "0.11" [dependencies] -tokio = { version = "1", features = ["full"] } -reqwest = { version = "0.11" } -dotenv = "0.15.0" -regex = "1" -log = "0.4" -pretty_env_logger = "0.3" +tokio = { version = "1", features = ["full"] } +reqwest = { version = "0.11" } +dotenv = "0.15.0" +regex = "1" +log = "0.4" +pretty_env_logger = "0.3" strip-ansi-escapes = "0.1.0" -serde = { version = "1.0.*", features = ["derive"] } -serde_json = "1.0" -lru-cache = "0.1" -async-trait = "0.1" -content_inspector = "0.2" -shell-words = "0.1" -const_format = "0.2" -lazy_static = "1.4.0" -similar = "2.1.0" +serde = { version = "1.0.*", features = ["derive"] } +serde_json = "1.0" +lru-cache = "0.1" +async-trait = "0.1" +content_inspector = "0.2" +shell-words = "0.1" +const_format = "0.2" +lazy_static = "1.4.0" +similar = "2.1.0" #tests -indoc = "1.0.3" +indoc = "1.0.3" test-context = "0.1" #dbl -dbl-rs = "0.3" +dbl-rs = "0.3" futures-util = "0.3.6" -warp = "0.3" -chrono = "0.4.19" +warp = "0.3" +chrono = "0.4.19" [target.'cfg(target_arch = "arm")'.dependencies] openssl = { version = "0.10", features = ["vendored"] } From 3c7431ae48930d94607ac7e5f42bae12b432b78d Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 30 Aug 2022 20:44:30 +0200 Subject: [PATCH 08/81] the full history, or so was I told... --- changelog.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/changelog.md b/changelog.md index 01fe5c6..dd13412 100644 --- a/changelog.md +++ b/changelog.md @@ -47,3 +47,10 @@ - if api don't return status, assume we failed - throw more compilation info in footer - cared about performance for once + +## First stable release version + +**v1.0** unwrapping + +- strongest cargo clippy analysis +- fixed some `panic!` on `.unwrap()` From de82db6d98ffb79daa0d01ce52a3460dd35a6743 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 30 Aug 2022 20:44:40 +0200 Subject: [PATCH 09/81] latest version of README file --- README.md | 85 +++++++++++++++++-------------------------------------- 1 file changed, 26 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 06183f8..b8a5c16 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# icon Discord compiler bot +# icon Discord Compiler Bot [![Linux](https://svgshare.com/i/Zhy.svg)](https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) [![Windows](https://svgshare.com/i/ZhY.svg)](https://svgshare.com/i/ZhY.svg) @@ -14,23 +14,29 @@ [![Author](https://img.shields.io/badge/author-@ThomasByr-blue)](https://github.com/ThomasByr) ````txt -;compile c++ -O3 -Wall -Wextra -Werror -Wpedantic +;compile rust argv1 argv2 argv3 ``` -stdin1 on the first line -stdin2 on the second line +stdin1 +stdin2 +stdin3 ``` -```cpp -#include -#include - -int main(int argc, char** argv) { - (void)argc; - std::string str; - std::getline(std::cin, str); - std::cout << str << "\n" - << argv[1] << std::endl; - return EXIT_SUCCESS; +```rs +fn main() -> Result<(), Box> { + let args: Vec = std::env::args().collect(); + + let query = &args.get(1).expect("command line argument missing"); + + let mut input = String::new(); + std::io::stdin().read_line(&mut input)?; + let input = input.trim(); + + println!( + "first command line argument: {}\nfirst input line: {}", + query, input + ); + + Ok(()) } ``` ```` @@ -46,7 +52,7 @@ int main(int argc, char** argv) { ## โœ๏ธ In short > **Note** -> This project was done in a week so do not expect crazy behavior and be immune to bugs. +> This project was initially done in a week so do not expect crazy behavior and be immune to bugs. This is a Discord compiler bot which can compile / interpret code blocks and display the result. Keep in mind that we're working in discord. This means, of course, that we have many operating restraints. Here's a few of the big ones. @@ -102,51 +108,12 @@ Please read the [changelog](changelog.md) file for the full history ! If you ever want to contribute to this project, either request the contributor status, or, more manually, fork the repo and make a full request ! On a more generic note, please do respect the [Rust Coding Conventions](https://rustc-dev-guide.rust-lang.org/conventions.html) and wait for your PR to be reviewed. Make sure you respect and read the [contributing](.github/CONTRIBUTING.md) guideline, make pull requests and be kind.
- Beta first minor release (click here to expand) - -**v0.1.0** first release - -- support for 30 more languages -- asm output -- embedded messages for discord -- `;invite`, `;botinfo` commands -- icons are now found online - -**v0.1.1** first patch - -- locked serenity dependency version to v0.11 -- client now has privileged access to context -- fix argument endpoint detection on replies - -**v0.1.2** code cleaning and slash commands - -- integration of some easy slash commands -- app commands to compile, format and make assembly -- cargo clippy to help keep the code cleaner -- deactivated local execution for some guilds -- `;block` and `;unblock` commands working (blacklist) -- added boilerplate code for some languages - -**v0.1.3** local rights and corrections - -- patched boilerplate code gen for java -- c detached from c++ boilerplate -- created alternate server count -- new `diff` slash command with colored output -- boilerplate code for php -- pinned serenity dependency to 0.11.1 to avoid headaches - -**v0.1.4** hotfix - -- compilation service unavailable + First stable release version (click here to expand) -**v0.1.5** online services +**v1.0** unwrapping -- assume some service won't work -- unwrap leading to panic -- if api don't return status, assume we failed -- throw more compilation info in footer -- cared about performance for once +- strongest cargo clippy analysis +- fixed some `panic!` on `.unwrap()`
From 0d0090689c72ba2e80673c38bc2626a59169401e Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Fri, 2 Sep 2022 20:38:39 +0200 Subject: [PATCH 10/81] added support for custom fail emoji --- .env.example | 2 ++ src/cache.rs | 2 ++ src/utls/discordhelpers/mod.rs | 24 +++++++++++++++++------- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index 10e931f..9740eb6 100644 --- a/.env.example +++ b/.env.example @@ -27,6 +27,8 @@ BOT_ID= ## Emojis SUCCESS_EMOJI_NAME= SUCCESS_EMOJI_ID= +FAIL_EMOJI_NAME= +FAIL_EMOJI_ID= LOADING_EMOJI_NAME= LOADING_EMOJI_ID= LOGO_EMOJI_NAME= diff --git a/src/cache.rs b/src/cache.rs index b2d4bef..ef3a5f4 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -122,6 +122,8 @@ pub async fn fill( let emoji_identifiers = [ "SUCCESS_EMOJI_ID", "SUCCESS_EMOJI_NAME", + "FAIL_EMOJI_ID", + "FAIL_EMOJI_NAME", "LOADING_EMOJI_ID", "LOADING_EMOJI_NAME", "LOGO_EMOJI_NAME", diff --git a/src/utls/discordhelpers/mod.rs b/src/utls/discordhelpers/mod.rs index ba52789..e0b40e2 100644 --- a/src/utls/discordhelpers/mod.rs +++ b/src/utls/discordhelpers/mod.rs @@ -222,11 +222,11 @@ pub async fn send_completion_react( success: bool, ) -> Result { let reaction; - if success { - { - let data = ctx.data.read().await; - let botinfo_lock = data.get::().unwrap(); - let botinfo = botinfo_lock.read().await; + let data = ctx.data.read().await; + let botinfo_lock = data.get::().unwrap(); + let botinfo = botinfo_lock.read().await; + match success { + true => { if let Some(success_id) = botinfo.get("SUCCESS_EMOJI_ID") { let success_name = botinfo .get("SUCCESS_EMOJI_NAME") @@ -240,8 +240,18 @@ pub async fn send_completion_react( reaction = ReactionType::Unicode(String::from("โœ…")); } } - } else { - reaction = ReactionType::Unicode(String::from("โŒ")); + false => { + if let Some(fail_id) = botinfo.get("FAIL_EMOJI_ID") { + let fail_name = botinfo + .get("FAIL_EMOJI_NAME") + .expect("Unable to find fail emoji name") + .clone(); + reaction = + discordhelpers::build_reaction(fail_id.parse::().unwrap(), &fail_name); + } else { + reaction = ReactionType::Unicode(String::from("โŒ")); + } + } } msg.react(&ctx.http, reaction).await } From fdb110b7fac1a405d406769833ce612aeae24273 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 3 Sep 2022 21:11:49 +0200 Subject: [PATCH 11/81] slightly better error dispatch --- src/events.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/events.rs b/src/events.rs index 9590939..c6064cc 100644 --- a/src/events.rs +++ b/src/events.rs @@ -418,6 +418,8 @@ pub async fn dispatch_error(ctx: &Context, msg: &Message, error: DispatchError, .send_message(&ctx.http, |_| &mut emb_msg) .await .is_err() - {} + { + panic!("Failed to send ratelimit message"); + } } } From cf16b37eda3786cfe552b67d5df451c9de660035 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 3 Sep 2022 21:12:36 +0200 Subject: [PATCH 12/81] include boilerplate in structure --- src/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/README.md b/src/README.md index e61e6ed..3ff47e2 100644 --- a/src/README.md +++ b/src/README.md @@ -25,6 +25,9 @@ src/ # Our source folder โ”œโ”€โ”€ apis/ # The home of any involved API integration โ”‚ โ””โ”€โ”€dbl.rs ## top.gg's webhook logic โ”‚ +โ”œโ”€โ”€ boilerplate/ # Module containing some boilerplate code geneeration +โ”‚ โ””โ”€โ”€... +โ”‚ โ””โ”€โ”€ utls/ # Module with random utilities to be used throughout the project โ”œโ”€โ”€ discordhelpers/ # Module with some discord shortcuts to help keep the project clean โ”‚ โ”œโ”€โ”€ mod.rs ## Menu handlers & other commonly used functions From 3176145e60564847cdc0e07d1e75bb1638b4e061 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 3 Sep 2022 21:12:52 +0200 Subject: [PATCH 13/81] cargo stuff [arm] [x86_64] [version] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 83846dc..a88bc9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "discord-compiler-bot" description = "๐Ÿค– discord bot to compile your spaghetti code" -version = "1.0.1" +version = "1.1.1" authors = ["ThomasByr"] edition = "2021" build = "src/build.rs" From 030cb623e969e113a2a389192400e9ed1d17b10f Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 3 Sep 2022 21:13:02 +0200 Subject: [PATCH 14/81] the full history, or so was I told... --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index dd13412..3f58363 100644 --- a/changelog.md +++ b/changelog.md @@ -54,3 +54,5 @@ - strongest cargo clippy analysis - fixed some `panic!` on `.unwrap()` +- support for custom fail emoji +- the bot now uses 75B less ram on average From 2dbf39379e731b099702de8c2686886334f84ed3 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 3 Sep 2022 21:13:10 +0200 Subject: [PATCH 15/81] latest version of README file --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b8a5c16..5e89bc6 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,8 @@ If you ever want to contribute to this project, either request the contributor s - strongest cargo clippy analysis - fixed some `panic!` on `.unwrap()` +- support for custom fail emoji +- the bot now uses 75B less ram on average
From fa87eb73a668dd56eb7aea06c0b8340c189ebc54 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 4 Sep 2022 12:23:25 +0200 Subject: [PATCH 16/81] contributing guidelines --- .github/CONTRIBUTING.md | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ae828b6..f246157 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -17,7 +17,7 @@ Any behavior that fails to meet these core values will result in your issue or p ## Pull Requests -Pull requests to fix issues or add new features are greatly appreciated, but having to outright reject contributions due to them being "not a good fit" is something we don't like to do. We ask that you coordinate changes with us to prevent any wasted time, as your time is valuable. The best place to get in contact with us is in our [support guild](discord.gg/ExraTaJ). +Pull requests to fix issues or add new features are greatly appreciated, but having to outright reject contributions due to them being "not a good fit" is something we don't like to do. We ask that you coordinate changes with us to prevent any wasted time, as your time is valuable. ## Rejection @@ -55,24 +55,35 @@ If you're looking for something - here's a breakdown of our codebase ``` src/ # Our source folder +โ”œโ”€โ”€ build.rs # Build script to embed git hash for ;botinfo commmand +| โ”œโ”€โ”€ main.rs # Code entry point, command registration, and client spawning โ”‚ โ”œโ”€โ”€ cache.rs # Sets up the cache to be used for the bot's resources โ”‚ โ”œโ”€โ”€ events.rs # All discord event handlers excluding command callbacks โ”‚ -โ”œโ”€โ”€ apis/ # The home of any involved API integration -โ”‚ โ””โ”€โ”€ dbl.rs ## Discord bot's list webhook logic -โ”‚ -โ”œโ”€โ”€ commands/ # Module containing all of our command logic +โ”œโ”€โ”€ commands/ # Module containing all of our command's logic โ”‚ โ””โ”€โ”€ ... โ”‚ -โ”œโ”€โ”€ stats/ # Module containing all statistics tracking logic -โ”‚ โ”œโ”€โ”€ stats.rs ## StatsManager abstraction for common code paths +โ”œโ”€โ”€ managers/ # Module containing all statistics tracking logic +โ”‚ โ”œโ”€โ”€ compilation.rs ## StatsManager abstraction for common code paths +โ”‚ โ””โ”€โ”€ stats.rs ## Manager used to handle all interactons with stats/tracking +โ”‚ +โ”œโ”€โ”€ stats/ # Module containing all statistics tracking structures โ”‚ โ””โ”€โ”€ structures.rs ## Stats request models & request dispatch โ”‚ -โ””โ”€โ”€ utls/ # Module with random utilities to be used throughout the project +โ”œโ”€โ”€ apis/ # The home of any involved API integration +โ”‚ โ””โ”€โ”€dbl.rs ## top.gg's webhook logic +โ”‚ +โ”œโ”€โ”€ boilerplate/ # Module containing some boilerplate code geneeration +โ”‚ โ””โ”€โ”€... +โ”‚ +โ””โ”€โ”€ utls/ # Module with random utilities to be used throughout the project + โ”œโ”€โ”€ discordhelpers/ # Module with some discord shortcuts to help keep the project clean + โ”‚ โ”œโ”€โ”€ mod.rs ## Menu handlers & other commonly used functions + โ”‚ โ””โ”€โ”€ embeds.rs ## Tools that builds our outputs & prepares them for display + โ”œโ”€โ”€ blocklist.rs ## Our blocklisting strategy to preven abuse โ”œโ”€โ”€ constants.rs ## Constants - โ”œโ”€โ”€ discordhelpers.rs ## Embed builders, menu builders, general tools to be used โ””โ”€โ”€ parser.rs ## Compile/Asm command parsing logic ``` From 51a2301365f393ede2c64db0557ea0fc2dc1a778 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 4 Sep 2022 12:23:58 +0200 Subject: [PATCH 17/81] support for custom fail emoji --- src/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cache.rs b/src/cache.rs index ef3a5f4..f6501a2 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -126,8 +126,8 @@ pub async fn fill( "FAIL_EMOJI_NAME", "LOADING_EMOJI_ID", "LOADING_EMOJI_NAME", - "LOGO_EMOJI_NAME", "LOGO_EMOJI_ID", + "LOGO_EMOJI_NAME", ]; for id in &emoji_identifiers { if let Ok(envvar) = env::var(id) { From 6783efb00b2248fdca81aad8d80657d2fa47f51f Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 4 Sep 2022 12:24:31 +0200 Subject: [PATCH 18/81] .env.example [links] [tocken] [panic] --- .env.example | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 9740eb6..87b4085 100644 --- a/.env.example +++ b/.env.example @@ -13,10 +13,10 @@ RUST_LOG=discord_compiler_bot BOT_PREFIX=; ## Bot info links -INVITE_LINK=https -DISCORDBOTS_LINK=https -GITHUB_LINK=https -STATS_LINK=https +INVITE_LINK=https://discordapp.com/oauth2/authorize?client_id=887037513682812960&permissions=379968&scope=applications.commands%20bot +DISCORDBOTS_LINK=https://discordbots.org/bot/887037513682812960 +GITHUB_LINK=https://github.com/ThomasByr/discord-compiler-bot +STATS_LINK=https://github.com/ThomasByr/discord-compiler-bot # Optional variables @@ -24,7 +24,7 @@ STATS_LINK=https ## the bot to continue BOT_ID= -## Emojis +## Emojis (type `\:emoji:` in discord to get the emoji code) SUCCESS_EMOJI_NAME= SUCCESS_EMOJI_ID= FAIL_EMOJI_NAME= @@ -34,7 +34,7 @@ LOADING_EMOJI_ID= LOGO_EMOJI_NAME= LOGO_EMOJI_ID= -## Usage logging +## Usage logging (unique channel ID) COMPILE_LOG= JOIN_LOG= PANIC_LOG= From a07ab7a68364dd2ac19edc10361550be3d8edd27 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 4 Sep 2022 12:24:53 +0200 Subject: [PATCH 19/81] the full history, or so was I told... --- changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelog.md b/changelog.md index 3f58363..ef2c61f 100644 --- a/changelog.md +++ b/changelog.md @@ -56,3 +56,7 @@ - fixed some `panic!` on `.unwrap()` - support for custom fail emoji - the bot now uses 75B less ram on average + +**v1.1** swaps + +- rewrote [CONTRIBUTING.md](.github/CONTRIBUTING.md) From 1b12729aa5ca27e4d33a140e95701553f3038ca1 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 4 Sep 2022 12:25:01 +0200 Subject: [PATCH 20/81] latest version of README file --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 5e89bc6..2822d6f 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,10 @@ If you ever want to contribute to this project, either request the contributor s - support for custom fail emoji - the bot now uses 75B less ram on average +**v1.1** swaps + +- rewrote [CONTRIBUTING.md](.github/CONTRIBUTING.md) +
## ๐Ÿ› Bugs and TODO From 50d29eec8c3c42cc9a85022b24770baef323748f Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Wed, 7 Sep 2022 11:23:32 +0200 Subject: [PATCH 21/81] update status --- src/utls/discordhelpers/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utls/discordhelpers/mod.rs b/src/utls/discordhelpers/mod.rs index e0b40e2..ff33c94 100644 --- a/src/utls/discordhelpers/mod.rs +++ b/src/utls/discordhelpers/mod.rs @@ -306,7 +306,7 @@ pub async fn send_global_presence(shard_manager: &MutexGuard<'_, ShardManager>, }; // update shard guild count & presence - let presence_str = format!("in {} servers | ;help", server_count); + let presence_str = format!("in {} guilds | ;help", server_count); let runners = shard_manager.runners.lock().await; for (_, v) in runners.iter() { From aa8dca2da16fbd71c1e154d2bed4368f223a76bc Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Wed, 7 Sep 2022 11:25:35 +0200 Subject: [PATCH 22/81] gitignore [beta] --- .gitignore | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index a6efee6..a09b191 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,22 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ +.idea/ + .env .env.base .env.beta *.exe +*.log blocklist.json + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock -# Added by cargo -/target -/.idea + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb From 064d843381310a9473ceccdd47cf68fcb6cd24f7 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Wed, 7 Sep 2022 11:26:57 +0200 Subject: [PATCH 23/81] background runner [log] --- run.bash | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 run.bash diff --git a/run.bash b/run.bash new file mode 100644 index 0000000..b34cbf2 --- /dev/null +++ b/run.bash @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# -*- coding: utf-8 -*- + +(./target/release/discord-compiler-bot > bot.log 2>&1) & From a11aa43a2885ed0f6af622b54900936c085bbd4e Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Wed, 7 Sep 2022 11:27:48 +0200 Subject: [PATCH 24/81] the full history, or so was I told... --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index ef2c61f..0807f71 100644 --- a/changelog.md +++ b/changelog.md @@ -60,3 +60,4 @@ **v1.1** swaps - rewrote [CONTRIBUTING.md](.github/CONTRIBUTING.md) +- rules for background runner > .log From 97363c865341ad7b0443d2c431a772b87b5803dc Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Wed, 7 Sep 2022 11:28:07 +0200 Subject: [PATCH 25/81] latest version of README file --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2822d6f..450de16 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ If you ever want to contribute to this project, either request the contributor s **v1.1** swaps - rewrote [CONTRIBUTING.md](.github/CONTRIBUTING.md) +- rules for background runner > .log
From cf238b7e20b5ade0a2d231d6855782833d5d7d34 Mon Sep 17 00:00:00 2001 From: Debian Date: Wed, 7 Sep 2022 10:00:05 +0000 Subject: [PATCH 26/81] background runner for linux [log] --- run.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 run.bash diff --git a/run.bash b/run.bash old mode 100644 new mode 100755 index b34cbf2..5c846a3 --- a/run.bash +++ b/run.bash @@ -1,4 +1,4 @@ #!/usr/bin/env bash # -*- coding: utf-8 -*- -(./target/release/discord-compiler-bot > bot.log 2>&1) & +(./target/release/discord-compiler-bot 2>&1 | tee bot.log) & From 62296c250a2bf49e4c71f3f811ef349da5cdc1e3 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 18 Sep 2022 09:50:18 +0200 Subject: [PATCH 27/81] updated presence [watch] --- src/utls/discordhelpers/mod.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/utls/discordhelpers/mod.rs b/src/utls/discordhelpers/mod.rs index ff33c94..bbcc7ef 100644 --- a/src/utls/discordhelpers/mod.rs +++ b/src/utls/discordhelpers/mod.rs @@ -306,12 +306,15 @@ pub async fn send_global_presence(shard_manager: &MutexGuard<'_, ShardManager>, }; // update shard guild count & presence - let presence_str = format!("in {} guilds | ;help", server_count); + let final_s = if sum > 1 { "s" } else { "" }; + let presence_str = format!("{} guild{} | ;help", server_count, final_s); 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::watching(&presence_str)), + OnlineStatus::Online, + ); } } From 3e83a24ea98c62bfd31c1cbc8b579ac1fedc033a Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 3 Oct 2022 18:44:49 +0200 Subject: [PATCH 28/81] background runner [log] --- run.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run.bash b/run.bash index 5c846a3..6843b75 100755 --- a/run.bash +++ b/run.bash @@ -1,4 +1,4 @@ #!/usr/bin/env bash # -*- coding: utf-8 -*- -(./target/release/discord-compiler-bot 2>&1 | tee bot.log) & +( (./target/release/discord-compiler-bot) 2>&1 | tee bot.log )& From c12bcc26a23c3299668a777567dd18e1af554ca0 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 3 Oct 2022 18:45:43 +0200 Subject: [PATCH 29/81] .env.example [link] [token] [panic] --- .env.example | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.env.example b/.env.example index 87b4085..9953a7e 100644 --- a/.env.example +++ b/.env.example @@ -4,7 +4,7 @@ BOT_TOKEN= ## Bot's application ID -APPLICATION_ID= +APPLICATION_ID=887037513682812960 ## Show only our logs, serenity is noisy RUST_LOG=discord_compiler_bot @@ -22,22 +22,22 @@ STATS_LINK=https://github.com/ThomasByr/discord-compiler-bot ## If the bot is unable to fetch it's own application info, having this filled in will allow ## the bot to continue -BOT_ID= - -## Emojis (type `\:emoji:` in discord to get the emoji code) -SUCCESS_EMOJI_NAME= -SUCCESS_EMOJI_ID= -FAIL_EMOJI_NAME= -FAIL_EMOJI_ID= -LOADING_EMOJI_NAME= -LOADING_EMOJI_ID= -LOGO_EMOJI_NAME= -LOGO_EMOJI_ID= - -## Usage logging (unique channel ID) -COMPILE_LOG= -JOIN_LOG= -PANIC_LOG= +BOT_ID=887037513682812960 + +## Emojis +SUCCESS_EMOJI_NAME=check_mark +SUCCESS_EMOJI_ID=1014226765863989391 +FAIL_EMOJI_NAME=across +FAIL_EMOJI_ID=1014227632461709383 +LOADING_EMOJI_NAME=loading +LOADING_EMOJI_ID=983470338417516615 +LOGO_EMOJI_NAME=copilot +LOGO_EMOJI_ID=1026534320489377853 + +## Usage logging +COMPILE_LOG=1014242294918021200 +JOIN_LOG=1015605164033445899 +PANIC_LOG=1014242354934329474 ## Top.gg bot voting announcement VOTE_CHANNEL= From 8ac423f994508cc87fe0cadda68f1fdb1b5ec0f7 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 4 Oct 2022 16:36:21 +0200 Subject: [PATCH 30/81] lower output length limit --- src/utls/constants.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utls/constants.rs b/src/utls/constants.rs index 562a5fe..abdeaba 100644 --- a/src/utls/constants.rs +++ b/src/utls/constants.rs @@ -13,8 +13,8 @@ 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_EXPLORER_ICON: &str = "https://i.imgur.com/GIgATFr.png"; pub const COMPILER_ICON: &str = "http://i.michaelwflaherty.com/u/XedLoQWCVc.png"; -pub const MAX_OUTPUT_LEN: usize = 1 << 9; -pub const MAX_ERROR_LEN: usize = 1 << 9; +pub const MAX_OUTPUT_LEN: usize = 1 << 8; +pub const MAX_ERROR_LEN: usize = 1 << 8; pub const USER_AGENT: &str = const_format::formatcp!("discord-compiler-bot/{}", env!("CARGO_PKG_VERSION")); pub const URL_ALLOW_LIST: [&str; 4] = [ From efb8806f16ab77c4ab25705f4384eae92b5bc6d5 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 4 Oct 2022 16:37:09 +0200 Subject: [PATCH 31/81] godbolt api : local dependency [compiler] [tokio] --- godbolt/Cargo.toml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/godbolt/Cargo.toml b/godbolt/Cargo.toml index a6176de..bbfa528 100644 --- a/godbolt/Cargo.toml +++ b/godbolt/Cargo.toml @@ -1,15 +1,15 @@ [package] -name = "godbolt" -version = "0.1.0" -authors = ["ThomasByr"] -edition = "2021" -license = "LGPL-3.0" -readme = "README.md" -keywords = ["Godbolt"] +name = "godbolt" +version = "0.1.0" +authors = ["ThomasByr"] +edition = "2021" +license = "LGPL-3.0" +readme = "README.md" +keywords = ["Godbolt"] categories = ["api-bindings", "web-programming::http-client"] [dependencies] serde_json = "1.0" -serde = { version = "1.0.*", features = ["derive"] } -reqwest = { version = "0.11", features = ["json"] } -tokio = { version = "1.0", features = ["macros"] } +serde = { version = "1.0.*", features = ["derive"] } +reqwest = { version = "0.11", features = ["json"] } +tokio = { version = "1", features = ["macros"] } From 39e69263ecdcec3a25a74827f018ea3ed3c89a49 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 4 Oct 2022 16:37:34 +0200 Subject: [PATCH 32/81] wandbox api : extern [tokio] --- wandbox/Cargo.toml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/wandbox/Cargo.toml b/wandbox/Cargo.toml index 53d2438..8d60ee5 100644 --- a/wandbox/Cargo.toml +++ b/wandbox/Cargo.toml @@ -1,24 +1,24 @@ [dependencies.reqwest] features = ["json"] -version = "0.11" +version = "0.11" [dependencies.serde] features = ["derive"] -version = "1.0.*" +version = "1.0.*" [dependencies.serde_json] version = "1.0" [dependencies.tokio] features = ["macros"] -version = "1.0" +version = "1" [package] -authors = ["ThomasByr"] -categories = ["api-bindings", "web-programming::http-client"] +authors = ["ThomasByr"] +categories = ["api-bindings", "web-programming::http-client"] description = "An api binding for Wandbox" -edition = "2021" -keywords = ["Wandbox"] -name = "wandbox" -readme = "README.md" -version = "0.1.3" +edition = "2021" +keywords = ["Wandbox"] +name = "wandbox" +readme = "README.md" +version = "0.1.3" From e1bbbc9c34616e42bc9694cea55f2f4d8d80880d Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 10 Oct 2022 14:03:08 +0200 Subject: [PATCH 33/81] boilerplate should `throw Exception` --- src/boilerplate/java.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/boilerplate/java.rs b/src/boilerplate/java.rs index d4d432c..9061e95 100644 --- a/src/boilerplate/java.rs +++ b/src/boilerplate/java.rs @@ -32,7 +32,7 @@ impl BoilerPlateGenerator for JavaGenerator { } format!( - "{}\nclass Main{{\npublic static void main(String[] args) {{\n{}}}}}", + "{}\nclass Main{{\npublic static void main(String[] args) throws Exception {{\n{}}}}}", header, main_body ) } From 2190f6d5884617047cfd645fca8cadc348e98b32 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 10 Oct 2022 14:05:23 +0200 Subject: [PATCH 34/81] TypeScript fix & shorthand `ts` --- src/managers/compilation.rs | 2 +- src/utls/parser.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/managers/compilation.rs b/src/managers/compilation.rs index 26424b9..c3b2c7b 100644 --- a/src/managers/compilation.rs +++ b/src/managers/compilation.rs @@ -222,7 +222,7 @@ impl CompilationManager { } pub fn resolve_target(&self, target: &str) -> RequestHandler { - if target == "scala" || target == "nim" { + if target == "scala" || target == "nim" || target == "typescript" { return RequestHandler::WandBox; } diff --git a/src/utls/parser.rs b/src/utls/parser.rs index fb9165f..0d0be74 100644 --- a/src/utls/parser.rs +++ b/src/utls/parser.rs @@ -18,6 +18,7 @@ pub fn shortname_to_qualified(language: &str) -> &str { "cpp" => "c++", "rs" => "rust", "js" => "javascript", + "ts" => "typescript", "csharp" => "c#", "cs" => "c#", "py" => "python", From 29cd6f494acd65b29ab52eab6b0701cfe425a66f Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 10 Oct 2022 14:05:45 +0200 Subject: [PATCH 35/81] `panic!` hook --- src/main.rs | 4 +++- src/utls/discordhelpers/mod.rs | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index d705efc..ead11da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -126,7 +126,9 @@ async fn main() -> Result<(), Box> { std::panic::set_hook(Box::new(move |info| { tokio::spawn({ let http = http.clone(); - let plog_parse = plog.parse::().unwrap(); + let plog_parse = plog.parse::().unwrap_or_else(|_| { + panic!("Unable to parse panic log channel id: {}", plog); + }); let panic_str = info.to_string(); async move { manual_dispatch(http, plog_parse, panic_embed(panic_str)).await } }); diff --git a/src/utls/discordhelpers/mod.rs b/src/utls/discordhelpers/mod.rs index bbcc7ef..8b27ea8 100644 --- a/src/utls/discordhelpers/mod.rs +++ b/src/utls/discordhelpers/mod.rs @@ -74,10 +74,9 @@ pub fn build_menu_items( pages } -// Pandas#3**2 on serenity disc, tyty pub fn build_reaction(emoji_id: u64, emoji_name: &str) -> ReactionType { ReactionType::Custom { - animated: false, + animated: false, // todo: Make this configurable id: EmojiId::from(emoji_id), name: Some(String::from(emoji_name)), } From 372fcbff41cfe6d4ad4f7e56b88ee82509c09809 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 10 Oct 2022 14:24:42 +0200 Subject: [PATCH 36/81] `?` --- src/cppeval/eval.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/cppeval/eval.rs b/src/cppeval/eval.rs index d61754b..b42c37e 100644 --- a/src/cppeval/eval.rs +++ b/src/cppeval/eval.rs @@ -49,17 +49,13 @@ impl CppEval { if self.input.starts_with('{') { // parsing a statement here - if let Err(e) = self.do_statements() { - return Err(e); - } + self.do_statements()?; } else if self.input.starts_with("<<") { // just outputting self.do_prints(); } else { // they're handling their own main - if let Err(e) = self.do_user_handled() { - return Err(e); - } + self.do_user_handled()?; } Ok(self.output.clone()) From 1271c0b8535001ad8e876c91d56c65fa838d41b7 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 11 Oct 2022 10:46:27 +0200 Subject: [PATCH 37/81] prevent invalid panic logs from obfuscating errors --- src/main.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index ead11da..d11498d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -124,14 +124,15 @@ async fn main() -> Result<(), Box> { let http = client.cache_and_http.http.clone(); std::panic::set_hook(Box::new(move |info| { - tokio::spawn({ - let http = http.clone(); - let plog_parse = plog.parse::().unwrap_or_else(|_| { - panic!("Unable to parse panic log channel id: {}", plog); - }); + let http = http.clone(); + if let Ok(plog_parse) = plog.parse::() { let panic_str = info.to_string(); - async move { manual_dispatch(http, plog_parse, panic_embed(panic_str)).await } - }); + tokio::spawn({ + async move { manual_dispatch(http, plog_parse, panic_embed(panic_str)).await } + }); + } else { + warn!("Unable to parse channel id64 from PANIC_LOG, is it valid?"); + } default_panic(info); })); } From 7b4bb8ca83682cd68fdc02a1b17bb4ebd2abe3e5 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 6 Nov 2022 18:34:13 +0100 Subject: [PATCH 38/81] checked for `stdio.h`, `stdlib.h` and `string.h` --- src/boilerplate/c.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/boilerplate/c.rs b/src/boilerplate/c.rs index 7943973..8d16709 100644 --- a/src/boilerplate/c.rs +++ b/src/boilerplate/c.rs @@ -31,9 +31,31 @@ impl BoilerPlateGenerator for CGenerator { } } - if main_body.contains("printf") && !header.contains("stdio.h") { + if (main_body.contains("printf") || main_body.contains("scanf")) + && !header.contains("stdio.h") + { header.push_str("#include ") } + if (main_body.contains("malloc") + || main_body.contains("free") + || main_body.contains("realloc") + || main_body.contains("calloc") + || main_body.contains("EXIT_FAILURE") + || main_body.contains("EXIT_SUCCESS")) + && !header.contains("stdlib.h") + { + header.push_str("#include ") + } + if (main_body.contains("strlen") || main_body.contains("strcmp")) + && !header.contains("string.h") + { + header.push_str("#include ") + } + if (main_body.contains("time") || main_body.contains("ctime")) && !header.contains("time.h") + { + header.push_str("#include ") + } + format!("{}\nint main(void) {{\n{}return 0;\n}}", header, main_body) } From d2c9b691b903a83fadf57c170f6b3c3c37ac68c9 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 6 Nov 2022 18:34:52 +0100 Subject: [PATCH 39/81] added `import java.utils.*` if no imports (might regret that later) --- src/boilerplate/java.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/boilerplate/java.rs b/src/boilerplate/java.rs index 9061e95..ae6257b 100644 --- a/src/boilerplate/java.rs +++ b/src/boilerplate/java.rs @@ -31,6 +31,11 @@ impl BoilerPlateGenerator for JavaGenerator { } } + // if they included nothing, we can just manually include everything + if !header.contains("import") { + header.push_str("import java.util.*;"); + } + format!( "{}\nclass Main{{\npublic static void main(String[] args) throws Exception {{\n{}}}}}", header, main_body From e9b7c0829b23559f702cdf0315864423f3b1be18 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 6 Nov 2022 18:35:07 +0100 Subject: [PATCH 40/81] the full history, or so was I told... --- changelog.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 0807f71..ae23913 100644 --- a/changelog.md +++ b/changelog.md @@ -55,9 +55,16 @@ - strongest cargo clippy analysis - fixed some `panic!` on `.unwrap()` - support for custom fail emoji -- the bot now uses 75B less ram on average +- the bot now uses 75B less ram on average, yay **v1.1** swaps - rewrote [CONTRIBUTING.md](.github/CONTRIBUTING.md) - rules for background runner > .log +- TypeScript fix & shorthand `ts` +- `panic!` hook : prevent invalid panic logs from obfuscating errors +- rolled back to a lower output length limit + +**v1.2** what is more useful when is broken ? + +- reworked C and Java boilerplates From e1b9ab20ffe5d5b1e2be18638dfde8bcf4eb8cb1 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 6 Nov 2022 18:35:25 +0100 Subject: [PATCH 41/81] =?UTF-8?q?latest=20version=20of=20README=20file=20?= =?UTF-8?q?=F0=9F=91=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 450de16..8944558 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# icon Discord Compiler Bot +# icon Discord Compiler Bot [![Linux](https://svgshare.com/i/Zhy.svg)](https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) [![Windows](https://svgshare.com/i/ZhY.svg)](https://svgshare.com/i/ZhY.svg) @@ -115,12 +115,19 @@ If you ever want to contribute to this project, either request the contributor s - strongest cargo clippy analysis - fixed some `panic!` on `.unwrap()` - support for custom fail emoji -- the bot now uses 75B less ram on average +- the bot now uses 75B less ram on average, yay **v1.1** swaps - rewrote [CONTRIBUTING.md](.github/CONTRIBUTING.md) - rules for background runner > .log +- TypeScript fix & shorthand `ts` +- `panic!` hook : prevent invalid panic logs from obfuscating errors +- rolled back to a lower output length limit + +**v1.2** what is more useful when is broken ? + +- reworked C and Java boilerplates
From b375b02f3c41a1d2b85f5a93b26726a4e993fe48 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 6 Nov 2022 19:34:50 +0100 Subject: [PATCH 42/81] forgot `"\n"` oops --- src/boilerplate/c.rs | 8 ++++---- src/boilerplate/cpp.rs | 2 +- src/boilerplate/csharp.rs | 2 +- src/boilerplate/java.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/boilerplate/c.rs b/src/boilerplate/c.rs index 8d16709..a36f9ce 100644 --- a/src/boilerplate/c.rs +++ b/src/boilerplate/c.rs @@ -34,7 +34,7 @@ impl BoilerPlateGenerator for CGenerator { if (main_body.contains("printf") || main_body.contains("scanf")) && !header.contains("stdio.h") { - header.push_str("#include ") + header.push_str("#include \n"); } if (main_body.contains("malloc") || main_body.contains("free") @@ -44,16 +44,16 @@ impl BoilerPlateGenerator for CGenerator { || main_body.contains("EXIT_SUCCESS")) && !header.contains("stdlib.h") { - header.push_str("#include ") + header.push_str("#include "); } if (main_body.contains("strlen") || main_body.contains("strcmp")) && !header.contains("string.h") { - header.push_str("#include ") + header.push_str("#include \n"); } if (main_body.contains("time") || main_body.contains("ctime")) && !header.contains("time.h") { - header.push_str("#include ") + header.push_str("#include \n"); } format!("{}\nint main(void) {{\n{}return 0;\n}}", header, main_body) diff --git a/src/boilerplate/cpp.rs b/src/boilerplate/cpp.rs index 022ca66..42103d2 100644 --- a/src/boilerplate/cpp.rs +++ b/src/boilerplate/cpp.rs @@ -33,7 +33,7 @@ impl BoilerPlateGenerator for CppGenerator { // if they included nothing, we can just manually include everything if !header.contains("#include") { - header.push_str("#include "); + header.push_str("#include \n"); } format!("{}\nint main(void) {{\n{}return 0;\n}}", header, main_body) } diff --git a/src/boilerplate/csharp.rs b/src/boilerplate/csharp.rs index 1b9a6df..7f86c07 100644 --- a/src/boilerplate/csharp.rs +++ b/src/boilerplate/csharp.rs @@ -33,7 +33,7 @@ impl BoilerPlateGenerator for CSharpGenerator { // if they included nothing, we can just manually include System since they probably want it if header.is_empty() { - header.push_str("using System;"); + header.push_str("using System;\n"); } format!( "{}\nnamespace Main{{\nclass Program {{\n static void Main(string[] args) {{\n{}}}}}}}", diff --git a/src/boilerplate/java.rs b/src/boilerplate/java.rs index ae6257b..48af89b 100644 --- a/src/boilerplate/java.rs +++ b/src/boilerplate/java.rs @@ -33,7 +33,7 @@ impl BoilerPlateGenerator for JavaGenerator { // if they included nothing, we can just manually include everything if !header.contains("import") { - header.push_str("import java.util.*;"); + header.push_str("import java.util.*;\n"); } format!( From 5f293f5ef4aef214a9539950663fff6d9a01599c Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 6 Nov 2022 19:35:40 +0100 Subject: [PATCH 43/81] =?UTF-8?q?cargo=20stuff=20[arm]=20[x86=5F64]=20[ver?= =?UTF-8?q?sion]=20=E2=9A=99=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a88bc9c..863a4a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "discord-compiler-bot" description = "๐Ÿค– discord bot to compile your spaghetti code" -version = "1.1.1" +version = "1.1.2" authors = ["ThomasByr"] edition = "2021" build = "src/build.rs" From 849fd4421d9ae52485375e2776e9c76c2c4a120d Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Fri, 25 Nov 2022 23:12:13 +0100 Subject: [PATCH 44/81] added `bash` and `sh` aliases for `bash script` --- src/utls/parser.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utls/parser.rs b/src/utls/parser.rs index 0d0be74..20a43bb 100644 --- a/src/utls/parser.rs +++ b/src/utls/parser.rs @@ -22,6 +22,7 @@ pub fn shortname_to_qualified(language: &str) -> &str { "csharp" => "c#", "cs" => "c#", "py" => "python", + "bash" | "sh" => "bash script", _ => language, } } From 5db5024b1b718d835bdcbffa0c34c009b2f27b9b Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Fri, 25 Nov 2022 23:12:29 +0100 Subject: [PATCH 45/81] check for existence of builds --- src/utls/discordhelpers/embeds.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/utls/discordhelpers/embeds.rs b/src/utls/discordhelpers/embeds.rs index 3eb4b01..99e36c7 100644 --- a/src/utls/discordhelpers/embeds.rs +++ b/src/utls/discordhelpers/embeds.rs @@ -154,10 +154,12 @@ impl ToEmbed for godbolt::GodboltResponse { } let mut errs = String::default(); - if let Some(errors) = self.build_result.unwrap().stderr { - for line in errors { - // errs.push_str(&format!("{}\n", line.text)); - writeln!(errs, "{}", line.text).unwrap(); + if let Some(build_result) = self.build_result { + if let Some(errors) = build_result.stderr { + for line in errors { + // errs.push_str(&format!("{}\n", line.text)); + writeln!(errs, "{}", line.text).unwrap(); + } } } for line in &self.stderr { From fa0862e1937b20453d1bbb02464d59a4053bebc7 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Fri, 25 Nov 2022 23:13:18 +0100 Subject: [PATCH 46/81] cargo stuff [arm] [x86_64] [version] --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 863a4a9..8a0e791 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "discord-compiler-bot" description = "๐Ÿค– discord bot to compile your spaghetti code" -version = "1.1.2" +version = "1.2.3" authors = ["ThomasByr"] edition = "2021" build = "src/build.rs" From f11bb5f6f550f12c5569179576f4ca1941ee88ad Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Fri, 25 Nov 2022 23:13:29 +0100 Subject: [PATCH 47/81] the full history, or so was I told... --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index ae23913..48a1c8a 100644 --- a/changelog.md +++ b/changelog.md @@ -68,3 +68,5 @@ **v1.2** what is more useful when is broken ? - reworked C and Java boilerplates +- check for existence of builds +- added `bash` and `sh` aliases for `bash script` From 57d2ca5f4807f717b63fa700ff78ae377f179bfa Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Fri, 25 Nov 2022 23:13:55 +0100 Subject: [PATCH 48/81] =?UTF-8?q?cargo=20stuff=20[arm]=20[x86=5F64]=20[ver?= =?UTF-8?q?sion]=20=E2=9A=99=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8a0e791..29fb367 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "discord-compiler-bot" description = "๐Ÿค– discord bot to compile your spaghetti code" -version = "1.2.3" +version = "1.2.4" authors = ["ThomasByr"] edition = "2021" build = "src/build.rs" From 27a7f1cb1b123d376a2ef8e7b2df1ccc8eb449c4 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Fri, 25 Nov 2022 23:14:59 +0100 Subject: [PATCH 49/81] =?UTF-8?q?latest=20version=20of=20README=20file=20?= =?UTF-8?q?=F0=9F=91=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8944558..4cdc9ea 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,8 @@ If you ever want to contribute to this project, either request the contributor s **v1.2** what is more useful when is broken ? - reworked C and Java boilerplates +- check for existence of builds +- added `bash` and `sh` aliases for `bash script` From 57a4374ce852771296c1d3aa3071c8d02dccb799 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 14 Jan 2023 15:49:36 +0100 Subject: [PATCH 50/81] more language shorthands --- src/utls/parser.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/utls/parser.rs b/src/utls/parser.rs index 20a43bb..01dd9b8 100644 --- a/src/utls/parser.rs +++ b/src/utls/parser.rs @@ -15,14 +15,21 @@ 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. - "cpp" => "c++", + "cpp" | "hpp" | "h++" => "c++", + "h" => "c", "rs" => "rust", "js" => "javascript", "ts" => "typescript", - "csharp" => "c#", - "cs" => "c#", + "csharp" | "cs" => "c#", "py" => "python", "bash" | "sh" => "bash script", + "rb" => "ruby", + "kt" => "kotlin", + "golang" => "go", + "fs" | "f#" => "fsharp", + "hs" | "lhs" => "haskell", + "jl" => "julia", + "gvy" => "groovy", _ => language, } } From ac65284743802f1a33a180e6f8950b77aa3dd15f Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 14 Jan 2023 15:52:51 +0100 Subject: [PATCH 51/81] =?UTF-8?q?cargo=20stuff=20[arm]=20[x86=5F64]=20[ver?= =?UTF-8?q?sion]=20=E2=9A=99=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 29fb367..5354a04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "discord-compiler-bot" description = "๐Ÿค– discord bot to compile your spaghetti code" -version = "1.2.4" +version = "1.3.1" authors = ["ThomasByr"] edition = "2021" build = "src/build.rs" From 944a67b19dee0ce04719f7b8f6811c5c7566ccb6 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 14 Jan 2023 15:53:14 +0100 Subject: [PATCH 52/81] the full history, or so was I told... --- changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.md b/changelog.md index 48a1c8a..ad20659 100644 --- a/changelog.md +++ b/changelog.md @@ -70,3 +70,8 @@ - reworked C and Java boilerplates - check for existence of builds - added `bash` and `sh` aliases for `bash script` + +**v1.3** more languages + +- added `c#` and `cs` aliases for `csharp` +- ... and many more From 791c3a256daaf910e60c7ab38625fd10aa43e520 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 14 Jan 2023 15:53:30 +0100 Subject: [PATCH 53/81] =?UTF-8?q?latest=20version=20of=20README=20file=20?= =?UTF-8?q?=F0=9F=91=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 4cdc9ea..5e9afc9 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,11 @@ If you ever want to contribute to this project, either request the contributor s - check for existence of builds - added `bash` and `sh` aliases for `bash script` +**v1.3** more languages + +- added `c#` and `cs` aliases for `csharp` +- ... and many more + ## ๐Ÿ› Bugs and TODO From 0a29e62de625f7fc361d9e200fbef584d02e9229 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 7 Feb 2023 22:00:18 +0100 Subject: [PATCH 54/81] prevent users from bypassing codeblock with embedded back-tick chars --- src/utls/discordhelpers/embeds.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utls/discordhelpers/embeds.rs b/src/utls/discordhelpers/embeds.rs index 99e36c7..8c2cb5c 100644 --- a/src/utls/discordhelpers/embeds.rs +++ b/src/utls/discordhelpers/embeds.rs @@ -127,7 +127,8 @@ impl ToEmbed for godbolt::GodboltResponse { let mut i = 1; for str in pieces { let title = format!("Assembly Output Pt. {}", i); - embed.field(&title, format!("```x86asm\n{}\n```", &str), false); + let piece = discordhelpers::conform_external_str(&str, MAX_OUTPUT_LEN); + embed.field(&title, format!("```x86asm\n{}\n```", &piece), false); output = true; i += 1; } @@ -138,7 +139,8 @@ impl ToEmbed for godbolt::GodboltResponse { String::from("Assembly Output") }; - embed.field(&title, format!("```x86asm\n{}\n```", &append), false); + let str = discordhelpers::conform_external_str(&append, MAX_OUTPUT_LEN); + embed.field(&title, format!("```x86asm\n{}\n```", &str), false); output = true; } From b5a66eeab01ac7456ca412dbc995d12de362cdd2 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Wed, 15 Feb 2023 11:38:20 +0100 Subject: [PATCH 55/81] =?UTF-8?q?AGPL-3.0=20License=20=E2=9A=96=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 156 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 98 insertions(+), 58 deletions(-) diff --git a/LICENSE b/LICENSE index 810fce6..be3f7b2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies @@ -7,17 +7,15 @@ Preamble - The GNU General Public License is a free, copyleft license for -software and other kinds of works. + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to +our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. +software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you @@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. The precise terms and conditions for copying, distribution and modification follow. @@ -72,7 +60,7 @@ modification follow. 0. Definitions. - "This License" refers to version 3 of the GNU General Public License. + "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. @@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. - 13. Use with the GNU Affero General Public License. + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single +under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General +Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published +GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's +versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. @@ -619,3 +617,45 @@ Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. From be08414f5a5d154c74bd3a0f9d5ee17525d71d07 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Wed, 15 Feb 2023 11:38:40 +0100 Subject: [PATCH 56/81] =?UTF-8?q?latest=20version=20of=20`README`=20file?= =?UTF-8?q?=20=F0=9F=91=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e9afc9..94e53ee 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ On a side note, support has been added for ARM architectures. Meaning you can no ## โš–๏ธ License -This project is licensed under the GPL-3.0 new or revised license. Please read the [LICENSE](LICENSE) file. +This project is licensed under the AGPL-3.0 new or revised license. Please read the [LICENSE](LICENSE) file. - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. From adf68113f891656e2a173d958ffa894f83a4f75d Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 19 Feb 2023 11:48:29 +0100 Subject: [PATCH 57/81] =?UTF-8?q?AGPL-3.0=20license=20=E2=9A=96=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index be3f7b2..0ad25db 100644 --- a/LICENSE +++ b/LICENSE @@ -633,8 +633,8 @@ the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, From 46b42bbc81bbdefb49758b4e3f1138c7b4b60b6b Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 27 Feb 2023 13:05:16 +0100 Subject: [PATCH 58/81] do not conform asm & prevent codeblock bypass --- src/utls/discordhelpers/embeds.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/utls/discordhelpers/embeds.rs b/src/utls/discordhelpers/embeds.rs index 8c2cb5c..1852723 100644 --- a/src/utls/discordhelpers/embeds.rs +++ b/src/utls/discordhelpers/embeds.rs @@ -127,7 +127,8 @@ impl ToEmbed for godbolt::GodboltResponse { let mut i = 1; for str in pieces { let title = format!("Assembly Output Pt. {}", i); - let piece = discordhelpers::conform_external_str(&str, MAX_OUTPUT_LEN); + // let piece = discordhelpers::conform_external_str(&str, MAX_OUTPUT_LEN); + let piece = str.replace('`', "\u{200B}`"); embed.field(&title, format!("```x86asm\n{}\n```", &piece), false); output = true; i += 1; @@ -139,8 +140,9 @@ impl ToEmbed for godbolt::GodboltResponse { String::from("Assembly Output") }; - let str = discordhelpers::conform_external_str(&append, MAX_OUTPUT_LEN); - embed.field(&title, format!("```x86asm\n{}\n```", &str), false); + // let piece = discordhelpers::conform_external_str(&append, MAX_OUTPUT_LEN); + let piece = append.replace('`', "\u{200B}`"); + embed.field(&title, format!("```x86asm\n{}\n```", &piece), false); output = true; } From b6ef3f0c1ebdbc28cc8b9a03b99345e69a1b608d Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 27 Feb 2023 13:45:41 +0100 Subject: [PATCH 59/81] godbolt api : local dependency [fushia] --- godbolt/src/lib.rs | 684 ++++++++++++++++++------------------- godbolt/src/tests/tests.rs | 185 +++++----- 2 files changed, 410 insertions(+), 459 deletions(-) diff --git a/godbolt/src/lib.rs b/godbolt/src/lib.rs index 8850ba0..c212c0f 100644 --- a/godbolt/src/lib.rs +++ b/godbolt/src/lib.rs @@ -7,457 +7,439 @@ mod tests; #[derive(Clone, Debug, Deserialize)] pub struct Compiler { - /// Unique compiler id - pub id: String, - /// Display name of compiler - pub name: String, - /// Unique associated language id - pub lang: String, - /// List of aliases to the compiler - pub alias: Vec, + /// Unique compiler id + pub id: String, + /// Display name of compiler + pub name: String, + /// Unique associated language id + pub lang: String, + /// List of aliases to the compiler + pub alias: Vec, } #[derive(Clone, Debug, Deserialize)] pub struct Language { - /// Unique language id - pub id: String, - /// Language display name - pub name: String, - /// Language file extensions supported by godbolt - pub extensions: Vec, - /// ??? - pub monaco: String, - /// Default compiler for the given language - #[serde(rename = "defaultCompiler")] - pub default_compiler: String, + /// Unique language id + pub id: String, + /// Language display name + pub name: String, + /// Language file extensions supported by godbolt + pub extensions: Vec, + /// ??? + pub monaco: String, + /// Default compiler for the given language + #[serde(rename = "defaultCompiler")] + pub default_compiler: String, } #[derive(Clone, Debug, Deserialize)] pub struct Library { - /// Unique identifier of library - pub id: String, - /// Library display name - pub name: String, - /// URL to library source - pub url: Option, - /// Library versions - pub versions: Vec, + /// Unique identifier of library + pub id: String, + /// Library display name + pub name: String, + /// URL to library source + pub url: Option, + /// Library versions + pub versions: Vec, } #[derive(Clone, Debug, Deserialize)] pub struct LibraryVersion { - /// Version of the library - pub version: String, - /// Unknown. - pub staticliblink: Vec, - /// Description of the library - pub description: Option, - /// List of the aliases to the library - pub alias: Vec, - /// List of the library's dependiences - pub dependencies: Vec, - /// Include paths compiler explorer uses - pub path: Vec, - /// Library binary paths - pub libpath: Vec, - /// Aditional library options - pub options: Vec, - /// Unique library ID - pub id: String, + /// Version of the library + pub version: String, + /// Unknown. + pub staticliblink: Vec, + /// Description of the library + pub description: Option, + /// List of the aliases to the library + pub alias: Vec, + /// List of the library's dependiences + pub dependencies: Vec, + /// Include paths compiler explorer uses + pub path: Vec, + /// Library binary paths + pub libpath: Vec, + /// Aditional library options + pub options: Vec, + /// Unique library ID + pub id: String, } #[derive(Clone, Debug, Deserialize, Default)] pub struct Format { - /// Path to executable - pub exe: String, - /// Long version string - pub version: String, - /// Name of formatter - pub name: String, - /// Possible format styles (if any) - pub styles: Vec, - /// Format type - #[serde(rename = "type")] - pub format_type: String, + /// Path to executable + pub exe: String, + /// Long version string + pub version: String, + /// Name of formatter + pub name: String, + /// Possible format styles (if any) + pub styles: Vec, + /// Format type + #[serde(rename = "type")] + pub format_type: String, } /// Internal Cache entry containing the language and it's relevant compilers pub struct GodboltCacheEntry { - /// Language - pub language: Language, - /// List of compilers for the language - pub compilers: Vec, + /// Language + pub language: Language, + /// List of compilers for the language + pub compilers: Vec, } #[derive(Clone, Debug, Deserialize, Default)] pub struct AsmResult { - pub text: Option, + pub text: Option, } #[derive(Clone, Debug, Deserialize, Default)] pub struct StdOutResult { - pub text: String, + pub text: String, } #[derive(Clone, Debug, Deserialize, Default)] pub struct StdErrResult { - pub text: String, - pub tag: Option, + pub text: String, + pub tag: Option, } #[derive(Clone, Debug, Deserialize, Default)] pub struct TagResult { - pub line: i32, - pub column: i32, - pub text: String, + pub line: i32, + pub column: i32, + pub text: String, } #[derive(Clone, Debug, Deserialize, Default)] pub struct BuildResult { - pub code: i32, - pub stdout: Option>, - pub stderr: Option>, - #[serde(rename = "inputFilename")] - pub input_filename: Option, - #[serde(rename = "compilationOptions")] - pub compilation_options: Option>, - pub tools: Option>, + pub code: i32, + pub stdout: Option>, + pub stderr: Option>, + #[serde(rename = "inputFilename")] + pub input_filename: Option, + #[serde(rename = "compilationOptions")] + pub compilation_options: Option>, + pub tools: Option>, } #[derive(Clone, Debug, Deserialize, Default)] pub struct GodboltResponse { - pub code: i32, - #[serde(rename = "didExecute")] - pub did_execute: Option, - #[serde(rename = "buildResult")] - pub build_result: Option, - #[serde(rename = "execTime")] - pub execution_time: Option, - pub stdout: Vec, - pub stderr: Vec, - #[serde(rename = "asmSize")] - pub asm_size: Option, - pub asm: Option>, + pub code: i32, + #[serde(rename = "didExecute")] + pub did_execute: Option, + #[serde(rename = "buildResult")] + pub build_result: Option, + #[serde(rename = "execTime")] + pub execution_time: Option, + pub stdout: Vec, + pub stderr: Vec, + #[serde(rename = "asmSize")] + pub asm_size: Option, + pub asm: Option>, } #[derive(Clone, Debug, Deserialize, Default)] pub struct FormatResult { - /// Exit code of the formatter - pub exit: i32, - /// Formatter Output - pub answer: String, + /// Exit code of the formatter + pub exit: i32, + /// Formatter Output + pub answer: String, } #[derive(Clone, Serialize, Debug, Default)] pub struct CompilerOptions { - #[serde(rename = "skipAsm")] - pub skip_asm: bool, - #[serde(rename = "executorRequest")] - pub executor_request: bool, + #[serde(rename = "skipAsm")] + pub skip_asm: bool, + #[serde(rename = "executorRequest")] + pub executor_request: bool, } #[derive(Clone, Serialize, Debug, Default)] pub struct ExecuteParameters { - pub args: Vec, - pub stdin: String, + pub args: Vec, + pub stdin: String, } #[derive(Clone, Serialize, Debug, Default)] pub struct RequestOptions { - /// Flags to pass to the compiler (i.e. -Wall -Werror) - #[serde(rename = "userArguments")] - pub user_arguments: String, - #[serde(rename = "compilerOptions")] - pub compiler_options: CompilerOptions, - #[serde(rename = "executeParameters")] - pub execute_parameters: ExecuteParameters, - /// Filters - pub filters: CompilationFilters, + /// Flags to pass to the compiler (i.e. -Wall -Werror) + #[serde(rename = "userArguments")] + pub user_arguments: String, + #[serde(rename = "compilerOptions")] + pub compiler_options: CompilerOptions, + #[serde(rename = "executeParameters")] + pub execute_parameters: ExecuteParameters, + /// Filters + pub filters: CompilationFilters, } /// Struct containing information needed to submit a compilation request #[derive(Clone, Debug, Serialize, Default)] pub struct CompilationRequest { - /// Source code to compile - source: String, - /// Compiler identifier - compiler: String, - /// List of compilation options - options: RequestOptions, + /// Source code to compile + source: String, + /// Compiler identifier + compiler: String, + /// List of compilation options + options: RequestOptions, } #[derive(Clone, Debug, Serialize, Default)] pub struct FormatterRequest { - source: String, - #[serde(skip_serializing_if = "Option::is_none")] - base: Option, - use_spaces: bool, - tab_width: i32, + source: String, + #[serde(skip_serializing_if = "Option::is_none")] + base: Option, + use_spaces: bool, + tab_width: i32, } #[derive(Clone, Debug, Serialize, Default)] pub struct CompilationFilters { - #[serde(skip_serializing_if = "Option::is_none")] - pub binary: Option, - #[serde(rename = "commentOnly", skip_serializing_if = "Option::is_none")] - pub comment_only: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub demangle: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub directives: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub execute: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub intel: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub labels: Option, - #[serde(rename = "libraryCode", skip_serializing_if = "Option::is_none")] - pub library_code: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub trim: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub binary: Option, + #[serde(rename = "commentOnly", skip_serializing_if = "Option::is_none")] + pub comment_only: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub demangle: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub directives: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub execute: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub intel: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub labels: Option, + #[serde(rename = "libraryCode", skip_serializing_if = "Option::is_none")] + pub library_code: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub trim: Option, } /// A struct with calls to all of Godbolt Compiler Explorer's endpoints pub struct Godbolt { - /// Internal cache of godbolt languages and their associated compilers - pub cache: Vec, - /// Cache of all formatting tools - pub formats: Vec, + /// Internal cache of godbolt languages and their associated compilers + pub cache: Vec, + /// Cache of all formatting tools + pub formats: Vec, } #[derive(Debug)] pub struct GodboltError { - details: String, + details: String, } impl GodboltError { - fn new(msg: &str) -> GodboltError { - GodboltError { - details: msg.to_string(), - } - } + fn new(msg: &str) -> GodboltError { + GodboltError { details: msg.to_string() } + } } impl fmt::Display for GodboltError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.details) - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.details) + } } impl std::error::Error for GodboltError {} impl Godbolt { - pub async fn new() -> Result> { - let formats = Godbolt::get_formats().await?; - - let mut instance = Godbolt { - cache: Vec::new(), - formats, - }; - - let langs = Godbolt::get_languages().await?; - let compilers = Godbolt::get_compilers().await?; - - for lang in langs { - let mut relevant = Vec::new(); - for compiler in &compilers { - if lang.id == compiler.lang { - relevant.push(compiler.clone()); - } - } - - let cache = GodboltCacheEntry { - language: lang, - compilers: relevant, - }; - instance.cache.push(cache); - } + pub async fn new() -> Result> { + let formats = Godbolt::get_formats().await?; - Ok(instance) - } - - /// Determines if the input compiler is a valid one - pub fn resolve(&self, target: &str) -> Option { - if let Some(comp) = self.find_compiler_by_id(target) { - Some(comp.clone()) - } else if let Some(lang) = self.find_language_by_id(target) { - Some(self.find_compiler_by_id(&lang.default_compiler)?.clone()) - } else { - None - } - } + let mut instance = Godbolt { cache: Vec::new(), formats }; - pub fn find_compiler_by_id(&self, compiler_id: &str) -> Option<&Compiler> { - for entry in &self.cache { - for compiler in &entry.compilers { - if compiler.id == compiler_id { - return Some(&compiler); - } - } - } - None - } + let langs = Godbolt::get_languages().await?; + let compilers = Godbolt::get_compilers().await?; - /// Determines if the input language is a valid one - pub fn find_language_by_id(&self, language_id: &str) -> Option<&Language> { - for entry in &self.cache { - if entry.language.id.to_lowercase() == language_id.to_lowercase() { - return Some(&entry.language); - } + for lang in langs { + let mut relevant = Vec::new(); + for compiler in &compilers { + if lang.id == compiler.lang { + relevant.push(compiler.clone()); } - None - } - - pub async fn send_request( - c: &Compiler, - source: &str, - options: RequestOptions, - user_agent: &str, - ) -> Result { - let req = CompilationRequest { - compiler: c.id.clone(), - source: String::from(source), - options: options, - }; - - let client = reqwest::Client::new(); - let endpoint = format!("https://godbolt.org/api/compiler/{}/compile", c.id); - - //println!("Sent: {}", serde_json::to_string(&req).unwrap()); - - let result = match client - .post(&endpoint) - .json(&req) - .header(USER_AGENT, user_agent) - .header(ACCEPT, "application/json") - .send() - .await - { - Ok(res) => res, - Err(e) => return Err(GodboltError::new(&format!("{}", e))), - }; - - let text = match result.text().await { - Ok(res) => res, - Err(e) => return Err(GodboltError::new(&format!("{}", e))), - }; - - //println!("Recieved: {}", text); - let res = match serde_json::from_str::(&text) { - Ok(res) => res, - Err(e) => return Err(GodboltError::new(&format!("{}", e))), - }; - - Ok(res) - } + } - /// Retrieves a vector of languages - pub async fn get_languages() -> Result, Box> { - static LANGUAGE_ENDPOINT: &str = - "https://godbolt.org/api/languages?fields=id,name,extensions,monaco,defaultCompiler"; - - let client = reqwest::Client::new(); - let res = client - .get(LANGUAGE_ENDPOINT) - .header(USER_AGENT, "godbolt-rust-crate") - .header(ACCEPT, "application/json") - .send() - .await?; - - let results: Vec = res.json::>().await?; - Ok(results) + let cache = GodboltCacheEntry { language: lang, compilers: relevant }; + instance.cache.push(cache); } - /// Retrieves a vector of compilers - pub async fn get_compilers() -> Result, Box> { - static LANGUAGE_ENDPOINT: &str = - "https://godbolt.org/api/compilers?fields=id,name,lang,alias"; - - let client = reqwest::Client::new(); - let res = client - .get(LANGUAGE_ENDPOINT) - .header(USER_AGENT, "godbolt-rust-crate") - .header(ACCEPT, "application/json") - .send() - .await?; - - let results: Vec = res.json::>().await?; - Ok(results) + Ok(instance) + } + + /// Determines if the input compiler is a valid one + pub fn resolve(&self, target: &str) -> Option { + if let Some(comp) = self.find_compiler_by_id(target) { + Some(comp.clone()) + } else if let Some(lang) = self.find_language_by_id(target) { + Some(self.find_compiler_by_id(&lang.default_compiler)?.clone()) + } else { + None } + } - /// Retrieves a vector of compilers for a given language identifier - pub async fn get_compilers_for(language_id: &str) -> Result, Box> { - let endpoint = format!( - "https://godbolt.org/api/compilers/{}?fields=id,name,lang,alias", - language_id - ); - - let client = reqwest::Client::new(); - let res = client - .get(&endpoint) - .header(USER_AGENT, "godbolt-rust-crate") - .header(ACCEPT, "application/json") - .send() - .await?; - - let results: Vec = res.json::>().await?; - Ok(results) - } - - /// Retrieves a vector of libraries for a given language identifier - pub async fn get_libraries_for(language_id: &str) -> Result, Box> { - let endpoint = format!("https://godbolt.org/api/libraries/{}", language_id); - - let client = reqwest::Client::new(); - let res = client - .get(&endpoint) - .header(USER_AGENT, "godbolt-rust-crate") - .header(ACCEPT, "application/json") - .send() - .await?; - - let results: Vec = res.json::>().await?; - Ok(results) + pub fn find_compiler_by_id(&self, compiler_id: &str) -> Option<&Compiler> { + for entry in &self.cache { + for compiler in &entry.compilers { + if compiler.id == compiler_id { + return Some(&compiler); + } + } } - - pub async fn get_formats() -> Result, Box> { - let client = reqwest::Client::new(); - let res = client - .get("https://godbolt.org/api/formats") - .header(USER_AGENT, "godbolt-rust-crate") - .header(ACCEPT, "application/json") - .send() - .await?; - - let results: Vec = res.json::>().await?; - Ok(results) + None + } + + /// Determines if the input language is a valid one + pub fn find_language_by_id(&self, language_id: &str) -> Option<&Language> { + for entry in &self.cache { + if entry.language.id.to_lowercase() == language_id.to_lowercase() { + return Some(&entry.language); + } } + None + } - pub async fn format_code( - fmt: &str, - style: &str, - source: &str, - use_spaces: bool, - tab_width: i32, - ) -> Result> { - let mut base = Option::None; - if !style.is_empty() { - base = Some(String::from(style)); - } - let formatter_request = FormatterRequest { - source: String::from(source), - base, - use_spaces, - tab_width, - }; - - let client = reqwest::Client::new(); - let res = client - .post(format!("https://godbolt.org/api/format/{}", fmt)) - .header(USER_AGENT, "godbolt-rust-crate") - .header(ACCEPT, "application/json") - .json(&formatter_request) - .send() - .await?; - - let result = res.json::().await?; - Ok(result) + pub async fn send_request( + c: &Compiler, + source: &str, + options: RequestOptions, + user_agent: &str, + ) -> Result { + let req = + CompilationRequest { compiler: c.id.clone(), source: String::from(source), options: options }; + + let client = reqwest::Client::new(); + let endpoint = format!("https://godbolt.org/api/compiler/{}/compile", c.id); + + //println!("Sent: {}", serde_json::to_string(&req).unwrap()); + + let result = match client + .post(&endpoint) + .json(&req) + .header(USER_AGENT, user_agent) + .header(ACCEPT, "application/json") + .send() + .await + { + Ok(res) => res, + Err(e) => return Err(GodboltError::new(&format!("{}", e))), + }; + + let text = match result.text().await { + Ok(res) => res, + Err(e) => return Err(GodboltError::new(&format!("{}", e))), + }; + + //println!("Recieved: {}", text); + let res = match serde_json::from_str::(&text) { + Ok(res) => res, + Err(e) => return Err(GodboltError::new(&format!("{}", e))), + }; + + Ok(res) + } + + /// Retrieves a vector of languages + pub async fn get_languages() -> Result, Box> { + static LANGUAGE_ENDPOINT: &str = + "https://godbolt.org/api/languages?fields=id,name,extensions,monaco,defaultCompiler"; + + let client = reqwest::Client::new(); + let res = client + .get(LANGUAGE_ENDPOINT) + .header(USER_AGENT, "godbolt-rust-crate") + .header(ACCEPT, "application/json") + .send() + .await?; + + let results: Vec = res.json::>().await?; + Ok(results) + } + + /// Retrieves a vector of compilers + pub async fn get_compilers() -> Result, Box> { + static LANGUAGE_ENDPOINT: &str = "https://godbolt.org/api/compilers?fields=id,name,lang,alias"; + + let client = reqwest::Client::new(); + let res = client + .get(LANGUAGE_ENDPOINT) + .header(USER_AGENT, "godbolt-rust-crate") + .header(ACCEPT, "application/json") + .send() + .await?; + + let results: Vec = res.json::>().await?; + Ok(results) + } + + /// Retrieves a vector of compilers for a given language identifier + pub async fn get_compilers_for(language_id: &str) -> Result, Box> { + let endpoint = + format!("https://godbolt.org/api/compilers/{}?fields=id,name,lang,alias", language_id); + + let client = reqwest::Client::new(); + let res = client + .get(&endpoint) + .header(USER_AGENT, "godbolt-rust-crate") + .header(ACCEPT, "application/json") + .send() + .await?; + + let results: Vec = res.json::>().await?; + Ok(results) + } + + /// Retrieves a vector of libraries for a given language identifier + pub async fn get_libraries_for(language_id: &str) -> Result, Box> { + let endpoint = format!("https://godbolt.org/api/libraries/{}", language_id); + + let client = reqwest::Client::new(); + let res = client + .get(&endpoint) + .header(USER_AGENT, "godbolt-rust-crate") + .header(ACCEPT, "application/json") + .send() + .await?; + + let results: Vec = res.json::>().await?; + Ok(results) + } + + pub async fn get_formats() -> Result, Box> { + let client = reqwest::Client::new(); + let res = client + .get("https://godbolt.org/api/formats") + .header(USER_AGENT, "godbolt-rust-crate") + .header(ACCEPT, "application/json") + .send() + .await?; + + let results: Vec = res.json::>().await?; + Ok(results) + } + + pub async fn format_code( + fmt: &str, + style: &str, + source: &str, + use_spaces: bool, + tab_width: i32, + ) -> Result> { + let mut base = Option::None; + if !style.is_empty() { + base = Some(String::from(style)); } + let formatter_request = + FormatterRequest { source: String::from(source), base, use_spaces, tab_width }; + + let client = reqwest::Client::new(); + let res = client + .post(format!("https://godbolt.org/api/format/{}", fmt)) + .header(USER_AGENT, "godbolt-rust-crate") + .header(ACCEPT, "application/json") + .json(&formatter_request) + .send() + .await?; + + let result = res.json::().await?; + Ok(result) + } } diff --git a/godbolt/src/tests/tests.rs b/godbolt/src/tests/tests.rs index 22de84c..8ff6b0e 100644 --- a/godbolt/src/tests/tests.rs +++ b/godbolt/src/tests/tests.rs @@ -3,151 +3,120 @@ use std::error::Error; #[tokio::test] async fn get_languages() -> Result<(), Box> { - let langs = Godbolt::get_languages().await?; - assert!(langs.len() > 0); - Ok(()) + let langs = Godbolt::get_languages().await?; + assert!(langs.len() > 0); + Ok(()) } #[tokio::test] async fn get_compilers() -> Result<(), Box> { - let compilers = Godbolt::get_compilers().await?; - assert!(compilers.len() > 0); - Ok(()) + let compilers = Godbolt::get_compilers().await?; + assert!(compilers.len() > 0); + Ok(()) } #[tokio::test] async fn get_compilers_for() -> Result<(), Box> { - let compilers = Godbolt::get_compilers_for("c++").await?; - assert!(compilers.len() > 0); - Ok(()) + let compilers = Godbolt::get_compilers_for("c++").await?; + assert!(compilers.len() > 0); + Ok(()) } #[tokio::test] async fn get_libraries_for() -> Result<(), Box> { - let libs = Godbolt::get_libraries_for("c").await?; - assert!(libs.len() > 0); - Ok(()) + let libs = Godbolt::get_libraries_for("c").await?; + assert!(libs.len() > 0); + Ok(()) } #[tokio::test] async fn godbolt_exec_asm() -> Result<(), Box> { - let gbolt = Godbolt::new().await?; - let cplusplus = gbolt - .cache - .iter() - .find(|p| p.language.name == "C++") - .unwrap(); - let compiler = &cplusplus.compilers[0]; - - let mut filters = CompilationFilters::default(); - filters.execute = Some(true); - - Godbolt::send_request( - compiler, - "int sum(int a, int b) { return a + b; }", - "-O3", - &filters, - ) + let gbolt = Godbolt::new().await?; + let cplusplus = gbolt.cache.iter().find(|p| p.language.name == "C++").unwrap(); + let compiler = &cplusplus.compilers[0]; + + let mut filters = CompilationFilters::default(); + filters.execute = Some(true); + + Godbolt::send_request(compiler, "int sum(int a, int b) { return a + b; }", "-O3", &filters) .await?; - Ok(()) + Ok(()) } #[tokio::test] async fn godbolt_exec_asm_fail() -> Result<(), Box> { - let gbolt = Godbolt::new().await?; - let cplusplus = gbolt - .cache - .iter() - .find(|p| p.language.name == "C++") - .unwrap(); - let compiler = &cplusplus.compilers[0]; - - let mut filters = CompilationFilters::default(); - filters.execute = Some(true); - - Godbolt::send_request( - compiler, - "int sum(iwnt a, int b) { return a + b; }", - "-O3", - &filters, - ) + let gbolt = Godbolt::new().await?; + let cplusplus = gbolt.cache.iter().find(|p| p.language.name == "C++").unwrap(); + let compiler = &cplusplus.compilers[0]; + + let mut filters = CompilationFilters::default(); + filters.execute = Some(true); + + Godbolt::send_request(compiler, "int sum(iwnt a, int b) { return a + b; }", "-O3", &filters) .await?; - Ok(()) + Ok(()) } #[tokio::test] async fn godbolt_exec_asm_filters() -> Result<(), Box> { - let gbolt = Godbolt::new().await?; - let cplusplus = gbolt - .cache - .iter() - .find(|p| p.language.name == "C++") - .unwrap(); - let compiler = &cplusplus.compilers[0]; - - let filters = CompilationFilters { - binary: None, - comment_only: Some(true), - demangle: Some(true), - directives: Some(true), - execute: None, - intel: Some(true), - labels: Some(true), - library_code: None, - trim: None, - }; - Godbolt::send_request( - compiler, - "int sum(int a, int b) { return a + b; }", - "-O3", - &filters, - ) + let gbolt = Godbolt::new().await?; + let cplusplus = gbolt.cache.iter().find(|p| p.language.name == "C++").unwrap(); + let compiler = &cplusplus.compilers[0]; + + let filters = CompilationFilters { + binary: None, + comment_only: Some(true), + demangle: Some(true), + directives: Some(true), + execute: None, + intel: Some(true), + labels: Some(true), + library_code: None, + trim: None, + }; + Godbolt::send_request(compiler, "int sum(int a, int b) { return a + b; }", "-O3", &filters) .await?; - Ok(()) + Ok(()) } #[tokio::test] async fn godbolt_exec_asm_filters_fail() -> Result<(), Box> { - let gbolt = Godbolt::new().await?; - let cplusplus = gbolt - .cache - .iter() - .find(|p| p.language.name == "C++") - .unwrap(); - let compiler = &cplusplus.compilers[0]; - - let filters = CompilationFilters { - binary: None, - comment_only: Some(true), - demangle: Some(true), - directives: Some(true), - execute: None, - intel: Some(true), - labels: Some(true), - library_code: None, - trim: None, - }; - Godbolt::send_request( - compiler, - "#include \nint main() {\nstd::cout << \"Test\";\n}", - "-O3", - &filters, - ) - .await?; - Ok(()) + let gbolt = Godbolt::new().await?; + let cplusplus = gbolt.cache.iter().find(|p| p.language.name == "C++").unwrap(); + let compiler = &cplusplus.compilers[0]; + + let filters = CompilationFilters { + binary: None, + comment_only: Some(true), + demangle: Some(true), + directives: Some(true), + execute: None, + intel: Some(true), + labels: Some(true), + library_code: None, + trim: None, + }; + Godbolt::send_request( + compiler, + "#include \nint main() {\nstd::cout << \"Test\";\n}", + "-O3", + &filters, + ) + .await?; + Ok(()) } #[tokio::test] async fn resolve() -> Result<(), Box> { - let gbolt = Godbolt::new().await?; - let c = gbolt.resolve("clang1000"); - assert!(c.is_some()); - Ok(()) + let gbolt = Godbolt::new().await?; + let c = gbolt.resolve("clang1000"); + assert!(c.is_some()); + Ok(()) } #[tokio::test] async fn format_test() -> Result<(), Box> { - let gbolt = Godbolt::new().await?; - assert!(gbolt.formats.len() > 0); - Ok(()) + let gbolt = Godbolt::new().await?; + assert!(gbolt.formats.len() > 0); + Ok(()) } From 53c05badab124a7303fb1c8736f2925aa309817c Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 27 Feb 2023 13:47:51 +0100 Subject: [PATCH 60/81] wandbox api : extern [fushia] --- wandbox/src/cache/mod.rs | 55 ++-- wandbox/src/lib.rs | 597 ++++++++++++++++++------------------- wandbox/src/tests/tests.rs | 80 ++--- 3 files changed, 354 insertions(+), 378 deletions(-) diff --git a/wandbox/src/cache/mod.rs b/wandbox/src/cache/mod.rs index f901483..ffc725d 100644 --- a/wandbox/src/cache/mod.rs +++ b/wandbox/src/cache/mod.rs @@ -7,35 +7,32 @@ use crate::{Compiler, Language}; pub type CompilerCache = HashMap; pub async fn load() -> Result> { - // grab wandbox compilers - let res = reqwest::get("https://wandbox.org/api/list.json").await?; - // retrieve compilers as vector - let result: Vec = res.json().await?; - - // we have to build our cache, iterating our vector and organizing - // compilers by their language. The language id should be lowercase. - let mut comp_cache: CompilerCache = HashMap::new(); - for c in result { - let language_name = c.language.to_ascii_lowercase(); - - // see if we can grab a mutable |Language| - let entry = comp_cache.get_mut(&language_name); - match entry { - Some(p) => { - p.compilers.push(c); - } - - // create one then.. - None => { - let mut lang = Language { - name: language_name.clone(), - compilers: Vec::new(), - }; - lang.compilers.push(c); - comp_cache.insert(language_name, lang); - } - } + // grab wandbox compilers + let res = reqwest::get("https://wandbox.org/api/list.json").await?; + // retrieve compilers as vector + let result: Vec = res.json().await?; + + // we have to build our cache, iterating our vector and organizing + // compilers by their language. The language id should be lowercase. + let mut comp_cache: CompilerCache = HashMap::new(); + for c in result { + let language_name = c.language.to_ascii_lowercase(); + + // see if we can grab a mutable |Language| + let entry = comp_cache.get_mut(&language_name); + match entry { + Some(p) => { + p.compilers.push(c); + } + + // create one then.. + None => { + let mut lang = Language { name: language_name.clone(), compilers: Vec::new() }; + lang.compilers.push(c); + comp_cache.insert(language_name, lang); + } } + } - Ok(comp_cache) + Ok(comp_cache) } diff --git a/wandbox/src/lib.rs b/wandbox/src/lib.rs index 32da516..2542ab5 100644 --- a/wandbox/src/lib.rs +++ b/wandbox/src/lib.rs @@ -13,171 +13,166 @@ use std::collections::HashSet; /// The main cache that holds on to the compiler cache pub struct Wandbox { - cache: Arc>, + cache: Arc>, } impl Wandbox { - /// Initializes the cache for Wandbox requests - /// - /// You may also choose to block certain compilers or languages from being supported. - /// This is useful if wandbox has any issues with certain compilers or languages. - /// - /// # Arguments - /// * `comps` - A vector of compiler identifiers that the library should ignore - /// * `langs` - A vector of language identifiers that the library should ignore - /// # Example - /// ```edition2018 - ///use std::collections::HashSet; - ///use wandbox::Wandbox; - /// - ///#[tokio::main] - ///async fn main() { - /// let mut set : HashSet = HashSet::new(); - /// set.insert(String::from("gcc-head")); - /// - /// let wbox : Wandbox = match Wandbox::new(Some(set), None).await { - /// Ok(wbox) => wbox, - /// Err(e) => return println!("{}", e) - /// }; - /// } - ///``` - pub async fn new( - comps: Option>, - langs: Option>, - ) -> Result> { - let mut cache: CompilerCache = cache::load().await?; - - if let Some(langs) = langs { - cache = cache - .into_iter() - .filter(|(_x, v)| !langs.contains(&v.name)) - .collect(); - } - - if let Some(comps) = comps { - for (_k, v) in cache.iter_mut() { - for str in &comps { - v.remove_compiler(str); - } - } - } + /// Initializes the cache for Wandbox requests + /// + /// You may also choose to block certain compilers or languages from being supported. + /// This is useful if wandbox has any issues with certain compilers or languages. + /// + /// # Arguments + /// * `comps` - A vector of compiler identifiers that the library should ignore + /// * `langs` - A vector of language identifiers that the library should ignore + /// # Example + /// ```edition2018 + ///use std::collections::HashSet; + ///use wandbox::Wandbox; + /// + ///#[tokio::main] + ///async fn main() { + /// let mut set : HashSet = HashSet::new(); + /// set.insert(String::from("gcc-head")); + /// + /// let wbox : Wandbox = match Wandbox::new(Some(set), None).await { + /// Ok(wbox) => wbox, + /// Err(e) => return println!("{}", e) + /// }; + /// } + ///``` + pub async fn new( + comps: Option>, + langs: Option>, + ) -> Result> { + let mut cache: CompilerCache = cache::load().await?; + + if let Some(langs) = langs { + cache = cache.into_iter().filter(|(_x, v)| !langs.contains(&v.name)).collect(); + } - // adjust language names to lower - for (_k, v) in cache.iter_mut() { - for mut c in v.compilers.iter_mut() { - c.language = c.language.to_ascii_lowercase(); - } + if let Some(comps) = comps { + for (_k, v) in cache.iter_mut() { + for str in &comps { + v.remove_compiler(str); } - - Ok(Wandbox { - cache: Arc::new(RwLock::new(cache)), - }) + } } - /// Gets a list of compilers given a certain language - /// - /// # Arguments - /// * `lang` - The language identifier to return the compilers for - pub fn get_compilers(&self, lang: &str) -> Option> { - let lock = self.cache.read().unwrap(); - let language_option = lock.get(lang); - let lang = match language_option { - Some(l) => l, - None => return None, - }; - - Some(lang.compilers.clone()) + // adjust language names to lower + for (_k, v) in cache.iter_mut() { + for mut c in v.compilers.iter_mut() { + c.language = c.language.to_ascii_lowercase(); + } } - /// Returns a list of every language - pub fn get_languages(&self) -> Vec { - let lock = self.cache.read().unwrap(); - - let mut vec: Vec = Vec::new(); - for (_k, v) in lock.iter() { - vec.push(v.clone()); - } - vec + Ok(Wandbox { cache: Arc::new(RwLock::new(cache)) }) + } + + /// Gets a list of compilers given a certain language + /// + /// # Arguments + /// * `lang` - The language identifier to return the compilers for + pub fn get_compilers(&self, lang: &str) -> Option> { + let lock = self.cache.read().unwrap(); + let language_option = lock.get(lang); + let lang = match language_option { + Some(l) => l, + None => return None, + }; + + Some(lang.compilers.clone()) + } + + /// Returns a list of every language + pub fn get_languages(&self) -> Vec { + let lock = self.cache.read().unwrap(); + + let mut vec: Vec = Vec::new(); + for (_k, v) in lock.iter() { + vec.push(v.clone()); } - - /// Determines if the compiler string supplied is a valid compiler - /// - /// # Arguments - /// * `c` - compiler identifier to check for - // n^2 :( - pub fn is_valid_compiler_str(&self, c: &str) -> bool { - // aquire our lock - let lock = self.cache.read().unwrap(); - for (_l, k) in lock.iter() { - for v in k.compilers.iter() { - if v.name == c { - return true; - } - } + vec + } + + /// Determines if the compiler string supplied is a valid compiler + /// + /// # Arguments + /// * `c` - compiler identifier to check for + // n^2 :( + pub fn is_valid_compiler_str(&self, c: &str) -> bool { + // aquire our lock + let lock = self.cache.read().unwrap(); + for (_l, k) in lock.iter() { + for v in k.compilers.iter() { + if v.name == c { + return true; } - - return false; + } } - pub fn get_compiler_language_str(&self, c: &str) -> Option { - // aquire our lock - let lock = self.cache.read().unwrap(); + return false; + } - for (_k, v) in lock.iter() { - for comp in &v.compilers { - if comp.name == c { - return Some(v.name.clone()); - } - } - } + pub fn get_compiler_language_str(&self, c: &str) -> Option { + // aquire our lock + let lock = self.cache.read().unwrap(); - return None; + for (_k, v) in lock.iter() { + for comp in &v.compilers { + if comp.name == c { + return Some(v.name.clone()); + } + } } - pub fn is_valid_language(&self, l: &str) -> bool { - let lock = self.cache.read().unwrap(); - return lock.get(l).is_some(); - } + return None; + } - pub fn get_default_compiler(&self, l: &str) -> Option { - let lock = self.cache.read().unwrap(); - if let Some(lang) = lock.get(l) { - Some(lang.compilers.get(0).expect("awd").name.clone()) - } else { - None - } + pub fn is_valid_language(&self, l: &str) -> bool { + let lock = self.cache.read().unwrap(); + return lock.get(l).is_some(); + } + + pub fn get_default_compiler(&self, l: &str) -> Option { + let lock = self.cache.read().unwrap(); + if let Some(lang) = lock.get(l) { + Some(lang.compilers.get(0).expect("awd").name.clone()) + } else { + None } + } } /// Representation of a compiler #[derive(Hash, Eq, PartialEq, Serialize, Deserialize)] pub struct Compiler { - #[serde(rename = "compiler-option-raw")] - pub compiler_option_raw: bool, - #[serde(rename = "display-compile-command")] - pub display_compile_command: String, - #[serde(rename = "runtime-option-raw")] - pub runtime_option_raw: bool, - - pub version: String, - pub language: String, - pub name: String, + #[serde(rename = "compiler-option-raw")] + pub compiler_option_raw: bool, + #[serde(rename = "display-compile-command")] + pub display_compile_command: String, + #[serde(rename = "runtime-option-raw")] + pub runtime_option_raw: bool, + + pub version: String, + pub language: String, + pub name: String, } impl Clone for Compiler { - fn clone(&self) -> Self { - Compiler { - compiler_option_raw: self.compiler_option_raw, - display_compile_command: self.display_compile_command.clone(), - runtime_option_raw: self.runtime_option_raw, - version: self.version.clone(), - language: self.language.clone(), - name: self.name.clone(), - } + fn clone(&self) -> Self { + Compiler { + compiler_option_raw: self.compiler_option_raw, + display_compile_command: self.display_compile_command.clone(), + runtime_option_raw: self.runtime_option_raw, + version: self.version.clone(), + language: self.language.clone(), + name: self.name.clone(), } + } } impl fmt::Debug for Compiler { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "[{} {}] : {}", self.name, self.version, self.language) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[{} {}] : {}", self.name, self.version, self.language) + } } /// A builder to allow you to easily build requests @@ -205,213 +200,197 @@ impl fmt::Debug for Compiler { /// ``` #[derive(Default, Serialize)] pub struct CompilationBuilder { - #[serde(skip)] - target: String, - pub lang: String, - compiler: String, - code: String, - stdin: String, - #[serde(skip)] - options: Vec, - #[serde(rename = "compiler-option-raw")] - compiler_options_raw: String, - save: bool, + #[serde(skip)] + target: String, + pub lang: String, + compiler: String, + code: String, + stdin: String, + #[serde(skip)] + options: Vec, + #[serde(rename = "compiler-option-raw")] + compiler_options_raw: String, + save: bool, } impl CompilationBuilder { - /// Creates a new CompilationBuilder with default values to be filled in later - pub fn new() -> CompilationBuilder { - return CompilationBuilder { - ..Default::default() - }; - } - - /// Sets the target of the compilation - /// - /// # Arguments - /// * `target` - The target of a compilation, this can be a language ('c++'), or a compiler ('gcc-head') - pub fn target(&mut self, target: &str) -> () { - self.target = target.trim().to_string(); - } - - /// Sets the code to be compiled - /// - /// # Arguments - /// * `code` - String of code to be compiled - pub fn code(&mut self, code: &str) -> () { - self.code = code.trim().to_string(); - } - - /// Sets the stdin to directed towards the application - /// - /// # Arguments - /// * `stdin` - program input - pub fn stdin(&mut self, stdin: &str) -> () { - self.stdin = stdin.trim().to_string(); - } - - /// Determines whether or not Wandbox saves the compilation & replies with a link for you - /// - /// # Arguments - /// * `save` - true if Wandbox should save this compilation - pub fn save(&mut self, save: bool) -> () { - self.save = save; - } - - /// Sets the list of compilation options. Useful for languages like c++ to pass linker/optimization - /// flags. - /// - /// # Arguments - /// * `options` - A list of compiler options i.e ["-Wall", "-Werror"] - pub fn options(&mut self, options: Vec) -> () { - self.options = options; - } - - /// Sets the list of compilation options. Useful for languages like c++ to pass linker/optimization - /// flags. - /// - /// This version allows you to pass a `Vec<&str>` - /// - /// # Arguments - /// * `options` - A list of compiler options i.e ["-Wall", "-Werror"] - pub fn options_str(&mut self, options: Vec<&str>) -> () { - self.options = options.into_iter().map(|f| f.to_owned()).collect(); - } - - /// Finalizes the builder & prepares itself for compilation dispatch. - /// - /// # Arguments - /// * `wb` - An instance of the Wandbox cache to resolve the compilation target - pub fn build(&mut self, wb: &Wandbox) -> Result<(), WandboxError> { - self.compiler_options_raw = self.options.join("\n"); - - if wb.is_valid_language(&self.target) { - let comp = match wb.get_default_compiler(&self.target) { - Some(def) => def, - None => { - return Err(WandboxError::new( - "Unable to determine default compiler for input language", - )) - } - }; - self.compiler = comp; - self.lang = self.target.clone(); - } else if wb.is_valid_compiler_str(&self.target) { - let lang = match wb.get_compiler_language_str(&self.target) { - Some(lang) => lang, - None => { - return Err(WandboxError::new( - "Unable to determine language for compiler}", - )) - } - }; - - self.lang = lang; - self.compiler = self.target.clone(); - } else { - return Err(WandboxError::new( - "Unable to find compiler or language for target", - )); + /// Creates a new CompilationBuilder with default values to be filled in later + pub fn new() -> CompilationBuilder { + return CompilationBuilder { ..Default::default() }; + } + + /// Sets the target of the compilation + /// + /// # Arguments + /// * `target` - The target of a compilation, this can be a language ('c++'), or a compiler ('gcc-head') + pub fn target(&mut self, target: &str) -> () { + self.target = target.trim().to_string(); + } + + /// Sets the code to be compiled + /// + /// # Arguments + /// * `code` - String of code to be compiled + pub fn code(&mut self, code: &str) -> () { + self.code = code.trim().to_string(); + } + + /// Sets the stdin to directed towards the application + /// + /// # Arguments + /// * `stdin` - program input + pub fn stdin(&mut self, stdin: &str) -> () { + self.stdin = stdin.trim().to_string(); + } + + /// Determines whether or not Wandbox saves the compilation & replies with a link for you + /// + /// # Arguments + /// * `save` - true if Wandbox should save this compilation + pub fn save(&mut self, save: bool) -> () { + self.save = save; + } + + /// Sets the list of compilation options. Useful for languages like c++ to pass linker/optimization + /// flags. + /// + /// # Arguments + /// * `options` - A list of compiler options i.e ["-Wall", "-Werror"] + pub fn options(&mut self, options: Vec) -> () { + self.options = options; + } + + /// Sets the list of compilation options. Useful for languages like c++ to pass linker/optimization + /// flags. + /// + /// This version allows you to pass a `Vec<&str>` + /// + /// # Arguments + /// * `options` - A list of compiler options i.e ["-Wall", "-Werror"] + pub fn options_str(&mut self, options: Vec<&str>) -> () { + self.options = options.into_iter().map(|f| f.to_owned()).collect(); + } + + /// Finalizes the builder & prepares itself for compilation dispatch. + /// + /// # Arguments + /// * `wb` - An instance of the Wandbox cache to resolve the compilation target + pub fn build(&mut self, wb: &Wandbox) -> Result<(), WandboxError> { + self.compiler_options_raw = self.options.join("\n"); + + if wb.is_valid_language(&self.target) { + let comp = match wb.get_default_compiler(&self.target) { + Some(def) => def, + None => { + return Err(WandboxError::new("Unable to determine default compiler for input language")) } - Ok(()) + }; + self.compiler = comp; + self.lang = self.target.clone(); + } else if wb.is_valid_compiler_str(&self.target) { + let lang = match wb.get_compiler_language_str(&self.target) { + Some(lang) => lang, + None => return Err(WandboxError::new("Unable to determine language for compiler}")), + }; + + self.lang = lang; + self.compiler = self.target.clone(); + } else { + return Err(WandboxError::new("Unable to find compiler or language for target")); } - - /// Dispatches the built request to Wandbox - pub async fn dispatch(&self) -> Result { - let client = reqwest::Client::new(); - - let result = client - .post("https://wandbox.org/api/compile.json") - .json(&self) - .header("Content-Type", "application/json; charset=utf-8") - .send() - .await; - - let response = match result { - Ok(r) => r, - Err(e) => return Err(WandboxError::new(&format!("{}", e))), - }; - - let status_code = response.status().clone(); - let res : CompilationResult = match response.json().await { + Ok(()) + } + + /// Dispatches the built request to Wandbox + pub async fn dispatch(&self) -> Result { + let client = reqwest::Client::new(); + + let result = client + .post("https://wandbox.org/api/compile.json") + .json(&self) + .header("Content-Type", "application/json; charset=utf-8") + .send() + .await; + + let response = match result { + Ok(r) => r, + Err(e) => return Err(WandboxError::new(&format!("{}", e))), + }; + + let status_code = response.status().clone(); + let res : CompilationResult = match response.json().await { Ok(res) => res, Err(_e) => return Err(WandboxError::new(&format!("NVidia replied with: {}\n\ This could mean NVidia is experiencing an outage, or a network connection error has occurred", status_code))) }; - return Ok(res); - } + return Ok(res); + } } /// Information regarding the result of a compilation request. #[derive(Default, Deserialize)] pub struct CompilationResult { - #[serde(default)] - pub status: String, - #[serde(default)] - pub signal: String, - #[serde(rename = "compiler_output", default)] - pub compiler_stdout: String, - #[serde(rename = "compiler_error", default)] - pub compiler_stderr: String, - #[serde(rename = "compiler_message", default)] - pub compiler_all: String, - #[serde(rename = "program_output", default)] - pub program_stdout: String, - #[serde(rename = "program_error", default)] - pub program_stderr: String, - #[serde(rename = "program_message", default)] - pub program_all: String, - #[serde(default)] - pub permlink: String, - #[serde(default)] - pub url: String, + #[serde(default)] + pub status: String, + #[serde(default)] + pub signal: String, + #[serde(rename = "compiler_output", default)] + pub compiler_stdout: String, + #[serde(rename = "compiler_error", default)] + pub compiler_stderr: String, + #[serde(rename = "compiler_message", default)] + pub compiler_all: String, + #[serde(rename = "program_output", default)] + pub program_stdout: String, + #[serde(rename = "program_error", default)] + pub program_stderr: String, + #[serde(rename = "program_message", default)] + pub program_all: String, + #[serde(default)] + pub permlink: String, + #[serde(default)] + pub url: String, } impl fmt::Debug for CompilationResult { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "[{} {}] {}: {}", - self.status, self.signal, self.compiler_all, self.program_all - ) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "[{} {}] {}: {}", self.status, self.signal, self.compiler_all, self.program_all) + } } /// A representation of a language with a list of it's compilers #[derive(Hash, Eq, PartialEq, Debug, Clone)] pub struct Language { - pub name: String, - pub compilers: Vec, + pub name: String, + pub compilers: Vec, } impl Language { - fn remove_compiler(&mut self, str: &str) { - let mut copy = self.compilers.clone(); - copy = copy.into_iter().filter(|v| v.name != str).collect(); - self.compilers = copy; - } + fn remove_compiler(&mut self, str: &str) { + let mut copy = self.compilers.clone(); + copy = copy.into_iter().filter(|v| v.name != str).collect(); + self.compilers = copy; + } } #[derive(Debug)] pub struct WandboxError { - details: String, + details: String, } impl WandboxError { - fn new(msg: &str) -> WandboxError { - WandboxError { - details: msg.to_string(), - } - } + fn new(msg: &str) -> WandboxError { + WandboxError { details: msg.to_string() } + } } impl fmt::Display for WandboxError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.details) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.details) + } } impl std::error::Error for WandboxError { - fn description(&self) -> &str { - &self.details - } + fn description(&self) -> &str { + &self.details + } } diff --git a/wandbox/src/tests/tests.rs b/wandbox/src/tests/tests.rs index 4799485..5b3ff07 100644 --- a/wandbox/src/tests/tests.rs +++ b/wandbox/src/tests/tests.rs @@ -4,77 +4,77 @@ use std::error::Error; #[tokio::test] async fn is_valid_language() -> Result<(), Box> { - let wbox: Wandbox = Wandbox::new(None, None).await?; + let wbox: Wandbox = Wandbox::new(None, None).await?; - let cache = wbox.cache.clone(); - let lock = cache.read().unwrap(); - for (_k, v) in &*lock { - assert!(wbox.get_default_compiler(&v.name).is_some()); - } + let cache = wbox.cache.clone(); + let lock = cache.read().unwrap(); + for (_k, v) in &*lock { + assert!(wbox.get_default_compiler(&v.name).is_some()); + } - Ok(()) + Ok(()) } #[tokio::test] async fn get_default_controller() -> Result<(), Box> { - let wbox: Wandbox = Wandbox::new(None, None).await?; + let wbox: Wandbox = Wandbox::new(None, None).await?; - let cache = wbox.cache.clone(); - let lock = cache.read().unwrap(); - for (_k, v) in &*lock { - assert!(wbox.get_default_compiler(&v.name).is_some()); - } + let cache = wbox.cache.clone(); + let lock = cache.read().unwrap(); + for (_k, v) in &*lock { + assert!(wbox.get_default_compiler(&v.name).is_some()); + } - Ok(()) + Ok(()) } #[tokio::test] async fn is_valid_compiler_str() -> Result<(), Box> { - let wbox: Wandbox = Wandbox::new(None, None).await?; + let wbox: Wandbox = Wandbox::new(None, None).await?; - assert!(wbox.is_valid_compiler_str("gcc-head")); - Ok(()) + assert!(wbox.is_valid_compiler_str("gcc-head")); + Ok(()) } #[tokio::test] async fn ignore_broken_compiler() -> Result<(), Box> { - let mut set: HashSet = HashSet::new(); - set.insert(String::from("gcc-head")); + let mut set: HashSet = HashSet::new(); + set.insert(String::from("gcc-head")); - let wbox: Wandbox = Wandbox::new(Some(set), None).await?; + let wbox: Wandbox = Wandbox::new(Some(set), None).await?; - assert!(!wbox.is_valid_compiler_str("gcc-head")); - Ok(()) + assert!(!wbox.is_valid_compiler_str("gcc-head")); + Ok(()) } #[tokio::test] async fn compilation_builder_lang() -> Result<(), Box> { - let wbox: Wandbox = Wandbox::new(None, None).await?; + let wbox: Wandbox = Wandbox::new(None, None).await?; - let mut builder = crate::CompilationBuilder::new(); - builder.target("c++"); - builder.options_str(vec!["-Wall", "-Werror"]); - builder.code("#include\nint main()\n{\nstd::cout<<\"test\";\n}"); - builder.build(&wbox)?; + let mut builder = crate::CompilationBuilder::new(); + builder.target("c++"); + builder.options_str(vec!["-Wall", "-Werror"]); + builder.code("#include\nint main()\n{\nstd::cout<<\"test\";\n}"); + builder.build(&wbox)?; - let res = builder.dispatch().await.expect("Failed to lookup"); - assert_eq!(res.program_all, "test"); + let res = builder.dispatch().await.expect("Failed to lookup"); + assert_eq!(res.program_all, "test"); - Ok(()) + Ok(()) } #[tokio::test] async fn compilation_builder_compiler() -> Result<(), Box> { - let wbox: Wandbox = Wandbox::new(None, None).await?; + let wbox: Wandbox = Wandbox::new(None, None).await?; - let mut builder = crate::CompilationBuilder::new(); - builder.target("gcc-6.3.0"); - builder.options_str(vec!["-Wall", "-Werror"]); - builder.code("#include\nint main()\n{\nstd::cout<<\"test\";\n}"); - builder.build(&wbox)?; + let mut builder = crate::CompilationBuilder::new(); + builder.target("gcc-6.3.0"); + builder.options_str(vec!["-Wall", "-Werror"]); + builder.code("#include\nint main()\n{\nstd::cout<<\"test\";\n}"); + builder.build(&wbox)?; - let res = builder.dispatch().await.expect("Failed to lookup"); - assert_eq!(res.program_all, "test"); + let res = builder.dispatch().await.expect("Failed to lookup"); + assert_eq!(res.program_all, "test"); - Ok(()) + Ok(()) } From 75fbedbf763c1622cabbdd144836b2e470be4afa Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 27 Feb 2023 13:52:51 +0100 Subject: [PATCH 61/81] .rustfml.toml [fushia] --- src/apis/dbl.rs | 190 +++-- src/boilerplate/c.rs | 104 ++- src/boilerplate/cpp.rs | 70 +- src/boilerplate/csharp.rs | 76 +- src/boilerplate/generator.rs | 75 +- src/boilerplate/java.rs | 76 +- src/boilerplate/php.rs | 32 +- src/build.rs | 28 +- src/cache.rs | 215 +++--- src/commands/asm.rs | 136 ++-- src/commands/block.rs | 20 +- src/commands/botinfo.rs | 106 ++- src/commands/compile.rs | 210 +++-- src/commands/compilers.rs | 144 ++-- src/commands/cpp.rs | 148 ++-- src/commands/format.rs | 217 +++--- src/commands/formats.rs | 69 +- src/commands/help.rs | 210 +++-- src/commands/invite.rs | 12 +- src/commands/languages.rs | 74 +- src/commands/ping.rs | 15 +- src/commands/unblock.rs | 20 +- src/cppeval/eval.rs | 313 ++++---- src/events.rs | 650 ++++++++-------- src/main.rs | 184 +++-- src/managers/command.rs | 338 ++++---- src/managers/compilation.rs | 519 ++++++------- src/managers/stats.rs | 190 ++--- src/slashcmds/asm.rs | 56 +- src/slashcmds/compile.rs | 56 +- src/slashcmds/cpp.rs | 86 ++- src/slashcmds/diff.rs | 196 +++-- src/slashcmds/diff_msg.rs | 164 ++-- src/slashcmds/format.rs | 394 +++++----- src/slashcmds/help.rs | 86 +-- src/slashcmds/invite.rs | 32 +- src/slashcmds/ping.rs | 27 +- src/stats/structures.rs | 140 ++-- src/tests/boilerplate/cpp.rs | 56 +- src/tests/boilerplate/java.rs | 104 +-- src/tests/cpp.rs | 50 +- src/tests/parser.rs | 428 +++++----- src/utls/blocklist.rs | 68 +- src/utls/constants.rs | 207 +++-- src/utls/discordhelpers/embeds.rs | 587 +++++++------- src/utls/discordhelpers/interactions.rs | 986 +++++++++++------------- src/utls/discordhelpers/menu.rs | 126 ++- src/utls/discordhelpers/mod.rs | 465 +++++------ src/utls/parser.rs | 502 ++++++------ 49 files changed, 4445 insertions(+), 4812 deletions(-) diff --git a/src/apis/dbl.rs b/src/apis/dbl.rs index bf2ea43..54bcf8a 100644 --- a/src/apis/dbl.rs +++ b/src/apis/dbl.rs @@ -5,9 +5,9 @@ use tokio::sync::RwLock; use serenity::{http::Http, prelude::TypeMap}; use warp::{ - body::BodyDeserializeError, - http::StatusCode, - {path, Filter, Rejection, Reply}, + body::BodyDeserializeError, + http::StatusCode, + {path, Filter, Rejection, Reply}, }; use dbl::types::Webhook; @@ -18,110 +18,94 @@ use crate::utls::discordhelpers; use crate::utls::discordhelpers::embeds::*; pub struct BotsListApi { - password: String, - port: u16, - vote_channel: u64, + password: String, + port: u16, + vote_channel: u64, } impl BotsListApi { - pub fn new() -> BotsListApi { - let webhookpass = env::var("DBL_WEBHOOK_PASSWORD").unwrap_or_default(); - let webhookport = env::var("DBL_WEBHOOK_PORT").unwrap_or_default(); - let port = webhookport.parse::().unwrap_or_default(); - let vote_channel = env::var("VOTE_CHANNEL").unwrap_or_default(); - let channel_id = vote_channel.parse::().unwrap_or_default(); - - BotsListApi { - password: webhookpass, - port, - vote_channel: channel_id, + pub fn new() -> BotsListApi { + let webhookpass = env::var("DBL_WEBHOOK_PASSWORD").unwrap_or_default(); + let webhookport = env::var("DBL_WEBHOOK_PORT").unwrap_or_default(); + let port = webhookport.parse::().unwrap_or_default(); + let vote_channel = env::var("VOTE_CHANNEL").unwrap_or_default(); + let channel_id = vote_channel.parse::().unwrap_or_default(); + + BotsListApi { password: webhookpass, port, vote_channel: channel_id } + } + + pub fn should_spawn(&self) -> bool { + self.port != 0 && !self.password.is_empty() && self.vote_channel != 0 + } + + pub fn spawn(self, http: Arc, data: Arc>) { + tokio::spawn(async move { + BotsListApi::start_webhook(http, data, self.vote_channel, self.password.clone(), self.port) + .await + }); + } + + async fn start_webhook( + http: Arc, + data: Arc>, + vote_channel: u64, + pass: String, + port: u16, + ) { + let filter = warp::header::("authorization") + .and_then(move |value| { + if value == pass { + future::ok(()) + } else { + future::err(warp::reject::custom(Unauthorized)) } - } - - pub fn should_spawn(&self) -> bool { - self.port != 0 && !self.password.is_empty() && self.vote_channel != 0 - } - - pub fn spawn(self, http: Arc, data: Arc>) { - tokio::spawn(async move { - BotsListApi::start_webhook( - http, - data, - self.vote_channel, - self.password.clone(), - self.port, - ) - .await - }); - } - - async fn start_webhook( - http: Arc, - data: Arc>, - vote_channel: u64, - pass: String, - port: u16, - ) { - let filter = warp::header::("authorization") - .and_then(move |value| { - if value == pass { - future::ok(()) - } else { - future::err(warp::reject::custom(Unauthorized)) - } - }) - .untuple_one(); - - let webhook = warp::post() - .and(path!("dblwebhook")) - .and(filter) - .and(warp::body::json()) - .map(move |hook: Webhook| { - let user_id = hook.user.0; - let data = data.clone(); - let http: Arc = http.clone(); - BotsListApi::send_vote(user_id, vote_channel, http, data); - - warp::reply() - }) - .recover(custom_error); - - info!("Starting webhook"); - warp::serve(webhook).run(([0, 0, 0, 0], port)).await; - } - - fn send_vote(user_id: u64, vote_channel: u64, http: Arc, data: Arc>) { - tokio::spawn(async move { - let read = data.read().await; - let client_lock = read.get::().expect("Unable to find dbl data"); - let awd = client_lock.read().await; - - let usr = match awd.user(user_id).await { - Ok(u) => u, - Err(err) => return warn!("Unable to retrieve user info: {}", err), - }; - - let tag = format!("{}#{}", usr.username, usr.discriminator); - let emb = build_dblvote_embed(tag); - discordhelpers::manual_dispatch(http.clone(), vote_channel, emb).await; - }); - } + }) + .untuple_one(); + + let webhook = warp::post() + .and(path!("dblwebhook")) + .and(filter) + .and(warp::body::json()) + .map(move |hook: Webhook| { + let user_id = hook.user.0; + let data = data.clone(); + let http: Arc = http.clone(); + BotsListApi::send_vote(user_id, vote_channel, http, data); + + warp::reply() + }) + .recover(custom_error); + + info!("Starting webhook"); + warp::serve(webhook).run(([0, 0, 0, 0], port)).await; + } + + fn send_vote(user_id: u64, vote_channel: u64, http: Arc, data: Arc>) { + tokio::spawn(async move { + let read = data.read().await; + let client_lock = read.get::().expect("Unable to find dbl data"); + let awd = client_lock.read().await; + + let usr = match awd.user(user_id).await { + Ok(u) => u, + Err(err) => return warn!("Unable to retrieve user info: {}", err), + }; + + let tag = format!("{}#{}", usr.username, usr.discriminator); + let emb = build_dblvote_embed(tag); + discordhelpers::manual_dispatch(http.clone(), vote_channel, emb).await; + }); + } } async fn custom_error(err: Rejection) -> Result { - if err.find::().is_some() { - Ok(warp::reply::with_status( - warp::reply(), - StatusCode::BAD_REQUEST, - )) - } else if err.find::().is_some() { - Ok(warp::reply::with_status( - warp::reply(), - StatusCode::UNAUTHORIZED, - )) - } else { - Err(err) - } + if err.find::().is_some() { + Ok(warp::reply::with_status(warp::reply(), StatusCode::BAD_REQUEST)) + } else if err.find::().is_some() { + Ok(warp::reply::with_status(warp::reply(), StatusCode::UNAUTHORIZED)) + } else { + Err(err) + } } #[derive(Debug)] @@ -130,9 +114,9 @@ struct Unauthorized; impl warp::reject::Reject for Unauthorized {} impl std::fmt::Display for Unauthorized { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Unauthorized") - } + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Unauthorized") + } } impl std::error::Error for Unauthorized {} diff --git a/src/boilerplate/c.rs b/src/boilerplate/c.rs index a36f9ce..98eda88 100644 --- a/src/boilerplate/c.rs +++ b/src/boilerplate/c.rs @@ -4,67 +4,65 @@ use std::fmt::Write as _; 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 ; + fn new(input: &str) -> Self { + let mut formated = input.to_string(); + formated = formated.replace(';', ";\n"); // separate lines by ; - Self { input: formated } - } - - fn generate(&self) -> String { - let mut main_body = String::default(); - let mut header = String::default(); + Self { input: formated } + } - let lines = self.input.split('\n'); - for line in lines { - let trimmed = line.trim(); - if trimmed.starts_with("#i") || trimmed.starts_with("#d") { - // header.push_str(&format!("{}\n", trimmed)); - writeln!(header, "{}", trimmed).unwrap(); - } else { - // main_body.push_str(&format!("{}\n", trimmed)); - writeln!(main_body, "{}", trimmed).unwrap(); - } - } + fn generate(&self) -> String { + let mut main_body = String::default(); + let mut header = String::default(); - if (main_body.contains("printf") || main_body.contains("scanf")) - && !header.contains("stdio.h") - { - header.push_str("#include \n"); - } - if (main_body.contains("malloc") - || main_body.contains("free") - || main_body.contains("realloc") - || main_body.contains("calloc") - || main_body.contains("EXIT_FAILURE") - || main_body.contains("EXIT_SUCCESS")) - && !header.contains("stdlib.h") - { - header.push_str("#include "); - } - if (main_body.contains("strlen") || main_body.contains("strcmp")) - && !header.contains("string.h") - { - header.push_str("#include \n"); - } - if (main_body.contains("time") || main_body.contains("ctime")) && !header.contains("time.h") - { - header.push_str("#include \n"); - } + let lines = self.input.split('\n'); + for line in lines { + let trimmed = line.trim(); + if trimmed.starts_with("#i") || trimmed.starts_with("#d") { + // header.push_str(&format!("{}\n", trimmed)); + writeln!(header, "{}", trimmed).unwrap(); + } else { + // main_body.push_str(&format!("{}\n", trimmed)); + writeln!(main_body, "{}", trimmed).unwrap(); + } + } - format!("{}\nint main(void) {{\n{}return 0;\n}}", header, main_body) + if (main_body.contains("printf") || main_body.contains("scanf")) && !header.contains("stdio.h") + { + header.push_str("#include \n"); + } + if (main_body.contains("malloc") + || main_body.contains("free") + || main_body.contains("realloc") + || main_body.contains("calloc") + || main_body.contains("EXIT_FAILURE") + || main_body.contains("EXIT_SUCCESS")) + && !header.contains("stdlib.h") + { + header.push_str("#include "); } + if (main_body.contains("strlen") || main_body.contains("strcmp")) + && !header.contains("string.h") + { + header.push_str("#include \n"); + } + if (main_body.contains("time") || main_body.contains("ctime")) && !header.contains("time.h") { + header.push_str("#include \n"); + } + + format!("{}\nint main(void) {{\n{}return 0;\n}}", header, main_body) + } - fn needs_boilerplate(&self) -> bool { - for m in C_LIKE_MAIN_REGEX.captures_iter(&self.input) { - if m.name("main").is_some() { - return false; - } - } - true + fn needs_boilerplate(&self) -> bool { + for m in C_LIKE_MAIN_REGEX.captures_iter(&self.input) { + if m.name("main").is_some() { + return false; + } } + true + } } diff --git a/src/boilerplate/cpp.rs b/src/boilerplate/cpp.rs index 42103d2..01afd7f 100644 --- a/src/boilerplate/cpp.rs +++ b/src/boilerplate/cpp.rs @@ -4,46 +4,46 @@ use std::fmt::Write as _; 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 ; - - Self { input: formated } + fn new(input: &str) -> Self { + let mut formated = input.to_string(); + formated = formated.replace(';', ";\n"); // separate lines by ; + + Self { input: formated } + } + + fn generate(&self) -> String { + let mut main_body = String::default(); + let mut header = String::default(); + + let lines = self.input.split('\n'); + for line in lines { + let trimmed = line.trim(); + if trimmed.starts_with("using") || trimmed.starts_with("#i") { + // header.push_str(&format!("{}\n", trimmed)); + writeln!(header, "{}", trimmed).unwrap(); + } else { + // main_body.push_str(&format!("{}\n", trimmed)); + writeln!(main_body, "{}", trimmed).unwrap(); + } } - fn generate(&self) -> String { - let mut main_body = String::default(); - let mut header = String::default(); - - let lines = self.input.split('\n'); - for line in lines { - let trimmed = line.trim(); - if trimmed.starts_with("using") || trimmed.starts_with("#i") { - // header.push_str(&format!("{}\n", trimmed)); - writeln!(header, "{}", trimmed).unwrap(); - } else { - // main_body.push_str(&format!("{}\n", trimmed)); - writeln!(main_body, "{}", trimmed).unwrap(); - } - } - - // if they included nothing, we can just manually include everything - if !header.contains("#include") { - header.push_str("#include \n"); - } - format!("{}\nint main(void) {{\n{}return 0;\n}}", header, main_body) + // if they included nothing, we can just manually include everything + if !header.contains("#include") { + header.push_str("#include \n"); } - - fn needs_boilerplate(&self) -> bool { - for m in C_LIKE_MAIN_REGEX.captures_iter(&self.input) { - if m.name("main").is_some() { - return false; - } - } - true + format!("{}\nint main(void) {{\n{}return 0;\n}}", header, main_body) + } + + fn needs_boilerplate(&self) -> bool { + for m in C_LIKE_MAIN_REGEX.captures_iter(&self.input) { + if m.name("main").is_some() { + return false; + } } + true + } } diff --git a/src/boilerplate/csharp.rs b/src/boilerplate/csharp.rs index 7f86c07..dd6c132 100644 --- a/src/boilerplate/csharp.rs +++ b/src/boilerplate/csharp.rs @@ -4,49 +4,49 @@ use std::fmt::Write as _; 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 ; - - Self { input: formated } + fn new(input: &str) -> Self { + let mut formated = input.to_string(); + formated = formated.replace(';', ";\n"); // separate lines by ; + + Self { input: formated } + } + + fn generate(&self) -> String { + let mut main_body = String::default(); + let mut header = String::default(); + + 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)); + writeln!(header, "{}", trimmed).unwrap(); + } else { + // main_body.push_str(&format!("{}\n", trimmed)); + writeln!(main_body, "{}", trimmed).unwrap(); + } } - fn generate(&self) -> String { - let mut main_body = String::default(); - let mut header = String::default(); - - 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)); - writeln!(header, "{}", trimmed).unwrap(); - } else { - // main_body.push_str(&format!("{}\n", trimmed)); - writeln!(main_body, "{}", trimmed).unwrap(); - } - } - - // if they included nothing, we can just manually include System since they probably want it - if header.is_empty() { - header.push_str("using System;\n"); - } - format!( - "{}\nnamespace Main{{\nclass Program {{\n static void Main(string[] args) {{\n{}}}}}}}", - header, main_body - ) + // if they included nothing, we can just manually include System since they probably want it + if header.is_empty() { + header.push_str("using System;\n"); } - - fn needs_boilerplate(&self) -> bool { - for m in CSHARP_MAIN_REGEX.captures_iter(&self.input) { - if m.name("main").is_some() { - return false; - } - } - true + 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 m.name("main").is_some() { + return false; + } } + true + } } diff --git a/src/boilerplate/generator.rs b/src/boilerplate/generator.rs index f233616..121c09c 100644 --- a/src/boilerplate/generator.rs +++ b/src/boilerplate/generator.rs @@ -1,64 +1,63 @@ use crate::boilerplate::{ - c::CGenerator, cpp::CppGenerator, csharp::CSharpGenerator, java::JavaGenerator, - php::PHPGenerator, + c::CGenerator, cpp::CppGenerator, csharp::CSharpGenerator, java::JavaGenerator, php::PHPGenerator, }; pub trait BoilerPlateGenerator { - fn new(input: &str) -> Self - where - Self: Sized; - fn generate(&self) -> String; - fn needs_boilerplate(&self) -> bool; + fn new(input: &str) -> Self + where + Self: Sized; + fn generate(&self) -> String; + fn needs_boilerplate(&self) -> bool; } pub struct BoilerPlate where - T: BoilerPlateGenerator, + T: BoilerPlateGenerator, { - generator: Box, + generator: Box, } impl BoilerPlate where - T: BoilerPlateGenerator, + T: BoilerPlateGenerator, { - pub fn new(generator: Box) -> Self { - Self { generator } - } + pub fn new(generator: Box) -> Self { + Self { generator } + } - pub fn generate(&self) -> String { - self.generator.generate() - } + pub fn generate(&self) -> String { + self.generator.generate() + } - pub fn needs_boilerplate(&self) -> bool { - self.generator.needs_boilerplate() - } + pub fn needs_boilerplate(&self) -> bool { + self.generator.needs_boilerplate() + } } pub struct Null; impl BoilerPlateGenerator for Null { - fn new(_: &str) -> Self { - Self {} - } + fn new(_: &str) -> Self { + Self {} + } - fn generate(&self) -> String { - panic!("Cannot generate null boilerplate!"); - } + fn generate(&self) -> String { + panic!("Cannot generate null boilerplate!"); + } - fn needs_boilerplate(&self) -> bool { - false - } + fn needs_boilerplate(&self) -> bool { + false + } } 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))), - "php" => BoilerPlate::new(Box::new(PHPGenerator::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))), - } + 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))), + "php" => BoilerPlate::new(Box::new(PHPGenerator::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))), + } } diff --git a/src/boilerplate/java.rs b/src/boilerplate/java.rs index 48af89b..cbc8f02 100644 --- a/src/boilerplate/java.rs +++ b/src/boilerplate/java.rs @@ -4,50 +4,50 @@ use std::fmt::Write as _; 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 ; - - Self { input: formated } + fn new(input: &str) -> Self { + let mut formated = input.to_string(); + formated = formated.replace(';', ";\n"); // separate lines by ; + + Self { input: formated } + } + + fn generate(&self) -> String { + let mut main_body = String::default(); + let mut header = String::default(); + + 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)); + writeln!(header, "{}", trimmed).unwrap(); + } else { + // main_body.push_str(&format!("{}\n", trimmed)); + writeln!(main_body, "{}", trimmed).unwrap(); + } } - fn generate(&self) -> String { - let mut main_body = String::default(); - let mut header = String::default(); - - 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)); - writeln!(header, "{}", trimmed).unwrap(); - } else { - // main_body.push_str(&format!("{}\n", trimmed)); - writeln!(main_body, "{}", trimmed).unwrap(); - } - } - - // if they included nothing, we can just manually include everything - if !header.contains("import") { - header.push_str("import java.util.*;\n"); - } - - format!( - "{}\nclass Main{{\npublic static void main(String[] args) throws Exception {{\n{}}}}}", - header, main_body - ) + // if they included nothing, we can just manually include everything + if !header.contains("import") { + header.push_str("import java.util.*;\n"); } - fn needs_boilerplate(&self) -> bool { - for m in JAVA_MAIN_REGEX.captures_iter(&self.input) { - if m.name("main").is_some() { - return false; - } - } - true + format!( + "{}\nclass Main{{\npublic static void main(String[] args) throws Exception {{\n{}}}}}", + header, main_body + ) + } + + fn needs_boilerplate(&self) -> bool { + for m in JAVA_MAIN_REGEX.captures_iter(&self.input) { + if m.name("main").is_some() { + return false; + } } + true + } } diff --git a/src/boilerplate/php.rs b/src/boilerplate/php.rs index b91667d..3afaab7 100644 --- a/src/boilerplate/php.rs +++ b/src/boilerplate/php.rs @@ -2,27 +2,27 @@ use crate::boilerplate::generator::BoilerPlateGenerator; use crate::utls::constants::PHP_START_REGEX; pub struct PHPGenerator { - input: String, + input: String, } impl BoilerPlateGenerator for PHPGenerator { - fn new(input: &str) -> Self { - let mut formated = input.to_string(); - formated = formated.replace(';', ";\n"); // separate lines by ; + fn new(input: &str) -> Self { + let mut formated = input.to_string(); + formated = formated.replace(';', ";\n"); // separate lines by ; - Self { input: formated } - } + Self { input: formated } + } - fn generate(&self) -> String { - format!(" String { + format!(" bool { - for m in PHP_START_REGEX.captures_iter(&self.input) { - if m.name("php_start").is_some() { - return false; - } - } - true + fn needs_boilerplate(&self) -> bool { + for m in PHP_START_REGEX.captures_iter(&self.input) { + if m.name("php_start").is_some() { + return false; + } } + true + } } diff --git a/src/build.rs b/src/build.rs index b08ff63..f1c6969 100644 --- a/src/build.rs +++ b/src/build.rs @@ -1,22 +1,22 @@ use std::process::Command; fn main() { - let long = get_github_build(false); - let short = get_github_build(true); - println!("cargo:rustc-env=GIT_HASH_LONG={}", long); - println!("cargo:rustc-env=GIT_HASH_SHORT={}", short); + let long = get_github_build(false); + let short = get_github_build(true); + println!("cargo:rustc-env=GIT_HASH_LONG={}", long); + println!("cargo:rustc-env=GIT_HASH_SHORT={}", short); } pub fn get_github_build(short: bool) -> String { - let mut args = vec!["rev-parse"]; - if short { - args.push("--short"); - } + let mut args = vec!["rev-parse"]; + if short { + args.push("--short"); + } - args.push("HEAD"); - if let Ok(output) = Command::new("git").args(&args).output() { - String::from_utf8(output.stdout).unwrap() - } else { - String::new() - } + args.push("HEAD"); + if let Ok(output) = Command::new("git").args(&args).output() { + String::from_utf8(output.stdout).unwrap() + } else { + String::new() + } } diff --git a/src/cache.rs b/src/cache.rs index f6501a2..6b84243 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -24,170 +24,167 @@ use serenity::model::interactions::application_command::ApplicationCommandIntera /// Contains bot configuration information provided mostly from environment variables pub struct ConfigCache; impl TypeMapKey for ConfigCache { - type Value = Arc>>; + type Value = Arc>>; } /// Main interface for compiler options for either Compiler Explorer or WandBox pub struct CompilerCache; impl TypeMapKey for CompilerCache { - type Value = Arc>; + type Value = Arc>; } /// Contains our top.gg api client for server count updates pub struct DblCache; impl TypeMapKey for DblCache { - type Value = Arc>; + type Value = Arc>; } /// Our endpoints for the in-house statistics tracing - see apis/dbl.rs pub struct StatsManagerCache; impl TypeMapKey for StatsManagerCache { - type Value = Arc>; + type Value = Arc>; } /// Internal blocklist for abusive users or guilds pub struct BlocklistCache; impl TypeMapKey for BlocklistCache { - type Value = Arc>; + type Value = Arc>; } /// Contains the shard manager - used to send global presence updates pub struct ShardManagerCache; impl TypeMapKey for ShardManagerCache { - type Value = Arc>; + type Value = Arc>; } 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 { - MessageCacheEntry { - our_msg, - original_msg, - } - } + pub fn new(our_msg: Message, original_msg: Message) -> Self { + MessageCacheEntry { our_msg, original_msg } + } } /// Message cache to interact with our own messages after they are dispatched pub struct MessageCache; impl TypeMapKey for MessageCache { - type Value = Arc>>; + type Value = Arc>>; } /// Holds the Command Manager which handles command registration logic pub struct CommandCache; impl TypeMapKey for CommandCache { - type Value = Arc>; + type Value = Arc>; } pub struct DiffCommandEntry { - pub expired_timestamp: SystemTime, - pub content: String, - pub first_interaction: ApplicationCommandInteraction, + pub expired_timestamp: SystemTime, + pub content: String, + pub first_interaction: ApplicationCommandInteraction, } impl DiffCommandEntry { - pub fn new(content: &str, msg: &ApplicationCommandInteraction) -> Self { - DiffCommandEntry { - content: content.to_owned(), - expired_timestamp: SystemTime::now() + std::time::Duration::from_secs(30), - first_interaction: msg.clone(), - } + pub fn new(content: &str, msg: &ApplicationCommandInteraction) -> Self { + DiffCommandEntry { + content: content.to_owned(), + expired_timestamp: SystemTime::now() + std::time::Duration::from_secs(30), + first_interaction: msg.clone(), } + } - pub fn is_expired(&self) -> bool { - self.expired_timestamp < SystemTime::now() - } + pub fn is_expired(&self) -> bool { + self.expired_timestamp < SystemTime::now() + } } /// Contains the first message used in the diff message command, w/ expiry timestamp pub struct DiffCommandCache; impl TypeMapKey for DiffCommandCache { - type Value = Arc>>; + type Value = Arc>>; } pub async fn fill( - data: Arc>, - prefix: &str, - id: u64, - shard_manager: Arc>, + data: Arc>, + prefix: &str, + id: u64, + shard_manager: Arc>, ) -> Result<(), Box> { - let mut data = data.write().await; - - // Lets map some common things in BotInfo - let mut map = HashMap::<&str, String>::new(); - - // optional additions - let emoji_identifiers = [ - "SUCCESS_EMOJI_ID", - "SUCCESS_EMOJI_NAME", - "FAIL_EMOJI_ID", - "FAIL_EMOJI_NAME", - "LOADING_EMOJI_ID", - "LOADING_EMOJI_NAME", - "LOGO_EMOJI_ID", - "LOGO_EMOJI_NAME", - ]; - for id in &emoji_identifiers { - if let Ok(envvar) = env::var(id) { - if !envvar.is_empty() { - map.insert(id, envvar); - } - } - } - - map.insert("GIT_HASH_LONG", String::from(env!("GIT_HASH_LONG"))); - map.insert("GIT_HASH_SHORT", String::from(env!("GIT_HASH_SHORT"))); - - if let Ok(jlog) = env::var("JOIN_LOG") { - map.insert("JOIN_LOG", jlog); - } - if let Ok(clog) = env::var("COMPILE_LOG") { - map.insert("COMPILE_LOG", clog); - } - - map.insert("INVITE_LINK", env::var("INVITE_LINK")?); - map.insert("DISCORDBOTS_LINK", env::var("DISCORDBOTS_LINK")?); - map.insert("GITHUB_LINK", env::var("GITHUB_LINK")?); - map.insert("STATS_LINK", env::var("STATS_LINK")?); - map.insert("BOT_PREFIX", String::from(prefix)); - map.insert("BOT_ID", id.to_string()); - data.insert::(Arc::new(RwLock::new(map))); - - // Shard manager for universal presence - data.insert::(shard_manager); - - // Message delete cache - data.insert::(Arc::new(Mutex::new(LruCache::new(25)))); - - // Compiler manager - data.insert::(Arc::new(RwLock::new(CompilationManager::new().await?))); - info!("Compilation manager loaded"); - - // DBL - if let Ok(token) = env::var("DBL_TOKEN") { - let client = dbl::Client::new(token)?; - data.insert::(Arc::new(RwLock::new(client))); - } - - // Stats tracking - let stats = StatsManager::new(); - if stats.should_track() { - info!("Statistics tracking enabled"); + let mut data = data.write().await; + + // Lets map some common things in BotInfo + let mut map = HashMap::<&str, String>::new(); + + // optional additions + let emoji_identifiers = [ + "SUCCESS_EMOJI_ID", + "SUCCESS_EMOJI_NAME", + "FAIL_EMOJI_ID", + "FAIL_EMOJI_NAME", + "LOADING_EMOJI_ID", + "LOADING_EMOJI_NAME", + "LOGO_EMOJI_ID", + "LOGO_EMOJI_NAME", + ]; + for id in &emoji_identifiers { + if let Ok(envvar) = env::var(id) { + if !envvar.is_empty() { + map.insert(id, envvar); + } } - data.insert::(Arc::new(Mutex::new(stats))); - - // Blocklist - let blocklist = Blocklist::new(); - data.insert::(Arc::new(RwLock::new(blocklist))); - - // Commands - let commands = CommandManager::new(); - data.insert::(Arc::new(RwLock::new(commands))); - - // Diff command message tracker - data.insert::(Arc::new(Mutex::new(LruCache::new(10)))); - - Ok(()) + } + + map.insert("GIT_HASH_LONG", String::from(env!("GIT_HASH_LONG"))); + map.insert("GIT_HASH_SHORT", String::from(env!("GIT_HASH_SHORT"))); + + if let Ok(jlog) = env::var("JOIN_LOG") { + map.insert("JOIN_LOG", jlog); + } + if let Ok(clog) = env::var("COMPILE_LOG") { + map.insert("COMPILE_LOG", clog); + } + + map.insert("INVITE_LINK", env::var("INVITE_LINK")?); + map.insert("DISCORDBOTS_LINK", env::var("DISCORDBOTS_LINK")?); + map.insert("GITHUB_LINK", env::var("GITHUB_LINK")?); + map.insert("STATS_LINK", env::var("STATS_LINK")?); + map.insert("BOT_PREFIX", String::from(prefix)); + map.insert("BOT_ID", id.to_string()); + data.insert::(Arc::new(RwLock::new(map))); + + // Shard manager for universal presence + data.insert::(shard_manager); + + // Message delete cache + data.insert::(Arc::new(Mutex::new(LruCache::new(25)))); + + // Compiler manager + data.insert::(Arc::new(RwLock::new(CompilationManager::new().await?))); + info!("Compilation manager loaded"); + + // DBL + if let Ok(token) = env::var("DBL_TOKEN") { + let client = dbl::Client::new(token)?; + data.insert::(Arc::new(RwLock::new(client))); + } + + // Stats tracking + let stats = StatsManager::new(); + if stats.should_track() { + info!("Statistics tracking enabled"); + } + data.insert::(Arc::new(Mutex::new(stats))); + + // Blocklist + let blocklist = Blocklist::new(); + data.insert::(Arc::new(RwLock::new(blocklist))); + + // Commands + let commands = CommandManager::new(); + data.insert::(Arc::new(RwLock::new(commands))); + + // Diff command message tracker + data.insert::(Arc::new(Mutex::new(LruCache::new(10)))); + + Ok(()) } diff --git a/src/commands/asm.rs b/src/commands/asm.rs index 55ebd4e..d78a341 100644 --- a/src/commands/asm.rs +++ b/src/commands/asm.rs @@ -1,6 +1,6 @@ use serenity::{ - client::Context, - framework::standard::{macros::command, Args, CommandError, CommandResult}, + client::Context, + framework::standard::{macros::command, Args, CommandError, CommandResult}, }; use crate::cache::{CompilerCache, ConfigCache, MessageCache, MessageCacheEntry}; @@ -19,90 +19,76 @@ use crate::utls::parser; #[command] #[bucket = "nospam"] pub async fn asm(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let emb = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; - let mut emb_msg = embeds::embed_message(emb); - let asm_embed = msg - .channel_id - .send_message(&ctx.http, |_| &mut emb_msg) - .await?; + let emb = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; + let mut emb_msg = embeds::embed_message(emb); + let asm_embed = msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await?; - // Success/fail react - let compilation_successful = asm_embed.embeds[0].colour.unwrap().0 == COLOR_OKAY; - discordhelpers::send_completion_react(ctx, &asm_embed, compilation_successful).await?; + // Success/fail react + let compilation_successful = asm_embed.embeds[0].colour.unwrap().0 == COLOR_OKAY; + discordhelpers::send_completion_react(ctx, &asm_embed, compilation_successful).await?; - 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()), - ); - debug!("Command executed"); - Ok(()) + 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())); + debug!("Command executed"); + Ok(()) } pub async fn handle_request( - ctx: Context, - mut content: String, - author: User, - msg: &Message, + 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(); - discordhelpers::build_reaction(loading_id.parse::()?, &loading_name) - } else { - ReactionType::Unicode(String::from("โณ")) - } - }; - - // Try to load in an attachment - let (code, ext) = parser::get_message_attachment(&msg.attachments).await?; - if !code.is_empty() { - // content.push_str(&format!("\n```{}\n{}\n```\n", ext, code)); - writeln!(content, "\n```{}\n{}\n```", ext, code).unwrap(); + 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(); + discordhelpers::build_reaction(loading_id.parse::()?, &loading_name) + } else { + ReactionType::Unicode(String::from("โณ")) } + }; - // 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))); - } - }; + // Try to load in an attachment + let (code, ext) = parser::get_message_attachment(&msg.attachments).await?; + if !code.is_empty() { + // content.push_str(&format!("\n```{}\n{}\n```\n", ext, code)); + writeln!(content, "\n```{}\n{}\n```", ext, code).unwrap(); + } - // send out loading emote - if msg - .react(&ctx.http, loading_reaction.clone()) - .await - .is_err() + // 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 { - return Err(CommandError::from("Unable to react to message, am I missing permissions to react or use external emoji?\n{}")); - } - - let comp_mngr_lock = comp_mngr.read().await; - let response = match comp_mngr_lock.assembly(&result, &author).await { - Ok(resp) => resp, - Err(e) => { - discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await?; - return Err(CommandError::from(format!( - "Azure request failed!\n\n{}", - e - ))); - } + Ok(r) => r, + Err(e) => { + return Err(CommandError::from(format!("{}", e))); + } }; - // remove our loading emote - discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await?; + // send out loading emote + 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?\n{}", + )); + } + + let comp_mngr_lock = comp_mngr.read().await; + let response = match comp_mngr_lock.assembly(&result, &author).await { + Ok(resp) => resp, + Err(e) => { + discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await?; + return Err(CommandError::from(format!("Azure request failed!\n\n{}", e))); + } + }; + + // remove our loading emote + discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await?; - Ok(response.1) + Ok(response.1) } diff --git a/src/commands/block.rs b/src/commands/block.rs index 40a15f0..ccf935e 100644 --- a/src/commands/block.rs +++ b/src/commands/block.rs @@ -7,19 +7,17 @@ use crate::cache::BlocklistCache; #[command] #[owners_only] pub async fn block(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - if args.len() != 1 { - return Err(CommandError::from("Supply an id to block")); - } + if args.len() != 1 { + return Err(CommandError::from("Supply an id to block")); + } - let arg = args.parse::()?; + let arg = args.parse::()?; - let data = ctx.data.read().await; - let mut blocklist = data.get::().unwrap().write().await; + let data = ctx.data.read().await; + let mut blocklist = data.get::().unwrap().write().await; - blocklist.block(arg); + blocklist.block(arg); - msg.channel_id - .say(&ctx.http, format!("Blocked snowflake `{}`", &arg)) - .await?; - Ok(()) + 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 b818a11..61efad5 100644 --- a/src/commands/botinfo.rs +++ b/src/commands/botinfo.rs @@ -10,73 +10,69 @@ use crate::utls::constants::COLOR_OKAY; #[command] pub async fn botinfo(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let invite = env::var("INVITE_LINK").expect("Expected invite link envvar"); - let topgg = env::var("DISCORDBOTS_LINK").expect("Expected top.gg link envvar"); - let github = env::var("GITHUB_LINK").expect("Expected github link envvar"); - let stats = env::var("STATS_LINK").expect("Expected stats link envvar"); + let invite = env::var("INVITE_LINK").expect("Expected invite link envvar"); + let topgg = env::var("DISCORDBOTS_LINK").expect("Expected top.gg link envvar"); + let github = env::var("GITHUB_LINK").expect("Expected github link envvar"); + let stats = env::var("STATS_LINK").expect("Expected stats link envvar"); - let hash_short; - let hash_long; - let avatar = { - let data_read = ctx.data.read().await; - let botinfo_lock = data_read - .get::() - .expect("Expected ConfigCache in global cache") - .clone(); - let botinfo = botinfo_lock.read().await; - hash_short = botinfo.get("GIT_HASH_SHORT").unwrap().clone(); - hash_long = botinfo.get("GIT_HASH_LONG").unwrap().clone(); - botinfo.get("BOT_AVATAR").unwrap().clone() - }; + let hash_short; + let hash_long; + let avatar = { + let data_read = ctx.data.read().await; + let botinfo_lock = + data_read.get::().expect("Expected ConfigCache in global cache").clone(); + let botinfo = botinfo_lock.read().await; + hash_short = botinfo.get("GIT_HASH_SHORT").unwrap().clone(); + hash_long = botinfo.get("GIT_HASH_LONG").unwrap().clone(); + botinfo.get("BOT_AVATAR").unwrap().clone() + }; - let msg = msg - .channel_id - .send_message(&ctx.http, |m: &mut CreateMessage| { - m.embed(|e: &mut CreateEmbed| { - e.title("Compiler Bot"); + let msg = msg + .channel_id + .send_message(&ctx.http, |m: &mut CreateMessage| { + m.embed(|e: &mut CreateEmbed| { + e.title("Compiler Bot"); - let fmt = format!( - "{}\n + let fmt = format!( + "{}\n {} [Invitation link]({}) [Vote for us!]({}) [GitHub Repository]({}) [Statistics Tracker]({}) {}", - env!("CARGO_PKG_DESCRIPTION"), - "==========================", - invite, - topgg, - github, - stats, - "==========================" - ); + env!("CARGO_PKG_DESCRIPTION"), + "==========================", + invite, + topgg, + github, + stats, + "==========================" + ); - e.description(fmt); - e.thumbnail(avatar); - e.color(COLOR_OKAY); + e.description(fmt); + 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 2021", false), - ("Software Version", env!("CARGO_PKG_VERSION"), false), - ("Author", env!("CARGO_PKG_AUTHORS"), false), - ("Build Information", str.as_str(), false), - ]); - e - }); - m - }) - .await; + e.fields(vec![ + ("Language", "Rust 2021", false), + ("Software Version", env!("CARGO_PKG_VERSION"), false), + ("Author", env!("CARGO_PKG_AUTHORS"), false), + ("Build Information", str.as_str(), false), + ]); + e + }); + m + }) + .await; - if let Err(why) = msg { - warn!("Error sending embed: {:?}", why); - } + if let Err(why) = msg { + warn!("Error sending embed: {:?}", why); + } - debug!("Command executed"); - Ok(()) + debug!("Command executed"); + Ok(()) } diff --git a/src/commands/compile.rs b/src/commands/compile.rs index d5d52e3..4423492 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -21,125 +21,109 @@ use std::fmt::Write as _; #[command] #[bucket = "nospam"] pub async fn compile(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let data_read = ctx.data.read().await; - - // Handle wandbox request logic - let embed = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; - - // Send our final embed - let mut message = embeds::embed_message(embed); - let compilation_embed = msg - .channel_id - .send_message(&ctx.http, |_| &mut message) - .await?; - - // Success/fail react - let compilation_successful = compilation_embed.embeds[0].colour.unwrap().0 == COLOR_OKAY; - 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()), - ); - debug!("Command executed"); - Ok(()) + let data_read = ctx.data.read().await; + + // Handle wandbox request logic + let embed = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; + + // Send our final embed + let mut message = embeds::embed_message(embed); + let compilation_embed = msg.channel_id.send_message(&ctx.http, |_| &mut message).await?; + + // Success/fail react + let compilation_successful = compilation_embed.embeds[0].colour.unwrap().0 == COLOR_OKAY; + 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())); + debug!("Command executed"); + Ok(()) } pub async fn handle_request( - ctx: Context, - mut content: String, - author: User, - msg: &Message, + 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(); - discordhelpers::build_reaction(loading_id.parse::()?, &loading_name) - } else { - ReactionType::Unicode(String::from("โณ")) - } - }; - - // Try to load in an attachment - let (code, ext) = parser::get_message_attachment(&msg.attachments).await?; - if !code.is_empty() { - // content.push_str(&format!("\n```{}\n{}\n```\n", ext, code)); - writeln!(content, "\n```{}\n{}\n```", ext, code).unwrap(); + 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(); + discordhelpers::build_reaction(loading_id.parse::()?, &loading_name) + } else { + ReactionType::Unicode(String::from("โณ")) } - - // 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?; - - // send out loading emote - 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?\n{}")); - } - - // dispatch our req - 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) => { - // 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))); - } - }; - - // remove our loading emote - 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; - if stats.should_track() { - stats.compilation(&result.0.language, !is_success).await; + }; + + // Try to load in an attachment + let (code, ext) = parser::get_message_attachment(&msg.attachments).await?; + if !code.is_empty() { + // content.push_str(&format!("\n```{}\n{}\n```\n", ext, code)); + writeln!(content, "\n```{}\n{}\n```", ext, code).unwrap(); + } + + // 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?; + + // send out loading emote + 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?\n{}", + )); + } + + // dispatch our req + 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) => { + // 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))); } - - let data = ctx.data.read().await; - let config = data.get::().unwrap(); - let config_lock = config.read().await; - - 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 emb = embeds::build_complog_embed( - is_success, - &parse_result.code, - &parse_result.target, - &msg.author.tag(), - msg.author.id.0, - &guild, - ); - discordhelpers::manual_dispatch(ctx.http.clone(), id, emb).await; - } + }; + + // remove our loading emote + 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; + if stats.should_track() { + stats.compilation(&result.0.language, !is_success).await; + } + + let data = ctx.data.read().await; + let config = data.get::().unwrap(); + let config_lock = config.read().await; + + 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 emb = embeds::build_complog_embed( + is_success, + &parse_result.code, + &parse_result.target, + &msg.author.tag(), + msg.author.id.0, + &guild, + ); + discordhelpers::manual_dispatch(ctx.http.clone(), id, emb).await; } + } - Ok(result.1) + Ok(result.1) } diff --git a/src/commands/compilers.rs b/src/commands/compilers.rs index 55bf3e7..0d28ac0 100644 --- a/src/commands/compilers.rs +++ b/src/commands/compilers.rs @@ -10,89 +10,83 @@ use crate::utls::parser::shortname_to_qualified; #[command] pub async fn compilers(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - // grab language arg - let user_lang = match _args.parse::() { - Ok(s) => s, - Err(_e) => { - return Err(CommandError::from( - "No language specified!\nPlease try giving me a language to search", - )); - } - }; - - let data_read = ctx.data.read().await; - let compiler_cache = data_read.get::().unwrap(); - let compiler_manager = compiler_cache.read().await; - if compiler_manager.gbolt.is_none() { - return Err(CommandError::from( - "Compiler Explorer service is currently down, please try again later.", - )); + // grab language arg + let user_lang = match _args.parse::() { + Ok(s) => s, + Err(_e) => { + return Err(CommandError::from( + "No language specified!\nPlease try giving me a language to search", + )); } + }; + + let data_read = ctx.data.read().await; + let compiler_cache = data_read.get::().unwrap(); + let compiler_manager = compiler_cache.read().await; + if compiler_manager.gbolt.is_none() { + return Err(CommandError::from( + "Compiler Explorer service is currently down, please try again later.", + )); + } - // Get our list of compilers - let mut langs: Vec = Vec::new(); + // Get our list of compilers + let mut langs: Vec = Vec::new(); - let lower_lang = user_lang.to_lowercase(); - let language = shortname_to_qualified(&lower_lang); - match compiler_manager.resolve_target(language) { - RequestHandler::CompilerExplorer => { - for cache_entry in &compiler_manager.gbolt.as_ref().unwrap().cache { - if cache_entry.language.id == language { - for compiler in &cache_entry.compilers { - langs.push(format!("{} -> **{}**", &compiler.name, &compiler.id)); - } - } - } + let lower_lang = user_lang.to_lowercase(); + let language = shortname_to_qualified(&lower_lang); + match compiler_manager.resolve_target(language) { + RequestHandler::CompilerExplorer => { + for cache_entry in &compiler_manager.gbolt.as_ref().unwrap().cache { + if cache_entry.language.id == language { + for compiler in &cache_entry.compilers { + langs.push(format!("{} -> **{}**", &compiler.name, &compiler.id)); + } } - RequestHandler::WandBox => { - match compiler_manager - .wbox - .as_ref() - .unwrap() - .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 - ))); - } - }; + } + } + RequestHandler::WandBox => { + match compiler_manager.wbox.as_ref().unwrap().get_compilers(shortname_to_qualified(language)) + { + Some(s) => { + for lang in s { + langs.push(lang.name); + } } - RequestHandler::None => { - return Err(CommandError::from(format!( - "Unable to find compilers for target '{}'.", - language - ))); + None => { + 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 + ))); } + } - let avatar = { - let data_read = ctx.data.read().await; - let botinfo_lock = data_read - .get::() - .expect("Expected BotInfo in global cache") - .clone(); - let botinfo = botinfo_lock.read().await; - botinfo.get("BOT_AVATAR").unwrap().clone() - }; + let avatar = { + let data_read = ctx.data.read().await; + let botinfo_lock = + data_read.get::().expect("Expected BotInfo in global cache").clone(); + let botinfo = botinfo_lock.read().await; + botinfo.get("BOT_AVATAR").unwrap().clone() + }; - let pages = discordhelpers::build_menu_items( - langs, - 15, - "Supported Compilers", - &avatar, - &msg.author.tag(), - "", - ); - let mut menu = Menu::new(ctx, msg, &pages); - menu.run().await?; + let pages = discordhelpers::build_menu_items( + langs, + 15, + "Supported Compilers", + &avatar, + &msg.author.tag(), + "", + ); + let mut menu = Menu::new(ctx, msg, &pages); + menu.run().await?; - debug!("Command executed"); - Ok(()) + debug!("Command executed"); + Ok(()) } diff --git a/src/commands/cpp.rs b/src/commands/cpp.rs index a498230..6e1b202 100644 --- a/src/commands/cpp.rs +++ b/src/commands/cpp.rs @@ -1,106 +1,96 @@ use serenity::{ - builder::CreateEmbed, - framework::standard::{macros::command, Args, CommandError, CommandResult}, - model::prelude::*, - prelude::*, + builder::CreateEmbed, + framework::standard::{macros::command, Args, CommandError, CommandResult}, + model::prelude::*, + prelude::*, }; use crate::utls::discordhelpers::embeds::EmbedOptions; use crate::{ - cache::{CompilerCache, ConfigCache, MessageCache, MessageCacheEntry}, - cppeval::eval::CppEval, - utls::discordhelpers, - utls::discordhelpers::embeds, - utls::discordhelpers::embeds::ToEmbed, - utls::parser::ParserResult, + cache::{CompilerCache, ConfigCache, MessageCache, MessageCacheEntry}, + cppeval::eval::CppEval, + utls::discordhelpers, + utls::discordhelpers::embeds, + utls::discordhelpers::embeds::ToEmbed, + utls::parser::ParserResult, }; #[command] #[aliases("c++")] #[bucket = "nospam"] pub async fn cpp(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let emb = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; - let mut emb_msg = embeds::embed_message(emb); + let emb = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; + let mut emb_msg = embeds::embed_message(emb); - // Dispatch our request - let compilation_embed = msg - .channel_id - .send_message(&ctx.http, |_| &mut emb_msg) - .await?; + // Dispatch our request + let compilation_embed = msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await?; - // 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()), - ); + // 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())); - Ok(()) + Ok(()) } pub async fn handle_request( - ctx: Context, - content: String, - author: User, - msg: &Message, + 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(); - discordhelpers::build_reaction(loading_id.parse::()?, &loading_name) - } else { - ReactionType::Unicode(String::from("โณ")) - } - }; - - let start = content.find(' '); - if start.is_none() { - return Err(CommandError::from("Invalid usage. View `;help cpp`")); + 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(); + discordhelpers::build_reaction(loading_id.parse::()?, &loading_name) + } else { + ReactionType::Unicode(String::from("โณ")) } + }; - let mut eval = CppEval::new(content.split_at(start.unwrap()).1); - let out = eval.evaluate()?; + let start = content.find(' '); + if start.is_none() { + return Err(CommandError::from("Invalid usage. View `;help cpp`")); + } - // send out loading emote - 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?\n{}")); - } + let mut eval = CppEval::new(content.split_at(start.unwrap()).1); + let out = eval.evaluate()?; - let fake_parse = ParserResult { - url: "".to_string(), - stdin: "".to_string(), - target: "g101".to_string(), - code: out, - options: vec![String::from("-O2"), String::from("-std=gnu++2a")], - args: vec![], - }; + // send out loading emote + 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?\n{}", + )); + } - let data_read = ctx.data.read().await; - let compiler_lock = data_read.get::().unwrap().read().await; - let result = match compiler_lock.compiler_explorer(&fake_parse).await { - Ok(r) => r, - Err(e) => { - // 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?; + let fake_parse = ParserResult { + url: "".to_string(), + stdin: "".to_string(), + target: "g101".to_string(), + code: out, + options: vec![String::from("-O2"), String::from("-std=gnu++2a")], + args: vec![], + }; + + let data_read = ctx.data.read().await; + let compiler_lock = data_read.get::().unwrap().read().await; + let result = match compiler_lock.compiler_explorer(&fake_parse).await { + Ok(r) => r, + Err(e) => { + // 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?; - let options = EmbedOptions::new(false, fake_parse.target.clone(), String::default()); + // remove our loading emote + discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await?; + let options = EmbedOptions::new(false, fake_parse.target.clone(), String::default()); - Ok(result.1.to_embed(&author, &options)) + Ok(result.1.to_embed(&author, &options)) } diff --git a/src/commands/format.rs b/src/commands/format.rs index d6ffb24..c2b5dbf 100644 --- a/src/commands/format.rs +++ b/src/commands/format.rs @@ -2,7 +2,7 @@ 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, + macros::command, Args, CommandError, CommandResult, Delimiter, }; use serenity::model::prelude::*; use serenity::prelude::*; @@ -10,135 +10,124 @@ use std::io::Write; #[command] pub async fn format(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { - let mut fmt = String::from("clangformat"); - let mut style = String::from("google"); - if !args.is_empty() { - // do not include ``` codeblocks into arg parsing.. lets just substr and replace args - let idx = msg.content.find('`'); - if let Some(idx) = idx { - let substr: String = msg.content.chars().take(idx).collect(); - args = Args::new(&substr, &[Delimiter::Single(' ')]); - args.advance(); - } + let mut fmt = String::from("clangformat"); + let mut style = String::from("google"); + if !args.is_empty() { + // do not include ``` codeblocks into arg parsing.. lets just substr and replace args + let idx = msg.content.find('`'); + if let Some(idx) = idx { + let substr: String = msg.content.chars().take(idx).collect(); + args = Args::new(&substr, &[Delimiter::Single(' ')]); + args.advance(); + } - // kind of odd - but since we replaced args we try again... - if !args.is_empty() { - fmt = args.single::()?.trim().to_owned(); + // kind of odd - but since we replaced args we try again... + if !args.is_empty() { + fmt = args.single::()?.trim().to_owned(); - style = String::from(""); - if !args.is_empty() { - style = args.single::()?.trim().to_owned(); - } - } + style = String::from(""); + if !args.is_empty() { + style = args.single::()?.trim().to_owned(); + } } + } - let data = ctx.data.read().await; - let comp_mgr = data.get::().unwrap().read().await; - if comp_mgr.gbolt.is_none() { - return Err(CommandError::from( - "Compiler Explorer service is currently down, please try again later.", - )); - } + let data = ctx.data.read().await; + let comp_mgr = data.get::().unwrap().read().await; + if comp_mgr.gbolt.is_none() { + return Err(CommandError::from( + "Compiler Explorer service is currently down, please try again later.", + )); + } - let gbolt = comp_mgr.gbolt.as_ref().unwrap(); + let gbolt = comp_mgr.gbolt.as_ref().unwrap(); - // validate user input - for format in &gbolt.formats { - 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(); + // validate user input + for format in &gbolt.formats { + 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 - for fmtstyle in &format.styles { - if fmtstyle.to_ascii_lowercase().contains(&style) { - style = fmtstyle.to_string(); - } - } - } + // 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 + for fmtstyle in &format.styles { + if fmtstyle.to_ascii_lowercase().contains(&style) { + style = fmtstyle.to_string(); + } } + } } + } - let mut lang_code = String::new(); - let mut attachment_name = String::new(); - let code; + let mut lang_code = String::new(); + let mut attachment_name = String::new(); + let code; - 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? { - lang_code = result.target.clone(); - code = result.code - } 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 { - 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; + 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? { + lang_code = result.target.clone(); + code = result.code + } 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", - )); - } else { - answer = res.answer; - } - } - Err(err) => { - return Err(CommandError::from(format!( - "An error occurred while formatting code: `{}`", - err - ))); - } + 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")); + } else { + answer = res.answer; } + } + Err(err) => { + return Err(CommandError::from(format!( + "An error occurred while formatting code: `{}`", + err + ))); + } } + } - if !attachment_name.is_empty() { - let _ = std::fs::create_dir("temp"); - let path = format!("temp/{}", attachment_name); - let mut file = std::fs::File::create(&path)?; - let _ = file.write_all(answer.as_bytes()); - let _ = file.flush(); + if !attachment_name.is_empty() { + let _ = std::fs::create_dir("temp"); + let path = format!("temp/{}", attachment_name); + let mut file = std::fs::File::create(&path)?; + 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 MS Azure") - }) - .await?; - let _ = std::fs::remove_file(&path); - } else { - msg.reply( - &ctx.http, - format!("\n```{}\n{}```\n*Powered by MS Azure*", lang_code, answer), - ) - .await?; - } - Ok(()) + msg + .channel_id + .send_message(&ctx.http, |msg| msg.add_file(path.as_str()).content("Powered by MS Azure")) + .await?; + let _ = std::fs::remove_file(&path); + } else { + msg + .reply(&ctx.http, format!("\n```{}\n{}```\n*Powered by MS Azure*", lang_code, answer)) + .await?; + } + Ok(()) } diff --git a/src/commands/formats.rs b/src/commands/formats.rs index a0f908b..6a40e16 100644 --- a/src/commands/formats.rs +++ b/src/commands/formats.rs @@ -11,46 +11,41 @@ use std::fmt::Write as _; #[command] pub async fn formats(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let data = ctx.data.read().await; - let prefix = { - let botinfo_lock = data - .get::() - .expect("Expected BotInfo in global cache") - .clone(); - let botinfo = botinfo_lock.read().await; - botinfo.get("BOT_PREFIX").unwrap().clone() - }; + let data = ctx.data.read().await; + let prefix = { + let botinfo_lock = data.get::().expect("Expected BotInfo in global cache").clone(); + let botinfo = botinfo_lock.read().await; + botinfo.get("BOT_PREFIX").unwrap().clone() + }; - let compiler_manager = data.get::().unwrap().read().await; - if compiler_manager.gbolt.is_none() { - return Err(CommandError::from( - "Compiler Explorer service is currently down, please try again later.", - )); - } + let compiler_manager = data.get::().unwrap().read().await; + if compiler_manager.gbolt.is_none() { + return Err(CommandError::from( + "Compiler Explorer service is currently down, please try again later.", + )); + } - let mut emb = CreateEmbed::default(); - emb.thumbnail(ICON_HELP); - emb.color(COLOR_OKAY); - emb.title("Formatters:"); - emb.description(format!("Below is the list of all formatters currently supported, an valid example request can be `{}format rust`, or `{}format clang mozilla`", prefix, prefix)); - for format in &compiler_manager.gbolt.as_ref().unwrap().formats { - let mut output = String::new(); - output.push_str("Styles:\n"); - if format.styles.is_empty() { - // output.push_str(" *(None)*\n"); - writeln!(output, " *(None)*").unwrap(); - } - for style in &format.styles { - // output.push_str(&format!(" *- {}*\n", style)); - writeln!(output, " *- {}*", style).unwrap(); - } - emb.field(&format.format_type, &output, false); + let mut emb = CreateEmbed::default(); + emb.thumbnail(ICON_HELP); + emb.color(COLOR_OKAY); + emb.title("Formatters:"); + emb.description(format!("Below is the list of all formatters currently supported, an valid example request can be `{}format rust`, or `{}format clang mozilla`", prefix, prefix)); + for format in &compiler_manager.gbolt.as_ref().unwrap().formats { + let mut output = String::new(); + output.push_str("Styles:\n"); + if format.styles.is_empty() { + // output.push_str(" *(None)*\n"); + writeln!(output, " *(None)*").unwrap(); + } + for style in &format.styles { + // output.push_str(&format!(" *- {}*\n", style)); + writeln!(output, " *- {}*", style).unwrap(); } + emb.field(&format.format_type, &output, false); + } - let mut emb_msg = embeds::embed_message(emb); - msg.channel_id - .send_message(&ctx.http, |_| &mut emb_msg) - .await?; + let mut emb_msg = embeds::embed_message(emb); + msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await?; - return Ok(()); + return Ok(()); } diff --git a/src/commands/help.rs b/src/commands/help.rs index 3148b9a..2d11655 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -1,10 +1,10 @@ use std::env; use serenity::{ - builder::CreateEmbed, - framework::standard::{macros::command, Args, CommandResult}, - model::prelude::*, - prelude::*, + builder::CreateEmbed, + framework::standard::{macros::command, Args, CommandResult}, + model::prelude::*, + prelude::*, }; use crate::utls::constants::*; @@ -12,125 +12,123 @@ use crate::utls::discordhelpers::embeds; #[command] pub async fn help(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let prefix = env::var("BOT_PREFIX").expect("Bot prefix is not set!"); - if !args.is_empty() { - let cmd = args.parse::().unwrap(); - let mut emb = CreateEmbed::default(); - emb.thumbnail(ICON_HELP); - emb.color(COLOR_OKAY); + let prefix = env::var("BOT_PREFIX").expect("Bot prefix is not set!"); + if !args.is_empty() { + let cmd = args.parse::().unwrap(); + let mut emb = CreateEmbed::default(); + emb.thumbnail(ICON_HELP); + emb.color(COLOR_OKAY); - let unknown = format!("Unknown command '{}'", cmd); - let description = match cmd.as_str() { - "help" => "Do you like recursion or something?", - "invite" => { - emb.title("Invite command"); - emb.field("Example", format!("{}invite", prefix), false); - "Grabs the bot's invite link\n\n" - } - "compile" => { - emb.title("Compile command"); - emb.field( - "Example", - format!( - "{}compile c++\n\ + let unknown = format!("Unknown command '{}'", cmd); + let description = match cmd.as_str() { + "help" => "Do you like recursion or something?", + "invite" => { + emb.title("Invite command"); + emb.field("Example", format!("{}invite", prefix), false); + "Grabs the bot's invite link\n\n" + } + "compile" => { + emb.title("Compile command"); + emb.field( + "Example", + format!( + "{}compile c++\n\ \\`\\`\\`\n\ #include \n\n\ int main() {{ \n\ \tstd::cout << \"Hello, world\";\n\ }}\n\ \\`\\`\\`\n", - prefix - ), - false, - ); - "Sends a compilation request\n\n" - } - "compilers" => { - emb.title("Compilers command"); - emb.field("Example", format!("{}compilers ", prefix), false); - "Lists all compilers supported for a given language" - } + prefix + ), + false, + ); + "Sends a compilation request\n\n" + } + "compilers" => { + emb.title("Compilers command"); + emb.field("Example", format!("{}compilers ", prefix), false); + "Lists all compilers supported for a given language" + } - "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 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" - } + "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 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" + } - "languages" => { - emb.title("Languages command"); - emb.field("Example", format!("{}languages", prefix), false); - "Lists all languages supported" - } - "asm" => { - emb.title("Assembly command"); - emb.field( - "Example", - format!( - "{}asm c++\n\ + "languages" => { + emb.title("Languages command"); + emb.field("Example", format!("{}languages", prefix), false); + "Lists all languages supported" + } + "asm" => { + emb.title("Assembly command"); + emb.field( + "Example", + format!( + "{}asm c++\n\ \\`\\`\\`\n\ #include \n\n\ int main() {{ \n\ \tstd::cout << \"Hello, world\";\n\ }}\n\ \\`\\`\\`\n", - prefix - ), - false, - ); - "Sends an assembly request, displaying the assembly output\n\n" - } - "botinfo" => { - emb.title("Bot info command"); - emb.field("Example", format!("{}botinfo", prefix), false); - "Outputs information about the bot" - } - "format" => { - emb.title("Format command"); - emb.field("Example", format!("{}format clang Google", prefix), false); - emb.field("Example", format!("{}format clang Mozilla", prefix), false); - emb.field("Example", format!("{}format rustfmt", prefix), false); - "Formats the input code with the formatter specified. Defaults to clang-format Google\n\n*(see .formats command for all formats)*\n\n" - } - _ => { - emb.title("Command not found"); - emb.color(COLOR_FAIL); - emb.thumbnail(ICON_FAIL); - unknown.as_str() - } - }; + prefix + ), + false, + ); + "Sends an assembly request, displaying the assembly output\n\n" + } + "botinfo" => { + emb.title("Bot info command"); + emb.field("Example", format!("{}botinfo", prefix), false); + "Outputs information about the bot" + } + "format" => { + emb.title("Format command"); + emb.field("Example", format!("{}format clang Google", prefix), false); + emb.field("Example", format!("{}format clang Mozilla", prefix), false); + emb.field("Example", format!("{}format rustfmt", prefix), false); + "Formats the input code with the formatter specified. Defaults to clang-format Google\n\n*(see .formats command for all formats)*\n\n" + } + _ => { + emb.title("Command not found"); + emb.color(COLOR_FAIL); + emb.thumbnail(ICON_FAIL); + unknown.as_str() + } + }; - emb.description(description); + emb.description(description); - let mut emb_msg = embeds::embed_message(emb); - msg.channel_id - .send_message(&ctx.http, |_| &mut emb_msg) - .await?; + let mut emb_msg = embeds::embed_message(emb); + msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await?; - return Ok(()); - } + return Ok(()); + } - let prefix = env::var("BOT_PREFIX").expect("Prefix has not been set!"); - msg.channel_id.send_message(&ctx.http, |m| { + let prefix = env::var("BOT_PREFIX").expect("Prefix has not been set!"); + msg.channel_id.send_message(&ctx.http, |m| { m.embed(|e| { e.thumbnail(ICON_HELP); e.description(format!("For help with a specific command, type `{}help `\n\nStruggling? Check out [our wiki](https://github.com/ThomasByr/discord-compiler-bot/wiki)", prefix)); @@ -149,6 +147,6 @@ pub async fn help(ctx: &Context, msg: &Message, args: Args) -> CommandResult { }) }).await?; - debug!("Command executed"); - Ok(()) + debug!("Command executed"); + Ok(()) } diff --git a/src/commands/invite.rs b/src/commands/invite.rs index e4f600b..e8e4df2 100644 --- a/src/commands/invite.rs +++ b/src/commands/invite.rs @@ -8,14 +8,12 @@ use crate::utls::discordhelpers::embeds; #[command] pub async fn invite(ctx: &Context, msg: &Message, _: Args) -> CommandResult { - let invite = env::var("INVITE_LINK").expect("Expected invite link envvar"); + let invite = env::var("INVITE_LINK").expect("Expected invite link envvar"); - let emb = embeds::build_invite_embed(&invite); + 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?; + let mut emb_msg = embeds::embed_message(emb); + msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await?; - Ok(()) + Ok(()) } diff --git a/src/commands/languages.rs b/src/commands/languages.rs index 0d072e7..28fad4b 100644 --- a/src/commands/languages.rs +++ b/src/commands/languages.rs @@ -8,49 +8,47 @@ use crate::utls::discordhelpers::menu::Menu; #[command] pub async fn languages(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let data_read = ctx.data.read().await; - let compiler_cache = data_read.get::().unwrap(); - let compiler_manager = compiler_cache.read().await; + let data_read = ctx.data.read().await; + let compiler_cache = data_read.get::().unwrap(); + let compiler_manager = compiler_cache.read().await; - let mut items = Vec::new(); - if let Some(gbolt) = compiler_manager.gbolt.as_ref() { - for cache_entry in &gbolt.cache { - items.push(format!("{}*", cache_entry.language.id)); - } + let mut items = Vec::new(); + if let Some(gbolt) = compiler_manager.gbolt.as_ref() { + for cache_entry in &gbolt.cache { + items.push(format!("{}*", cache_entry.language.id)); } - if let Some(wbox) = &compiler_manager.wbox { - let langs = wbox.get_languages(); - for lang in langs { - if !items.contains(&lang.name) && !items.contains(&format!("{}*", &lang.name)) { - items.push(lang.name); - } - } + } + if let Some(wbox) = &compiler_manager.wbox { + let langs = wbox.get_languages(); + for lang in langs { + if !items.contains(&lang.name) && !items.contains(&format!("{}*", &lang.name)) { + items.push(lang.name); + } } + } - let avatar = { - let data_read = ctx.data.read().await; - let botinfo_lock = data_read - .get::() - .expect("Expected BotInfo in global cache") - .clone(); - let botinfo = botinfo_lock.read().await; - botinfo.get("BOT_AVATAR").unwrap().clone() - }; + let avatar = { + let data_read = ctx.data.read().await; + let botinfo_lock = + data_read.get::().expect("Expected BotInfo in global cache").clone(); + let botinfo = botinfo_lock.read().await; + botinfo.get("BOT_AVATAR").unwrap().clone() + }; - let mut items_vec: Vec = items.into_iter().collect(); - items_vec.sort(); + let mut items_vec: Vec = items.into_iter().collect(); + items_vec.sort(); - let pages = discordhelpers::build_menu_items( - items_vec, - 15, - "Supported Languages", - &avatar, - &msg.author.tag(), - "*\\* = supports assembly output*", - ); - let mut menu = Menu::new(ctx, msg, &pages); - menu.run().await?; + let pages = discordhelpers::build_menu_items( + items_vec, + 15, + "Supported Languages", + &avatar, + &msg.author.tag(), + "*\\* = supports assembly output*", + ); + let mut menu = Menu::new(ctx, msg, &pages); + menu.run().await?; - debug!("Command executed"); - Ok(()) + debug!("Command executed"); + Ok(()) } diff --git a/src/commands/ping.rs b/src/commands/ping.rs index 9a958e9..3ff1bec 100644 --- a/src/commands/ping.rs +++ b/src/commands/ping.rs @@ -6,14 +6,11 @@ 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(); + 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?; - debug!("Command executed"); - Ok(()) + 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 ca1ed96..e57b86e 100644 --- a/src/commands/unblock.rs +++ b/src/commands/unblock.rs @@ -7,19 +7,17 @@ use crate::cache::BlocklistCache; #[command] #[owners_only] pub async fn unblock(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - if args.len() != 1 { - return Err(CommandError::from("Supply an id to unblock")); - } + if args.len() != 1 { + return Err(CommandError::from("Supply an id to unblock")); + } - let arg = args.parse::()?; + let arg = args.parse::()?; - let data = ctx.data.read().await; - let mut blocklist = data.get::().unwrap().write().await; + let data = ctx.data.read().await; + let mut blocklist = data.get::().unwrap().write().await; - blocklist.unblock(arg); + blocklist.unblock(arg); - msg.channel_id - .say(&ctx.http, format!("Unblocked snowflake `{}`", &arg)) - .await?; - Ok(()) + 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 b42c37e..20d2a99 100644 --- a/src/cppeval/eval.rs +++ b/src/cppeval/eval.rs @@ -3,187 +3,180 @@ use std::fmt::Write as _; #[derive(Debug, Clone)] pub struct EvalError { - details: String, + details: String, } impl EvalError { - fn new(msg: &str) -> EvalError { - EvalError { - details: msg.to_string(), - } - } + fn new(msg: &str) -> EvalError { + EvalError { details: msg.to_string() } + } } impl fmt::Display for EvalError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.details) - } + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.details) + } } impl std::error::Error for EvalError { - fn description(&self) -> &str { - &self.details - } + fn description(&self) -> &str { + &self.details + } } pub struct CppEval { - input: String, - output: String, + input: String, + output: String, } impl CppEval { - pub fn new(input: &str) -> CppEval { - CppEval { - input: input.trim().to_owned(), - output: String::default(), - } + pub fn new(input: &str) -> CppEval { + CppEval { input: input.trim().to_owned(), output: String::default() } + } + + 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 = self.input.trim().to_string(); } - 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 = self.input.trim().to_string(); - } - - // add bits we need for every request - self.add_headers(); - - if self.input.starts_with('{') { - // parsing a statement here - self.do_statements()?; - } else if self.input.starts_with("<<") { - // just outputting - self.do_prints(); - } else { - // they're handling their own main - self.do_user_handled()?; - } - - Ok(self.output.clone()) + // add bits we need for every request + self.add_headers(); + + if self.input.starts_with('{') { + // parsing a statement here + self.do_statements()?; + } else if self.input.starts_with("<<") { + // just outputting + self.do_prints(); + } else { + // they're handling their own main + self.do_user_handled()?; } - 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(); - let rest = self.input.replacen(&main, "", 1).trim().to_string(); - - // self.output.push_str(&format!("{}\n", rest)); - writeln!(self.output, "{}", rest).unwrap(); - // self.output.push_str(&format!("{}\n", main)); - writeln!(self.output, "{}", main).unwrap(); - } else { - return Err(EvalError::new("No main() specified. Invalid request")); - } - - Ok(()) + Ok(self.output.clone()) + } + + 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(); + let rest = self.input.replacen(&main, "", 1).trim().to_string(); + + // self.output.push_str(&format!("{}\n", rest)); + writeln!(self.output, "{}", rest).unwrap(); + // self.output.push_str(&format!("{}\n", main)); + writeln!(self.output, "{}", main).unwrap(); + } else { + return Err(EvalError::new("No main() specified. Invalid request")); } - 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.", - )); - } - - self.do_rest(end + 1); + Ok(()) + } - let statements = self.input[1..end].to_owned(); - self.build_main(&statements); - - Ok(()) + 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.")); } - fn get_statement_end(&self) -> usize { - let mut balance = 0; - let mut stop_idx = 0; - let mut ignore = false; - let mut inline_comment = false; - let mut multiline_comment = false; - let mut last = '\0'; - for (index, char) in self.input.chars().enumerate() { - // prevent non-syntactic }'s from messing up our balance - if (char == '\'' || char == '"') && last != '\\' { - ignore = !ignore; - } - if ignore && last != '\\' { - last = char; - continue; - } - - if char == '/' && last == '/' { - inline_comment = true; - } - - if inline_comment { - if char == '\n' { - inline_comment = false; - } - last = char; - continue; - } - - /* awd */ - if char == '*' && last == '/' { - multiline_comment = true; - } - - if multiline_comment { - if char == '/' && last == '*' { - multiline_comment = false; - } - last = char; - continue; - } - - // balance our braces - if char == '{' { - balance += 1; - } - if char == '}' { - balance -= 1; - } - if balance == 0 { - stop_idx = index; - break; - } - last = char; + self.do_rest(end + 1); + + let statements = self.input[1..end].to_owned(); + self.build_main(&statements); + + Ok(()) + } + + fn get_statement_end(&self) -> usize { + let mut balance = 0; + let mut stop_idx = 0; + let mut ignore = false; + let mut inline_comment = false; + let mut multiline_comment = false; + let mut last = '\0'; + for (index, char) in self.input.chars().enumerate() { + // prevent non-syntactic }'s from messing up our balance + if (char == '\'' || char == '"') && last != '\\' { + ignore = !ignore; + } + if ignore && last != '\\' { + last = char; + continue; + } + + if char == '/' && last == '/' { + inline_comment = true; + } + + if inline_comment { + if char == '\n' { + inline_comment = false; } - stop_idx - } - - 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); - - self.input[..statement_end].to_owned() - } else { - self.input.clone() - }; - self.build_main(&format!("cout {};", input)); - } - - 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)); - writeln!(self.output, "\nint main (void) {{\n{}\n}}", statements).unwrap(); - } - - /* 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"); + last = char; + continue; + } + + /* awd */ + if char == '*' && last == '/' { + multiline_comment = true; + } + + if multiline_comment { + if char == '/' && last == '*' { + multiline_comment = false; } - */ + last = char; + continue; + } + + // balance our braces + if char == '{' { + balance += 1; + } + if char == '}' { + balance -= 1; + } + if balance == 0 { + stop_idx = index; + break; + } + last = char; + } + stop_idx + } + + 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); + + self.input[..statement_end].to_owned() + } else { + self.input.clone() + }; + self.build_main(&format!("cout {};", input)); + } + + 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)); + writeln!(self.output, "\nint main (void) {{\n{}\n}}", statements).unwrap(); + } + + /* 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/events.rs b/src/events.rs index c6064cc..ebf85bc 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,15 +1,15 @@ use std::env; use serenity::{ - async_trait, - collector::CollectReaction, - framework::{standard::macros::hook, standard::CommandResult, standard::DispatchError}, - model::{ - channel::Message, channel::ReactionType, event::MessageUpdateEvent, gateway::Ready, - guild::Guild, id::ChannelId, id::GuildId, id::MessageId, interactions::Interaction, - prelude::UnavailableGuild, - }, - prelude::*, + async_trait, + collector::CollectReaction, + framework::{standard::macros::hook, standard::CommandResult, standard::DispatchError}, + model::{ + channel::Message, channel::ReactionType, event::MessageUpdateEvent, gateway::Ready, + guild::Guild, id::ChannelId, id::GuildId, id::MessageId, interactions::Interaction, + prelude::UnavailableGuild, + }, + prelude::*, }; use tokio::sync::MutexGuard; @@ -17,409 +17,371 @@ use tokio::sync::MutexGuard; use chrono::{DateTime, Utc}; use crate::{ - cache::*, - commands::compile::handle_request, - managers::compilation::RequestHandler, - managers::stats::StatsManager, - utls::{ - discordhelpers, - discordhelpers::embeds, - discordhelpers::interactions::send_error_msg, - parser::{get_message_attachment, shortname_to_qualified}, - }, + cache::*, + commands::compile::handle_request, + managers::compilation::RequestHandler, + managers::stats::StatsManager, + utls::{ + discordhelpers, + discordhelpers::embeds, + discordhelpers::interactions::send_error_msg, + 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>); + async fn all_shards_ready(&self, ctx: &Context, stats: &mut MutexGuard<'_, StatsManager>); } #[async_trait] impl ShardsReadyHandler for Handler { - async fn all_shards_ready(&self, ctx: &Context, stats: &mut MutexGuard<'_, StatsManager>) { - let data = ctx.data.read().await; + async fn all_shards_ready(&self, ctx: &Context, stats: &mut MutexGuard<'_, StatsManager>) { + let data = ctx.data.read().await; - let shard_manager = data.get::().unwrap().lock().await; - let guild_count = stats.get_boot_vec_sum(); + let shard_manager = data.get::().unwrap().lock().await; + let guild_count = stats.get_boot_vec_sum(); - stats.post_servers(guild_count).await; + stats.post_servers(guild_count).await; - discordhelpers::send_global_presence(&shard_manager, stats.server_count()).await; + discordhelpers::send_global_presence(&shard_manager, stats.server_count()).await; - info!("Ready in {} guilds", stats.server_count()); + info!("Ready in {} guilds", stats.server_count()); - // register commands globally in release - if !cfg!(debug_assertions) { - let mut cmd_mgr = data.get::().unwrap().write().await; - cmd_mgr.register_commands_global(ctx).await; - } + // register commands globally in release + if !cfg!(debug_assertions) { + let mut cmd_mgr = data.get::().unwrap().write().await; + cmd_mgr.register_commands_global(ctx).await; } + } } #[async_trait] impl EventHandler for Handler { - async fn guild_create(&self, ctx: Context, guild: Guild) { - let data = ctx.data.read().await; - - // in debug, we'll register on a guild-per-guild basis - if cfg!(debug_assertions) { - let mut cmd_mgr = data.get::().unwrap().write().await; - cmd_mgr.register_commands_guild(&ctx, &guild).await; - } - - let now: DateTime = Utc::now(); - if guild.joined_at.unix_timestamp() + 30 > now.timestamp() { - // post new server to join log - let id; - { - let info = data.get::().unwrap().read().await; - id = info.get("BOT_ID").unwrap().parse::().unwrap(); - - if let Some(log) = info.get("JOIN_LOG") { - if let Ok(id) = log.parse::() { - let emb = embeds::build_join_embed(&guild); - discordhelpers::manual_dispatch(ctx.http.clone(), id, emb).await; - } - } - } - - // publish/queue new server to stats - let mut stats = data.get::().unwrap().lock().await; - stats.new_server().await; - - // ensure we're actually loaded in before we start posting our server counts - if stats.server_count() > 0 { - let new_stats = dbl::types::ShardStats::Cumulative { - server_count: stats.server_count(), - shard_count: Some(stats.shard_count()), - }; - - if let Some(dbl_cache) = data.get::() { - let dbl = dbl_cache.read().await; - if let Err(e) = dbl.update_stats(id, new_stats).await { - warn!("Failed to post stats to dbl: {}", e); - } - } - - // update guild count in presence - let shard_manager = data.get::().unwrap().lock().await; - discordhelpers::send_global_presence(&shard_manager, stats.server_count()).await; - } - - info!("Joining {}", guild.name); + async fn guild_create(&self, ctx: Context, guild: Guild) { + let data = ctx.data.read().await; - 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; - } - } + // in debug, we'll register on a guild-per-guild basis + if cfg!(debug_assertions) { + let mut cmd_mgr = data.get::().unwrap().write().await; + cmd_mgr.register_commands_guild(&ctx, &guild).await; } - async fn guild_delete(&self, ctx: Context, incomplete: UnavailableGuild) { - let data = ctx.data.read().await; - - // post new server to join log + let now: DateTime = Utc::now(); + if guild.joined_at.unix_timestamp() + 30 > now.timestamp() { + // post new server to join log + let id; + { let info = data.get::().unwrap().read().await; - let id = info.get("BOT_ID").unwrap().parse::().unwrap(); // used later + id = info.get("BOT_ID").unwrap().parse::().unwrap(); + if let Some(log) = info.get("JOIN_LOG") { - if let Ok(join_id) = log.parse::() { - let emb = embeds::build_leave_embed(&incomplete.id); - discordhelpers::manual_dispatch(ctx.http.clone(), join_id, emb).await; - } + if let Ok(id) = log.parse::() { + let emb = embeds::build_join_embed(&guild); + discordhelpers::manual_dispatch(ctx.http.clone(), id, emb).await; + } + } + } + + // publish/queue new server to stats + let mut stats = data.get::().unwrap().lock().await; + stats.new_server().await; + + // ensure we're actually loaded in before we start posting our server counts + if stats.server_count() > 0 { + let new_stats = dbl::types::ShardStats::Cumulative { + server_count: stats.server_count(), + shard_count: Some(stats.shard_count()), + }; + + if let Some(dbl_cache) = data.get::() { + let dbl = dbl_cache.read().await; + if let Err(e) = dbl.update_stats(id, new_stats).await { + warn!("Failed to post stats to dbl: {}", e); + } } - // publish/queue new server to stats - let mut stats = data.get::().unwrap().lock().await; - stats.leave_server().await; + // update guild count in presence + let shard_manager = data.get::().unwrap().lock().await; + discordhelpers::send_global_presence(&shard_manager, stats.server_count()).await; + } - // ensure we're actually loaded in before we start posting our server counts - if stats.server_count() > 0 { - let new_stats = dbl::types::ShardStats::Cumulative { - server_count: stats.server_count(), - shard_count: Some(stats.shard_count()), - }; + info!("Joining {}", guild.name); - if let Some(dbl_cache) = data.get::() { - let dbl = dbl_cache.read().await; - if let Err(e) = dbl.update_stats(id, new_stats).await { - warn!("Failed to post stats to dbl: {}", e); - } - } + 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; + } + } + } - // update guild count in presence - let shard_manager = data.get::().unwrap().lock().await; - discordhelpers::send_global_presence(&shard_manager, stats.server_count()).await; - } + async fn guild_delete(&self, ctx: Context, incomplete: UnavailableGuild) { + let data = ctx.data.read().await; - info!("Leaving {}", &incomplete.id); + // post new server to join log + let info = data.get::().unwrap().read().await; + let id = info.get("BOT_ID").unwrap().parse::().unwrap(); // used later + if let Some(log) = info.get("JOIN_LOG") { + if let Ok(join_id) = log.parse::() { + let emb = embeds::build_leave_embed(&incomplete.id); + discordhelpers::manual_dispatch(ctx.http.clone(), join_id, emb).await; + } } - async fn message(&self, ctx: Context, new_message: Message) { - if !new_message.attachments.is_empty() { - if let Ok((code, language)) = get_message_attachment(&new_message.attachments).await { - let data = ctx.data.read().await; - let target = { - let cm = data.get::().unwrap().read().await; - cm.resolve_target(shortname_to_qualified(&language)) - }; - - 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(); - discordhelpers::build_reaction(id.parse::().unwrap(), &name) - } else { - ReactionType::Unicode(String::from("๐Ÿ’ป")) - } - }; - - 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; - let _ = new_message.delete_reactions(&ctx.http).await; - if collector.is_some() { - let prefix = - env::var("BOT_PREFIX").expect("Expected bot prefix in .env file"); - let emb = match handle_request( - ctx.clone(), - format!("{}compile\n```{}\n{}\n```", prefix, 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 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), - ); - } - return; - } - }; - let mut emb_msg = embeds::embed_message(emb); - emb_msg.reference_message(&new_message); - let _ = new_message - .channel_id - .send_message(&ctx.http, |_| &mut emb_msg) - .await; - } - } - } + // publish/queue new server to stats + let mut stats = data.get::().unwrap().lock().await; + stats.leave_server().await; + + // ensure we're actually loaded in before we start posting our server counts + if stats.server_count() > 0 { + let new_stats = dbl::types::ShardStats::Cumulative { + server_count: stats.server_count(), + shard_count: Some(stats.shard_count()), + }; + + if let Some(dbl_cache) = data.get::() { + let dbl = dbl_cache.read().await; + if let Err(e) = dbl.update_stats(id, new_stats).await { + warn!("Failed to post stats to dbl: {}", e); } + } + + // update guild count in presence + let shard_manager = data.get::().unwrap().lock().await; + discordhelpers::send_global_presence(&shard_manager, stats.server_count()).await; } - async fn message_delete( - &self, - ctx: Context, - _channel_id: ChannelId, - id: MessageId, - _guild_id: Option, - ) { + info!("Leaving {}", &incomplete.id); + } + + async fn message(&self, ctx: Context, new_message: Message) { + if !new_message.attachments.is_empty() { + if let Ok((code, language)) = get_message_attachment(&new_message.attachments).await { 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()) { - if msg.our_msg.delete(ctx.http).await.is_err() { - // ignore for now + let target = { + let cm = data.get::().unwrap().read().await; + cm.resolve_target(shortname_to_qualified(&language)) + }; + + 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(); + discordhelpers::build_reaction(id.parse::().unwrap(), &name) + } else { + ReactionType::Unicode(String::from("๐Ÿ’ป")) } - message_cache.remove(id.as_u64()); + }; + + 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; + let _ = new_message.delete_reactions(&ctx.http).await; + if collector.is_some() { + let prefix = env::var("BOT_PREFIX").expect("Expected bot prefix in .env file"); + let emb = match handle_request( + ctx.clone(), + format!("{}compile\n```{}\n{}\n```", prefix, 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 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)); + } + return; + } + }; + let mut emb_msg = embeds::embed_message(emb); + emb_msg.reference_message(&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, + ) { + 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()) { + if msg.our_msg.delete(ctx.http).await.is_err() { + // ignore for now + } + message_cache.remove(id.as_u64()); + } + } - async fn message_update(&self, ctx: Context, new_data: MessageUpdateEvent) { - let data = ctx.data.read().await; - 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; - } - } + async fn message_update(&self, ctx: Context, new_data: MessageUpdateEvent) { + let data = ctx.data.read().await; + 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; } + } } + } - async fn ready(&self, ctx: Context, ready: Ready) { - info!("[Shard {}] Ready", ctx.shard_id); - let data = ctx.data.read().await; - - { - let mut stats = data.get::().unwrap().lock().await; - // occasionally we can have a ready event fire well after execution - // this check prevents us from double calling all_shards_ready - let total_shards_to_spawn = ready.shard.unwrap()[1]; - if stats.shard_count() + 1 > total_shards_to_spawn { - info!("Skipping duplicate ready event..."); - return; - } - - let guild_count = ready.guilds.len() as u64; - stats.add_shard(guild_count); - - // insert avatar at first opportunity - if stats.shard_count() == 1 { - let mut info = data.get::().unwrap().write().await; - info.insert("BOT_AVATAR", ready.user.avatar_url().unwrap()); - } + async fn ready(&self, ctx: Context, ready: Ready) { + info!("[Shard {}] Ready", ctx.shard_id); + let data = ctx.data.read().await; - if stats.shard_count() == total_shards_to_spawn { - self.all_shards_ready(&ctx, &mut stats).await; - } - } + { + let mut stats = data.get::().unwrap().lock().await; + // occasionally we can have a ready event fire well after execution + // this check prevents us from double calling all_shards_ready + let total_shards_to_spawn = ready.shard.unwrap()[1]; + if stats.shard_count() + 1 > total_shards_to_spawn { + info!("Skipping duplicate ready event..."); + return; + } + + let guild_count = ready.guilds.len() as u64; + stats.add_shard(guild_count); + + // insert avatar at first opportunity + if stats.shard_count() == 1 { + let mut info = data.get::().unwrap().write().await; + info.insert("BOT_AVATAR", ready.user.avatar_url().unwrap()); + } + + if stats.shard_count() == total_shards_to_spawn { + self.all_shards_ready(&ctx, &mut stats).await; + } } - - async fn interaction_create(&self, ctx: Context, interaction: Interaction) { - if let Interaction::ApplicationCommand(command) = interaction { - let data_read = ctx.data.read().await; - let commands = data_read.get::().unwrap().read().await; - match commands.on_command(&ctx, &command).await { - Ok(_) => {} - Err(e) => { - // in order to respond to messages with errors, we'll first try to - // 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 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; - } - } - } + } + + async fn interaction_create(&self, ctx: Context, interaction: Interaction) { + if let Interaction::ApplicationCommand(command) = interaction { + let data_read = ctx.data.read().await; + let commands = data_read.get::().unwrap().read().await; + match commands.on_command(&ctx, &command).await { + Ok(_) => {} + Err(e) => { + // in order to respond to messages with errors, we'll first try to + // 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 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; + } } + } } + } } #[hook] pub async fn before(ctx: &Context, msg: &Message, _: &str) -> bool { - let data = ctx.data.read().await; - { - let stats = data.get::().unwrap().lock().await; - if stats.should_track() { - stats.post_request().await; - } - } - - // we'll go with 0 if we couldn't grab guild id - let mut guild_id = 0; - if let Some(id) = msg.guild_id { - guild_id = id.0; + let data = ctx.data.read().await; + { + let stats = data.get::().unwrap().lock().await; + if stats.should_track() { + stats.post_request().await; } - - // check user against our blocklist - { - let blocklist = data.get::().unwrap().read().await; - let author_blocklisted = blocklist.contains(msg.author.id.0); - 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. + } + + // we'll go with 0 if we couldn't grab guild id + let mut guild_id = 0; + if let Some(id) = msg.guild_id { + guild_id = id.0; + } + + // check user against our blocklist + { + let blocklist = data.get::().unwrap().read().await; + let author_blocklisted = blocklist.contains(msg.author.id.0); + 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. 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.", - ); - - let mut emb_msg = embeds::embed_message(emb); - 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 { - warn!("Blocked guild {}", guild_id); - } - } - return false; + ); + + let mut emb_msg = embeds::embed_message(emb); + 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 { + warn!("Blocked guild {}", guild_id); } + } + return false; } + } - true + true } #[hook] pub async fn after( - ctx: &Context, - msg: &Message, - command_name: &str, - command_result: CommandResult, + ctx: &Context, + msg: &Message, + command_name: &str, + command_result: CommandResult, ) { - let data = ctx.data.read().await; - - if let Err(e) = command_result { - let emb = embeds::build_fail_embed(&msg.author, &format!("{}", e)); - let mut emb_msg = embeds::embed_message(emb); - if let Ok(sent) = msg - .channel_id - .send_message(&ctx.http, |_| &mut emb_msg) - .await - { - let mut message_cache = data.get::().unwrap().lock().await; - message_cache.insert(msg.id.0, MessageCacheEntry::new(sent, msg.clone())); - } + let data = ctx.data.read().await; + + if let Err(e) = command_result { + let emb = embeds::build_fail_embed(&msg.author, &format!("{}", e)); + let mut emb_msg = embeds::embed_message(emb); + if let Ok(sent) = msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await { + let mut message_cache = data.get::().unwrap().lock().await; + message_cache.insert(msg.id.0, MessageCacheEntry::new(sent, msg.clone())); } + } - // push command executed to api - let stats = data.get::().unwrap().lock().await; - if stats.should_track() { - stats.command_executed(command_name, msg.guild_id).await; - } + // push command executed to api + let stats = data.get::().unwrap().lock().await; + if stats.should_track() { + stats.command_executed(command_name, msg.guild_id).await; + } } #[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 mut emb_msg = embeds::embed_message(emb); - if msg - .channel_id - .send_message(&ctx.http, |_| &mut emb_msg) - .await - .is_err() - { - panic!("Failed to send ratelimit message"); - } + if let DispatchError::Ratelimited(_) = error { + 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.send_message(&ctx.http, |_| &mut emb_msg).await.is_err() { + panic!("Failed to send ratelimit message"); } + } } diff --git a/src/main.rs b/src/main.rs index d11498d..53e041c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,8 +27,8 @@ extern crate pretty_env_logger; /** Command Registration **/ use crate::commands::{ - asm::*, block::*, botinfo::*, compile::*, compilers::*, cpp::*, format::*, formats::*, help::*, - invite::*, languages::*, ping::*, unblock::*, + asm::*, block::*, botinfo::*, compile::*, compilers::*, cpp::*, format::*, formats::*, help::*, + invite::*, languages::*, ping::*, unblock::*, }; use crate::utls::discordhelpers::embeds::panic_embed; @@ -36,115 +36,105 @@ use crate::utls::discordhelpers::manual_dispatch; #[group] #[commands( - botinfo, compile, languages, compilers, ping, help, asm, block, unblock, invite, cpp, formats, - format + botinfo, compile, languages, compilers, ping, help, asm, block, unblock, invite, cpp, formats, + format )] struct General; /** Spawn bot **/ #[tokio::main] async fn main() -> Result<(), Box> { - if let Err(e) = dotenv::dotenv() { - error!("Unable to find .env configuration file: {}", e); - } - - pretty_env_logger::init(); + if let Err(e) = dotenv::dotenv() { + error!("Unable to find .env configuration file: {}", e); + } - let token = env::var("BOT_TOKEN").expect("Expected bot token in .env file"); + pretty_env_logger::init(); - let http = Http::new(&token); - let (owners, bot_id) = match http.get_current_application_info().await { - Ok(info) => { - let mut owners = HashSet::new(); + let token = env::var("BOT_TOKEN").expect("Expected bot token in .env file"); - owners.insert(info.owner.id); + let http = Http::new(&token); + let (owners, bot_id) = match http.get_current_application_info().await { + Ok(info) => { + let mut owners = HashSet::new(); - if let Some(team) = info.team { - for member in &team.members { - owners.insert(member.user.id); - } - } + owners.insert(info.owner.id); - (owners, info.id) - } - 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"); - (HashSet::new(), serenity::model::id::ApplicationId(bot_id)) + if let Some(team) = info.team { + for member in &team.members { + owners.insert(member.user.id); } - }; - - info!( - "Registering owner(s): {}", - owners - .iter() - .map(|o| format!("{}", o.0)) - .collect::>() - .join(", ") - ); - - if cfg!(debug_assertions) { - warn!("Running bot in DEBUG mode..."); - } - - 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) - .configure(|c| c.owners(owners).prefix(&prefix)) - .group(&GENERAL_GROUP) - .bucket("nospam", |b| b.delay(3).time_span(10).limit(3)) - .await - .on_dispatch_error(events::dispatch_error); - - let intents = GatewayIntents::GUILDS - | GatewayIntents::MESSAGE_CONTENT - | GatewayIntents::GUILD_INTEGRATIONS - | GatewayIntents::GUILD_MESSAGE_REACTIONS - | GatewayIntents::GUILD_MESSAGES; - 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?; + } - if let Ok(plog) = env::var("PANIC_LOG") { - let default_panic = std::panic::take_hook(); - let http = client.cache_and_http.http.clone(); - - std::panic::set_hook(Box::new(move |info| { - let http = http.clone(); - if let Ok(plog_parse) = plog.parse::() { - let panic_str = info.to_string(); - tokio::spawn({ - async move { manual_dispatch(http, plog_parse, panic_embed(panic_str)).await } - }); - } else { - warn!("Unable to parse channel id64 from PANIC_LOG, is it valid?"); - } - default_panic(info); - })); + (owners, info.id) } - - let dbl = BotsListApi::new(); - if dbl.should_spawn() { - dbl.spawn(client.cache_and_http.http.clone(), client.data.clone()); - } - - if let Err(why) = client.start_autosharded().await { - error!("Client error: {:?}", why); + 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"); + (HashSet::new(), serenity::model::id::ApplicationId(bot_id)) } + }; + + info!( + "Registering owner(s): {}", + owners.iter().map(|o| format!("{}", o.0)).collect::>().join(", ") + ); + + if cfg!(debug_assertions) { + warn!("Running bot in DEBUG mode..."); + } + + 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) + .configure(|c| c.owners(owners).prefix(&prefix)) + .group(&GENERAL_GROUP) + .bucket("nospam", |b| b.delay(3).time_span(10).limit(3)) + .await + .on_dispatch_error(events::dispatch_error); + + let intents = GatewayIntents::GUILDS + | GatewayIntents::MESSAGE_CONTENT + | GatewayIntents::GUILD_INTEGRATIONS + | GatewayIntents::GUILD_MESSAGE_REACTIONS + | GatewayIntents::GUILD_MESSAGES; + let mut client = serenity::Client::builder(token, intents) + .framework(framework) + .event_handler(events::Handler) + .application_id(app_id.parse::().unwrap()) + .await?; - Ok(()) + cache::fill(client.data.clone(), &prefix, bot_id.0, client.shard_manager.clone()).await?; + + if let Ok(plog) = env::var("PANIC_LOG") { + let default_panic = std::panic::take_hook(); + let http = client.cache_and_http.http.clone(); + + std::panic::set_hook(Box::new(move |info| { + let http = http.clone(); + if let Ok(plog_parse) = plog.parse::() { + let panic_str = info.to_string(); + tokio::spawn({ + async move { manual_dispatch(http, plog_parse, panic_embed(panic_str)).await } + }); + } else { + warn!("Unable to parse channel id64 from PANIC_LOG, is it valid?"); + } + default_panic(info); + })); + } + + let dbl = BotsListApi::new(); + if dbl.should_spawn() { + dbl.spawn(client.cache_and_http.http.clone(), client.data.clone()); + } + + if let Err(why) = client.start_autosharded().await { + error!("Client error: {:?}", why); + } + + Ok(()) } diff --git a/src/managers/command.rs b/src/managers/command.rs index 8a27e1d..baa7699 100644 --- a/src/managers/command.rs +++ b/src/managers/command.rs @@ -2,199 +2,175 @@ use crate::cache::StatsManagerCache; use crate::slashcmds; use serenity::{ - builder::CreateApplicationCommand, - client::Context, - framework::standard::CommandResult, - model::{ - guild::Guild, interactions::application_command::ApplicationCommand, - interactions::application_command::ApplicationCommandInteraction, - interactions::application_command::ApplicationCommandOptionType, - interactions::application_command::ApplicationCommandType, - }, + builder::CreateApplicationCommand, + client::Context, + framework::standard::CommandResult, + model::{ + guild::Guild, interactions::application_command::ApplicationCommand, + interactions::application_command::ApplicationCommandInteraction, + interactions::application_command::ApplicationCommandOptionType, + interactions::application_command::ApplicationCommandType, + }, }; pub struct CommandManager { - commands_registered: bool, - commands: Vec, + commands_registered: bool, + commands: Vec, } impl CommandManager { - pub fn new() -> Self { - CommandManager { - commands_registered: false, - commands: CommandManager::build_commands(), - } + pub fn new() -> Self { + CommandManager { commands_registered: false, commands: CommandManager::build_commands() } + } + + 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; + } } - 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; - } - } - - match command_name.as_str() { - "compile" | "compile [beta]" => slashcmds::compile::compile(ctx, command).await, - "assembly" | "assembly [beta]" => 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" | "format [beta]" => slashcmds::format::format(ctx, command).await, - "diff" | "diff [beta]" => { - if command.data.kind == ApplicationCommandType::Message { - slashcmds::diff_msg::diff_msg(ctx, command).await - } else { - slashcmds::diff::diff(ctx, command).await - } - } - e => { - warn!("Unknown application command received: {}", e); - Ok(()) - } + match command_name.as_str() { + "compile" | "compile [beta]" => slashcmds::compile::compile(ctx, command).await, + "assembly" | "assembly [beta]" => 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" | "format [beta]" => slashcmds::format::format(ctx, command).await, + "diff" | "diff [beta]" => { + if command.data.kind == ApplicationCommandType::Message { + slashcmds::diff_msg::diff_msg(ctx, command).await + } else { + slashcmds::diff::diff(ctx, command).await } + } + e => { + warn!("Unknown application command received: {}", e); + Ok(()) + } } - - pub async fn register_commands_guild(&mut self, ctx: &Context, guild: &Guild) { - match guild - .set_application_commands(&ctx.http, |setter| { - setter.set_application_commands(self.commands.clone()) - }) - .await - { - Err(e) => error!( - "Unable to set application commands for guild '{}': {}", - guild.id, e - ), - Ok(commands) => info!( - "Registered {} commands in guild: {}", - commands.len(), - guild.id - ), - } + } + + pub async fn register_commands_guild(&mut self, ctx: &Context, guild: &Guild) { + match guild + .set_application_commands(&ctx.http, |setter| { + setter.set_application_commands(self.commands.clone()) + }) + .await + { + Err(e) => error!("Unable to set application commands for guild '{}': {}", guild.id, e), + Ok(commands) => info!("Registered {} commands in guild: {}", commands.len(), guild.id), } + } - pub async fn register_commands_global(&mut self, ctx: &Context) { - if self.commands_registered { - return; - } - self.commands_registered = true; - - match ApplicationCommand::set_global_application_commands(&ctx.http, |setter| { - setter.set_application_commands(self.commands.clone()) - }) - .await - { - Ok(cmds) => info!("Registered {} application commands", cmds.len()), - Err(e) => error!("Unable to set application commands: {}", e), - } + pub async fn register_commands_global(&mut self, ctx: &Context) { + if self.commands_registered { + return; } - - pub fn build_commands() -> Vec { - let mut cmds = Vec::new(); - - let mut cmd = CreateApplicationCommand::default(); - cmd.kind(ApplicationCommandType::Message).name(format!( - "Compile{}", - if cfg!(debug_assertions) { - " [BETA]" - } else { - "" - } - )); - cmds.push(cmd); - - cmd = CreateApplicationCommand::default(); - cmd.kind(ApplicationCommandType::Message).name(format!( - "Assembly{}", - if cfg!(debug_assertions) { - " [BETA]" - } else { - "" - } - )); - cmds.push(cmd); - - cmd = CreateApplicationCommand::default(); - cmd.kind(ApplicationCommandType::Message).name(format!( - "Format{}", - if cfg!(debug_assertions) { - " [BETA]" - } else { - "" - } - )); - cmds.push(cmd); - - cmd = CreateApplicationCommand::default(); - cmd.kind(ApplicationCommandType::Message).name(format!( - "Diff{}", - if cfg!(debug_assertions) { - " [BETA]" - } else { - "" - } - )); - cmds.push(cmd); - - cmd = CreateApplicationCommand::default(); - cmd.kind(ApplicationCommandType::ChatInput) - .name("help") - .description("Information on how to use the compiler"); - cmds.push(cmd); - - cmd = CreateApplicationCommand::default(); - cmd.kind(ApplicationCommandType::ChatInput) - .name("invite") - .description("Grab my invite link to invite me to your server"); - cmds.push(cmd); - - cmd = CreateApplicationCommand::default(); - cmd.kind(ApplicationCommandType::ChatInput) - .name("ping") - .description("Test my ping to Discord's endpoint"); - cmds.push(cmd); - - cmd = CreateApplicationCommand::default(); - 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") - }); - cmds.push(cmd); - - cmd = CreateApplicationCommand::default(); - cmd.kind(ApplicationCommandType::ChatInput) - .name("diff") - .description("Posts a diff of two message code blocks") - .create_option(|opt| { - opt.required(true) - .name("message1") - .kind(ApplicationCommandOptionType::String) - .description("Message id of first code-block") - }) - .create_option(|opt| { - opt.required(true) - .name("message2") - .kind(ApplicationCommandOptionType::String) - .description("Message id of second code-block") - }); - cmds.push(cmd); - - cmds + self.commands_registered = true; + + match ApplicationCommand::set_global_application_commands(&ctx.http, |setter| { + setter.set_application_commands(self.commands.clone()) + }) + .await + { + Ok(cmds) => info!("Registered {} application commands", cmds.len()), + Err(e) => error!("Unable to set application commands: {}", e), } + } + + pub fn build_commands() -> Vec { + let mut cmds = Vec::new(); + + let mut cmd = CreateApplicationCommand::default(); + cmd + .kind(ApplicationCommandType::Message) + .name(format!("Compile{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(ApplicationCommandType::Message) + .name(format!("Assembly{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(ApplicationCommandType::Message) + .name(format!("Format{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(ApplicationCommandType::Message) + .name(format!("Diff{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(ApplicationCommandType::ChatInput) + .name("help") + .description("Information on how to use the compiler"); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(ApplicationCommandType::ChatInput) + .name("invite") + .description("Grab my invite link to invite me to your server"); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(ApplicationCommandType::ChatInput) + .name("ping") + .description("Test my ping to Discord's endpoint"); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + 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") + }); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(ApplicationCommandType::ChatInput) + .name("diff") + .description("Posts a diff of two message code blocks") + .create_option(|opt| { + opt + .required(true) + .name("message1") + .kind(ApplicationCommandOptionType::String) + .description("Message id of first code-block") + }) + .create_option(|opt| { + opt + .required(true) + .name("message2") + .kind(ApplicationCommandOptionType::String) + .description("Message id of second code-block") + }); + cmds.push(cmd); + + cmds + } } diff --git a/src/managers/compilation.rs b/src/managers/compilation.rs index c3b2c7b..de40abe 100644 --- a/src/managers/compilation.rs +++ b/src/managers/compilation.rs @@ -15,30 +15,30 @@ use crate::utls::parser::ParserResult; // struct containing any information resolved during the compilation step #[derive(Default)] pub struct CompilationDetails { - pub language: String, - pub compiler: String, + pub language: String, + pub compiler: String, } //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 { - self.is_valid_language(language) || self.is_valid_compiler_str(language) - } + 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 { - self.resolve(language).is_some() - } + fn resolve(&self, language: &str) -> bool { + self.resolve(language).is_some() + } } pub enum RequestHandler { - None, - WandBox, - CompilerExplorer, + None, + WandBox, + CompilerExplorer, } /// An abstraction for wandbox and godbolt. This object serves as the main interface between @@ -46,287 +46,258 @@ 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: Option, - pub gbolt: Option, + pub wbox: Option, + pub gbolt: Option, } impl CompilationManager { - pub async fn new() -> Result> { - let mut broken_compilers = std::collections::HashSet::new(); - broken_compilers.insert(String::from("ghc-head")); - broken_compilers.insert(String::from("go-head")); - let mut broken_languages = std::collections::HashSet::new(); - broken_languages.insert(String::from("cpp")); - - let wbox = wandbox::Wandbox::new(Some(broken_compilers), Some(broken_languages)).await; - if let Err(e) = &wbox { - error!("Unable to load wandbox: {}", e); - } - - let gbolt = Godbolt::new().await; - if let Err(e) = &gbolt { - error!("Unable to load compiler explorer: {}", e); - } - Ok(CompilationManager { - wbox: wbox.ok(), - gbolt: gbolt.ok(), - }) + pub async fn new() -> Result> { + let mut broken_compilers = std::collections::HashSet::new(); + broken_compilers.insert(String::from("ghc-head")); + broken_compilers.insert(String::from("go-head")); + let mut broken_languages = std::collections::HashSet::new(); + broken_languages.insert(String::from("cpp")); + + let wbox = wandbox::Wandbox::new(Some(broken_compilers), Some(broken_languages)).await; + if let Err(e) = &wbox { + error!("Unable to load wandbox: {}", e); } - pub async fn compile( - &self, - parser_result: &ParserResult, - author: &User, - ) -> Result<(CompilationDetails, CreateEmbed), CommandError> { - match self.resolve_target(&parser_result.target) { - RequestHandler::CompilerExplorer => { - let result = self.compiler_explorer(parser_result).await?; - - let options = - EmbedOptions::new(false, result.0.language.clone(), result.0.compiler.clone()); - Ok((result.0, result.1.to_embed(author, &options))) - } - RequestHandler::WandBox => { - let result = self.wandbox(parser_result).await?; - - let options = - EmbedOptions::new(false, result.0.language.clone(), result.0.compiler.clone()); - Ok((result.0, result.1.to_embed(author, &options))) - } - RequestHandler::None => { - let mut target = parser_result.target.clone(); - if target.starts_with('@') { - target = format!("\\{}", target); - } - Err(CommandError::from(format!( - "Unable to find compiler or language for target '{}'.", - target - ))) - } - } + let gbolt = Godbolt::new().await; + if let Err(e) = &gbolt { + error!("Unable to load compiler explorer: {}", e); } - - pub async fn assembly( - &self, - parse_result: &ParserResult, - author: &User, - ) -> Result<(String, CreateEmbed), CommandError> { - let gbolt = match &self.gbolt { - Some(gbolt) => gbolt, - None => { - return Err(CommandError::from("Compiler Explorer is uninitialized, this may be due to an outage or error. Please try again later.")); - } - }; - - let filters = CompilationFilters { - binary: None, - comment_only: Some(true), - demangle: Some(true), - directives: Some(true), - execute: Some(false), - intel: Some(true), - labels: Some(true), - library_code: None, - trim: Some(true), - }; - - let options = RequestOptions { - user_arguments: parse_result.options.join(" "), - compiler_options: CompilerOptions { - skip_asm: false, - executor_request: false, - }, - execute_parameters: Default::default(), - filters, - }; - - let target = if parse_result.target == "haskell" { - "ghc901" - } else { - &parse_result.target - }; - let resolution_result = gbolt.resolve(target); - match resolution_result { - None => { - Err(CommandError::from(format!("Target '{}' either does not produce assembly or is not currently supported on MS Azure", target))) - } - Some(compiler) => { - let response = Godbolt::send_request(&compiler, &parse_result.code, options, USER_AGENT).await?; - let options = EmbedOptions::new(true, target.to_string(), compiler.name); - Ok((compiler.lang, response.to_embed(author, &options))) - } + Ok(CompilationManager { wbox: wbox.ok(), gbolt: gbolt.ok() }) + } + + pub async fn compile( + &self, + parser_result: &ParserResult, + author: &User, + ) -> Result<(CompilationDetails, CreateEmbed), CommandError> { + match self.resolve_target(&parser_result.target) { + RequestHandler::CompilerExplorer => { + let result = self.compiler_explorer(parser_result).await?; + + let options = + EmbedOptions::new(false, result.0.language.clone(), result.0.compiler.clone()); + Ok((result.0, result.1.to_embed(author, &options))) + } + RequestHandler::WandBox => { + let result = self.wandbox(parser_result).await?; + + let options = + EmbedOptions::new(false, result.0.language.clone(), result.0.compiler.clone()); + Ok((result.0, result.1.to_embed(author, &options))) + } + RequestHandler::None => { + let mut target = parser_result.target.clone(); + if target.starts_with('@') { + target = format!("\\{}", target); } + Err(CommandError::from(format!( + "Unable to find compiler or language for target '{}'.", + target + ))) + } } - - pub async fn compiler_explorer( - &self, - parse_result: &ParserResult, - ) -> Result<(CompilationDetails, godbolt::GodboltResponse), CommandError> { - let gbolt = match &self.gbolt { - Some(gbolt) => gbolt, - None => { - return Err(CommandError::from("Compiler Explorer is uninitialized, this may be due to an outage or error. Please try again later.")); - } - }; - - let filters = CompilationFilters { - binary: None, - comment_only: Some(true), - demangle: Some(true), - directives: Some(true), - execute: Some(true), - intel: Some(true), - labels: Some(true), - library_code: None, - trim: Some(true), - }; - - let options = RequestOptions { - user_arguments: parse_result.options.join(" "), - compiler_options: CompilerOptions { - skip_asm: true, - executor_request: true, - }, - execute_parameters: godbolt::ExecuteParameters { - args: parse_result.args.clone(), - stdin: parse_result.stdin.clone(), - }, - filters, - }; - - let target = if parse_result.target == "haskell" { - "ghc901" - } else { - &parse_result.target - }; - let compiler = gbolt.resolve(target).unwrap(); - - // report discovered information - let details = CompilationDetails { - compiler: compiler.name.clone(), - language: compiler.lang.clone(), - }; - - // add boilerplate code if needed & fix common mistakes - let mut code = parse_result.code.clone(); - { - let generator = boilerplate_factory(&compiler.lang, &code); - if generator.needs_boilerplate() { - code = generator.generate(); - } - - code = fix_common_problems(&compiler.lang, code); - } - let response = Godbolt::send_request(&compiler, &code, options, USER_AGENT).await?; - Ok((details, response)) + } + + pub async fn assembly( + &self, + parse_result: &ParserResult, + author: &User, + ) -> Result<(String, CreateEmbed), CommandError> { + let gbolt = match &self.gbolt { + Some(gbolt) => gbolt, + None => { + return Err(CommandError::from("Compiler Explorer is uninitialized, this may be due to an outage or error. Please try again later.")); + } + }; + + let filters = CompilationFilters { + binary: None, + comment_only: Some(true), + demangle: Some(true), + directives: Some(true), + execute: Some(false), + intel: Some(true), + labels: Some(true), + library_code: None, + trim: Some(true), + }; + + let options = RequestOptions { + user_arguments: parse_result.options.join(" "), + compiler_options: CompilerOptions { skip_asm: false, executor_request: false }, + execute_parameters: Default::default(), + filters, + }; + + let target = if parse_result.target == "haskell" { "ghc901" } else { &parse_result.target }; + let resolution_result = gbolt.resolve(target); + match resolution_result { + None => Err(CommandError::from(format!( + "Target '{}' either does not produce assembly or is not currently supported on MS Azure", + target + ))), + Some(compiler) => { + let response = + Godbolt::send_request(&compiler, &parse_result.code, options, USER_AGENT).await?; + let options = EmbedOptions::new(true, target.to_string(), compiler.name); + Ok((compiler.lang, response.to_embed(author, &options))) + } } + } + + pub async fn compiler_explorer( + &self, + parse_result: &ParserResult, + ) -> Result<(CompilationDetails, godbolt::GodboltResponse), CommandError> { + let gbolt = match &self.gbolt { + Some(gbolt) => gbolt, + None => { + return Err(CommandError::from("Compiler Explorer is uninitialized, this may be due to an outage or error. Please try again later.")); + } + }; + + let filters = CompilationFilters { + binary: None, + comment_only: Some(true), + demangle: Some(true), + directives: Some(true), + execute: Some(true), + intel: Some(true), + labels: Some(true), + library_code: None, + trim: Some(true), + }; + + let options = RequestOptions { + user_arguments: parse_result.options.join(" "), + compiler_options: CompilerOptions { skip_asm: true, executor_request: true }, + execute_parameters: godbolt::ExecuteParameters { + args: parse_result.args.clone(), + stdin: parse_result.stdin.clone(), + }, + filters, + }; + + let target = if parse_result.target == "haskell" { "ghc901" } else { &parse_result.target }; + let compiler = gbolt.resolve(target).unwrap(); + + // report discovered information + let details = + CompilationDetails { compiler: compiler.name.clone(), language: compiler.lang.clone() }; + + // add boilerplate code if needed & fix common mistakes + let mut code = parse_result.code.clone(); + { + let generator = boilerplate_factory(&compiler.lang, &code); + if generator.needs_boilerplate() { + code = generator.generate(); + } + + code = fix_common_problems(&compiler.lang, code); + } + let response = Godbolt::send_request(&compiler, &code, options, USER_AGENT).await?; + Ok((details, response)) + } - pub fn resolve_target(&self, target: &str) -> RequestHandler { - if target == "scala" || target == "nim" || target == "typescript" { - return RequestHandler::WandBox; - } - - if let Some(gbolt) = &self.gbolt { - if gbolt.resolve(target).is_some() { - return RequestHandler::CompilerExplorer; - } - } - if let Some(wbox) = &self.wbox { - if wbox.resolve(target) { - return RequestHandler::WandBox; - } - } + pub fn resolve_target(&self, target: &str) -> RequestHandler { + if target == "scala" || target == "nim" || target == "typescript" { + return RequestHandler::WandBox; + } - RequestHandler::None + if let Some(gbolt) = &self.gbolt { + if gbolt.resolve(target).is_some() { + return RequestHandler::CompilerExplorer; + } + } + if let Some(wbox) = &self.wbox { + if wbox.resolve(target) { + return RequestHandler::WandBox; + } } - pub async fn wandbox( - &self, - parse_result: &ParserResult, - ) -> Result<(CompilationDetails, wandbox::CompilationResult), CommandError> { - let wbox = match &self.wbox { - Some(wbox) => wbox, - None => { - return Err(CommandError::from("WandBox is uninitialized, this may be due to an outage or error. Please try again later.")); - } - }; - let mut details = CompilationDetails::default(); - - let lang = { - let mut found = String::default(); - for lang in wbox.get_languages() { - if parse_result.target == lang.name { - found = parse_result.target.clone(); - details.compiler = lang.compilers[0].name.clone(); - } - for compiler in lang.compilers { - if compiler.name == parse_result.target { - found = lang.name.clone(); - } - } - } - if found.is_empty() { - warn!("Invalid target leaked checks and was caught before boilerplate creation") - } - found - }; - - details.language = lang.clone(); - - let mut code = parse_result.code.clone(); - { - let generator = boilerplate_factory(&lang, &code); - if generator.needs_boilerplate() { - code = generator.generate(); - } - - code = fix_common_problems(&lang, code); + RequestHandler::None + } + + pub async fn wandbox( + &self, + parse_result: &ParserResult, + ) -> Result<(CompilationDetails, wandbox::CompilationResult), CommandError> { + let wbox = match &self.wbox { + Some(wbox) => wbox, + None => { + return Err(CommandError::from("WandBox is uninitialized, this may be due to an outage or error. Please try again later.")); + } + }; + let mut details = CompilationDetails::default(); + + let lang = { + let mut found = String::default(); + for lang in wbox.get_languages() { + if parse_result.target == lang.name { + found = parse_result.target.clone(); + details.compiler = lang.compilers[0].name.clone(); } - - let mut builder = CompilationBuilder::new(); - builder.code(&code); - builder.target(&parse_result.target); - builder.stdin(&parse_result.stdin); - builder.save(false); - builder.options(parse_result.options.clone()); - - builder.build(wbox)?; - let res = builder.dispatch().await?; - Ok((details, res)) + for compiler in lang.compilers { + if compiler.name == parse_result.target { + found = lang.name.clone(); + } + } + } + if found.is_empty() { + warn!("Invalid target leaked checks and was caught before boilerplate creation") + } + found + }; + + details.language = lang.clone(); + + let mut code = parse_result.code.clone(); + { + let generator = boilerplate_factory(&lang, &code); + if generator.needs_boilerplate() { + code = generator.generate(); + } + + code = fix_common_problems(&lang, code); } - pub fn slash_cmd_langs() -> [&'static str; 11] { - [ - "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"] - } + let mut builder = CompilationBuilder::new(); + builder.code(&code); + builder.target(&parse_result.target); + builder.stdin(&parse_result.stdin); + builder.save(false); + builder.options(parse_result.options.clone()); + + builder.build(wbox)?; + let res = builder.dispatch().await?; + Ok((details, res)) + } + + pub fn slash_cmd_langs() -> [&'static str; 11] { + ["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"] + } } fn fix_common_problems(language: &str, code: String) -> String { - match language { - "java" => { - // Fix compilations that - let mut fix_candidate = code.clone(); - for m in JAVA_PUBLIC_CLASS_REGEX.captures_iter(&code) { - if let Some(pub_keyword) = m.name("public") { - fix_candidate.replace_range(pub_keyword.range(), "") - } - } - fix_candidate + match language { + "java" => { + // Fix compilations that + let mut fix_candidate = code.clone(); + for m in JAVA_PUBLIC_CLASS_REGEX.captures_iter(&code) { + if let Some(pub_keyword) = m.name("public") { + fix_candidate.replace_range(pub_keyword.range(), "") } - _ => code, + } + fix_candidate } + _ => code, + } } diff --git a/src/managers/stats.rs b/src/managers/stats.rs index b2ce921..168c95e 100644 --- a/src/managers/stats.rs +++ b/src/managers/stats.rs @@ -6,121 +6,121 @@ use crate::stats::structures::*; use serenity::model::id::GuildId; pub struct StatsManager { - client: Arc, - url: String, - pass: String, - servers: u64, - shards: u64, - boot_count: Vec, - leave_queue: u64, - join_queue: u64, + client: Arc, + url: String, + pass: String, + servers: u64, + shards: u64, + boot_count: Vec, + leave_queue: u64, + join_queue: u64, } impl StatsManager { - pub fn new() -> StatsManager { - StatsManager { - client: Arc::new(reqwest::Client::new()), - url: env::var("STATS_API_LINK").unwrap_or_default(), - pass: env::var("STATS_API_KEY").unwrap_or_default(), - servers: 0, - leave_queue: 0, - join_queue: 0, - shards: 0, - boot_count: Vec::new(), - } + pub fn new() -> StatsManager { + StatsManager { + client: Arc::new(reqwest::Client::new()), + url: env::var("STATS_API_LINK").unwrap_or_default(), + pass: env::var("STATS_API_KEY").unwrap_or_default(), + servers: 0, + leave_queue: 0, + join_queue: 0, + shards: 0, + boot_count: Vec::new(), } + } - pub fn should_track(&self) -> bool { - !self.url.is_empty() && !self.pass.is_empty() - } - - pub async fn compilation(&self, language: &str, fail: bool) { - let mut cmd = LanguageRequest::new(language, fail); - self.send_request::(&mut cmd).await; - } - - pub async fn command_executed(&self, command: &str, guild: Option) { - let mut cmd = CommandRequest::new(command, guild); - self.send_request::(&mut cmd).await; - } + pub fn should_track(&self) -> bool { + !self.url.is_empty() && !self.pass.is_empty() + } - pub async fn post_servers(&mut self, amount: u64) { - self.servers = amount; + pub async fn compilation(&self, language: &str, fail: bool) { + let mut cmd = LanguageRequest::new(language, fail); + self.send_request::(&mut cmd).await; + } - // in the connect phase it's entirely possible for our server count to be - // zero while we receive a guild left or guild joined event, since they were - // queued we can now modify the server count safely + pub async fn command_executed(&self, command: &str, guild: Option) { + let mut cmd = CommandRequest::new(command, guild); + self.send_request::(&mut cmd).await; + } - // join queue - self.servers += self.join_queue; - self.join_queue = 0; + pub async fn post_servers(&mut self, amount: u64) { + self.servers = amount; - // leave queue - self.servers -= self.leave_queue; - self.leave_queue = 0; + // in the connect phase it's entirely possible for our server count to be + // zero while we receive a guild left or guild joined event, since they were + // queued we can now modify the server count safely - // update our stats - if self.should_track() { - let mut legacy = LegacyRequest::new(Some(self.servers)); - self.send_request::(&mut legacy).await; - } - } + // join queue + self.servers += self.join_queue; + self.join_queue = 0; - pub async fn new_server(&mut self) { - if self.servers < 1 { - // not all shards have loaded in yet - queue the join for post_servers - self.join_queue += 1; - return; - } - - self.servers += 1; - if self.should_track() { - let mut legacy = LegacyRequest::new(Some(self.servers)); - self.send_request::(&mut legacy).await; - } - } + // leave queue + self.servers -= self.leave_queue; + self.leave_queue = 0; - pub async fn leave_server(&mut self) { - if self.servers < 1 { - // not loaded in - queue leave for post_servers - self.leave_queue += 1; - return; - } - - self.servers -= 1; - if self.should_track() { - let mut legacy = LegacyRequest::new(Some(self.servers)); - self.send_request::(&mut legacy).await; - } + // update our stats + if self.should_track() { + let mut legacy = LegacyRequest::new(Some(self.servers)); + self.send_request::(&mut legacy).await; } + } - pub async fn post_request(&self) { - let mut legacy = LegacyRequest::new(None); - self.send_request::(&mut legacy).await; + pub async fn new_server(&mut self) { + if self.servers < 1 { + // not all shards have loaded in yet - queue the join for post_servers + self.join_queue += 1; + return; } - pub fn server_count(&self) -> u64 { - self.servers + self.servers += 1; + if self.should_track() { + let mut legacy = LegacyRequest::new(Some(self.servers)); + self.send_request::(&mut legacy).await; } + } - pub fn shard_count(&self) -> u64 { - self.shards + pub async fn leave_server(&mut self) { + if self.servers < 1 { + // not loaded in - queue leave for post_servers + self.leave_queue += 1; + return; } - pub fn add_shard(&mut self, server_count: u64) { - self.shards += 1; - self.boot_count.push(server_count); + self.servers -= 1; + if self.should_track() { + let mut legacy = LegacyRequest::new(Some(self.servers)); + self.send_request::(&mut legacy).await; } - - pub fn get_boot_vec_sum(&self) -> u64 { - self.boot_count.iter().sum() - } - - async fn send_request(&self, sendable: &mut T) { - sendable.set_key(&self.pass); - match sendable.send(self.client.clone(), &self.url).await { - Ok(_) => (), - Err(e) => warn!("Request failed to {}: {}", sendable.endpoint(), e), - } + } + + pub async fn post_request(&self) { + let mut legacy = LegacyRequest::new(None); + self.send_request::(&mut legacy).await; + } + + pub fn server_count(&self) -> u64 { + self.servers + } + + pub fn shard_count(&self) -> u64 { + self.shards + } + + pub fn add_shard(&mut self, server_count: u64) { + self.shards += 1; + self.boot_count.push(server_count); + } + + pub fn get_boot_vec_sum(&self) -> u64 { + self.boot_count.iter().sum() + } + + async fn send_request(&self, sendable: &mut T) { + sendable.set_key(&self.pass); + match sendable.send(self.client.clone(), &self.url).await { + Ok(_) => (), + Err(e) => warn!("Request failed to {}: {}", sendable.endpoint(), e), } + } } diff --git a/src/slashcmds/asm.rs b/src/slashcmds/asm.rs index 03ada33..c841cbc 100644 --- a/src/slashcmds/asm.rs +++ b/src/slashcmds/asm.rs @@ -1,37 +1,35 @@ use serenity::{ - client::Context, - framework::standard::{CommandError, CommandResult}, - model::interactions::application_command::ApplicationCommandInteraction, + client::Context, + framework::standard::{CommandError, CommandResult}, + model::interactions::application_command::ApplicationCommandInteraction, }; use crate::{ - cache::CompilerCache, managers::compilation::CompilationManager, - utls::discordhelpers::interactions, + 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?; - debug!("Command executed"); - Ok(()) + 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?; + debug!("Command executed"); + Ok(()) } diff --git a/src/slashcmds/compile.rs b/src/slashcmds/compile.rs index 0b89bc9..9a7f577 100644 --- a/src/slashcmds/compile.rs +++ b/src/slashcmds/compile.rs @@ -1,39 +1,37 @@ use serenity::{ - client::Context, framework::standard::CommandError, framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, + client::Context, framework::standard::CommandError, framework::standard::CommandResult, + model::interactions::application_command::ApplicationCommandInteraction, }; use tokio::sync::RwLockReadGuard; use crate::{ - cache::CompilerCache, managers::compilation::CompilationManager, - 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?; - debug!("Command executed"); - Ok(()) + 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?; + debug!("Command executed"); + Ok(()) } diff --git a/src/slashcmds/cpp.rs b/src/slashcmds/cpp.rs index 08544a9..f1008c6 100644 --- a/src/slashcmds/cpp.rs +++ b/src/slashcmds/cpp.rs @@ -1,19 +1,19 @@ use serenity::{ - framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, - model::interactions::application_command::ApplicationCommandInteractionDataOptionValue, - model::prelude::*, prelude::*, + framework::standard::CommandResult, + model::interactions::application_command::ApplicationCommandInteraction, + model::interactions::application_command::ApplicationCommandInteractionDataOptionValue, + model::prelude::*, prelude::*, }; use crate::utls::discordhelpers::embeds::EmbedOptions; use crate::{ - cache::CompilerCache, cppeval::eval::CppEval, utls::constants::COLOR_OKAY, - utls::discordhelpers::embeds::ToEmbed, utls::parser::ParserResult, + 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 { - if msg.data.options.is_empty() { - msg.create_interaction_response(&ctx.http, |resp| { + if msg.data.options.is_empty() { + msg.create_interaction_response(&ctx.http, |resp| { resp.interaction_response_data(|data| { data.embed(|emb| { emb @@ -28,46 +28,48 @@ pub async fn cpp(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandR }) }) }).await?; - return Ok(()); - } - msg.create_interaction_response(&ctx.http, |resp| { - resp.kind(InteractionResponseType::DeferredChannelMessageWithSource) + return Ok(()); + } + msg + .create_interaction_response(&ctx.http, |resp| { + resp.kind(InteractionResponseType::DeferredChannelMessageWithSource) }) .await?; - let geordi_input = msg - .data - .options - .get(0) - .expect("Expected interaction option 0") - .resolved - .as_ref() - .expect("Expected data option value"); + let geordi_input = msg + .data + .options + .get(0) + .expect("Expected interaction option 0") + .resolved + .as_ref() + .expect("Expected data option value"); - if let ApplicationCommandInteractionDataOptionValue::String(input) = geordi_input { - let mut eval = CppEval::new(input); - let out = eval.evaluate()?; + if let ApplicationCommandInteractionDataOptionValue::String(input) = geordi_input { + let mut eval = CppEval::new(input); + let out = eval.evaluate()?; - let fake_parse = ParserResult { - url: "".to_string(), - stdin: "".to_string(), - target: "g101".to_string(), - code: out, - options: vec![String::from("-O2"), String::from("-std=gnu++2a")], - args: vec![], - }; + let fake_parse = ParserResult { + url: "".to_string(), + stdin: "".to_string(), + target: "g101".to_string(), + code: out, + options: vec![String::from("-O2"), String::from("-std=gnu++2a")], + args: vec![], + }; - let data_read = ctx.data.read().await; - let compiler_lock = data_read.get::().unwrap().read().await; - let result = compiler_lock.compiler_explorer(&fake_parse).await?; - let options = EmbedOptions::new(false, fake_parse.target.clone(), String::default()); + let data_read = ctx.data.read().await; + let compiler_lock = data_read.get::().unwrap().read().await; + let result = compiler_lock.compiler_explorer(&fake_parse).await?; + let options = EmbedOptions::new(false, fake_parse.target.clone(), String::default()); - msg.edit_original_interaction_response(&ctx.http, |resp| { - resp.add_embed(result.1.to_embed(&msg.user, &options)) - }) - .await?; - } + msg + .edit_original_interaction_response(&ctx.http, |resp| { + resp.add_embed(result.1.to_embed(&msg.user, &options)) + }) + .await?; + } - debug!("Command executed"); - Ok(()) + debug!("Command executed"); + Ok(()) } diff --git a/src/slashcmds/diff.rs b/src/slashcmds/diff.rs index 096bb4d..72e8db9 100644 --- a/src/slashcmds/diff.rs +++ b/src/slashcmds/diff.rs @@ -1,139 +1,137 @@ use serenity::framework::standard::CommandError; use serenity::{ - framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, - model::interactions::application_command::ApplicationCommandInteractionDataOptionValue, - model::prelude::*, prelude::*, + framework::standard::CommandResult, + model::interactions::application_command::ApplicationCommandInteraction, + model::interactions::application_command::ApplicationCommandInteractionDataOptionValue, + model::prelude::*, prelude::*, }; use similar::ChangeTag; use crate::{ - utls::constants::COLOR_FAIL, utls::constants::COLOR_OKAY, utls::parser::find_code_block, - utls::parser::ParserResult, + utls::constants::COLOR_FAIL, utls::constants::COLOR_OKAY, utls::parser::find_code_block, + utls::parser::ParserResult, }; use std::fmt::Write as _; pub async fn diff(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandResult { - let message1 = msg - .data - .options - .get(0) - .expect("Expected interaction option 0") - .resolved - .as_ref() - .expect("Expected data option value"); + let message1 = msg + .data + .options + .get(0) + .expect("Expected interaction option 0") + .resolved + .as_ref() + .expect("Expected data option value"); - let message2 = msg - .data - .options - .get(1) - .expect("Expected interaction option 1") - .resolved - .as_ref() - .expect("Expected data option value"); + let message2 = msg + .data + .options + .get(1) + .expect("Expected interaction option 1") + .resolved + .as_ref() + .expect("Expected data option value"); - let mut message1_parse = None; - if let ApplicationCommandInteractionDataOptionValue::String(input) = message1 { - message1_parse = input.parse::().ok(); - } - let mut message2_parse = None; - if let ApplicationCommandInteractionDataOptionValue::String(input) = message2 { - message2_parse = input.parse::().ok(); - } + let mut message1_parse = None; + if let ApplicationCommandInteractionDataOptionValue::String(input) = message1 { + message1_parse = input.parse::().ok(); + } + let mut message2_parse = None; + if let ApplicationCommandInteractionDataOptionValue::String(input) = message2 { + message2_parse = input.parse::().ok(); + } - if message1_parse.is_none() || message2_parse.is_none() { - msg.create_interaction_response(&ctx.http, |resp| { - resp.interaction_response_data(|data| { - data.embed(|emb| { - emb.color(COLOR_FAIL).description( - "Invalid message ID specified!\n\n\ + if message1_parse.is_none() || message2_parse.is_none() { + msg + .create_interaction_response(&ctx.http, |resp| { + resp.interaction_response_data(|data| { + data + .embed(|emb| { + emb.color(COLOR_FAIL).description( + "Invalid message ID specified!\n\n\ Right click a message and select 'Copy ID' at the bottom. If you cannot \ see this option then you must first enable Developer Mode by going to the \ User Settings > Advanced tab", - ) - }) - .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + ) }) + .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) }) - .await?; - return Ok(()); - } + }) + .await?; + return Ok(()); + } - let message1_obj = ctx - .http - .get_message(msg.channel_id.0, message1_parse.unwrap()) - .await - .ok(); - let message2_obj = ctx - .http - .get_message(msg.channel_id.0, message2_parse.unwrap()) - .await - .ok(); - if message1_obj.is_none() || message2_obj.is_none() { - msg.create_interaction_response(&ctx.http, |resp| { - resp.interaction_response_data(|data| { - data.embed(|emb| { - emb.color(COLOR_FAIL).description( - "Unable to find message.\n\nEnsure both messages belong to \ + let message1_obj = ctx.http.get_message(msg.channel_id.0, message1_parse.unwrap()).await.ok(); + let message2_obj = ctx.http.get_message(msg.channel_id.0, message2_parse.unwrap()).await.ok(); + if message1_obj.is_none() || message2_obj.is_none() { + msg + .create_interaction_response(&ctx.http, |resp| { + resp.interaction_response_data(|data| { + data + .embed(|emb| { + emb.color(COLOR_FAIL).description( + "Unable to find message.\n\nEnsure both messages belong to \ this channel and the Message IDs are correct.", - ) - }) - .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + ) }) + .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) }) - .await?; - return Ok(()); - } + }) + .await?; + return Ok(()); + } - let msg1 = message1_obj.unwrap(); - let msg2 = message2_obj.unwrap(); + let msg1 = message1_obj.unwrap(); + let msg2 = message2_obj.unwrap(); - let content1 = get_code_block_or_content(&msg1.content, &msg1.author).await?; - let content2 = get_code_block_or_content(&msg2.content, &msg2.author).await?; + let content1 = get_code_block_or_content(&msg1.content, &msg1.author).await?; + let content2 = get_code_block_or_content(&msg2.content, &msg2.author).await?; - let output = run_diff(&content1, &content2); + let output = run_diff(&content1, &content2); - msg.create_interaction_response(&ctx.http, |resp| { - resp.interaction_response_data(|data| { - data.embed(|emb| { - emb.color(COLOR_OKAY) - .title("Diff completed") - .description(format!("```diff\n{}\n```", output)) - }) + msg + .create_interaction_response(&ctx.http, |resp| { + resp.interaction_response_data(|data| { + data.embed(|emb| { + emb + .color(COLOR_OKAY) + .title("Diff completed") + .description(format!("```diff\n{}\n```", output)) }) + }) }) .await?; - debug!("Command executed"); - Ok(()) + debug!("Command executed"); + Ok(()) } pub fn run_diff(first: &str, second: &str) -> String { - let diff = similar::TextDiff::from_lines(first, second); - let mut output = String::new(); - for change in diff.iter_all_changes() { - let sign = match change.tag() { - ChangeTag::Delete => "-", - ChangeTag::Insert => "+", - ChangeTag::Equal => " ", - }; - // output.push_str(&format!("{}{}", sign, change)); - writeln!(output, "{}{}", sign, change).unwrap(); - } - output + let diff = similar::TextDiff::from_lines(first, second); + let mut output = String::new(); + for change in diff.iter_all_changes() { + let sign = match change.tag() { + ChangeTag::Delete => "-", + ChangeTag::Insert => "+", + ChangeTag::Equal => " ", + }; + // output.push_str(&format!("{}{}", sign, change)); + writeln!(output, "{}{}", sign, change).unwrap(); + } + output } pub async fn get_code_block_or_content( - input: &str, - author: &User, + input: &str, + author: &User, ) -> std::result::Result { - let mut fake_parse = ParserResult::default(); - if find_code_block(&mut fake_parse, input, author).await? { - Ok(fake_parse.code) - } else { - // assume content is message content itself - Ok(input.to_owned()) - } + let mut fake_parse = ParserResult::default(); + if find_code_block(&mut fake_parse, input, author).await? { + Ok(fake_parse.code) + } else { + // assume content is message content itself + Ok(input.to_owned()) + } } diff --git a/src/slashcmds/diff_msg.rs b/src/slashcmds/diff_msg.rs index a447816..b9fa0f8 100644 --- a/src/slashcmds/diff_msg.rs +++ b/src/slashcmds/diff_msg.rs @@ -1,102 +1,108 @@ use std::time::Duration; use serenity::{ - framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, prelude::*, + framework::standard::CommandResult, + model::interactions::application_command::ApplicationCommandInteraction, prelude::*, }; use crate::slashcmds::diff::run_diff; use crate::{ - cache::{DiffCommandCache, DiffCommandEntry}, - slashcmds::diff::get_code_block_or_content, - utls::constants::COLOR_OKAY, - utls::discordhelpers::interactions, + cache::{DiffCommandCache, DiffCommandEntry}, + slashcmds::diff::get_code_block_or_content, + utls::constants::COLOR_OKAY, + utls::discordhelpers::interactions, }; pub async fn diff_msg(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandResult { - let data = ctx.data.read().await; - let diff_cache_lock = data.get::().unwrap(); + let data = ctx.data.read().await; + let diff_cache_lock = data.get::().unwrap(); - let is_first = { - let mut diff_cache = diff_cache_lock.lock().await; - if let Some(entry) = diff_cache.get_mut(msg.user.id.as_u64()) { - entry.is_expired() - } else { - true - } - }; + let is_first = { + let mut diff_cache = diff_cache_lock.lock().await; + if let Some(entry) = diff_cache.get_mut(msg.user.id.as_u64()) { + entry.is_expired() + } else { + true + } + }; - if is_first { - let (_, new_msg) = msg.data.resolved.messages.iter().next().unwrap(); + if is_first { + let (_, new_msg) = msg.data.resolved.messages.iter().next().unwrap(); - msg.create_interaction_response(&ctx.http, |resp| { - interactions::create_diff_select_response(resp) - }) - .await - .unwrap(); - { - let mut diff_cache = diff_cache_lock.lock().await; - let content = get_code_block_or_content(&new_msg.content, &new_msg.author).await?; - diff_cache.insert(msg.user.id.0, DiffCommandEntry::new(&content, msg)); - } - let resp = msg.get_interaction_response(&ctx.http).await?; - let button_resp = resp - .await_component_interaction(&ctx.shard) - .timeout(Duration::from_secs(30)) - .author_id(msg.user.id.0) - .await; - if let Some(interaction) = button_resp { - interaction.defer(&ctx.http).await?; - let mut diff_cache = diff_cache_lock.lock().await; - diff_cache.remove(interaction.user.id.as_u64()); - msg.edit_original_interaction_response(&ctx.http, |edit| { - edit.set_embeds(Vec::new()) - .embed(|emb| { - emb.color(COLOR_OKAY).description( - "Interaction cancelled, you may safely dismiss this message", - ) - }) - .components(|cmps| cmps.set_action_rows(Vec::new())) + msg + .create_interaction_response(&ctx.http, |resp| { + interactions::create_diff_select_response(resp) + }) + .await + .unwrap(); + { + let mut diff_cache = diff_cache_lock.lock().await; + let content = get_code_block_or_content(&new_msg.content, &new_msg.author).await?; + diff_cache.insert(msg.user.id.0, DiffCommandEntry::new(&content, msg)); + } + let resp = msg.get_interaction_response(&ctx.http).await?; + let button_resp = resp + .await_component_interaction(&ctx.shard) + .timeout(Duration::from_secs(30)) + .author_id(msg.user.id.0) + .await; + if let Some(interaction) = button_resp { + interaction.defer(&ctx.http).await?; + let mut diff_cache = diff_cache_lock.lock().await; + diff_cache.remove(interaction.user.id.as_u64()); + msg + .edit_original_interaction_response(&ctx.http, |edit| { + edit + .set_embeds(Vec::new()) + .embed(|emb| { + emb + .color(COLOR_OKAY) + .description("Interaction cancelled, you may safely dismiss this message") }) - .await?; - } else { - // Button expired - msg.edit_original_interaction_response(&ctx.http, |edit| { - edit.set_embeds(Vec::new()) - .embed(|emb| { - emb.color(COLOR_OKAY).description( - "Interaction expired, you may safely dismiss this messsage", - ) - }) - .components(|cmps| cmps.set_action_rows(Vec::new())) + .components(|cmps| cmps.set_action_rows(Vec::new())) + }) + .await?; + } else { + // Button expired + msg + .edit_original_interaction_response(&ctx.http, |edit| { + edit + .set_embeds(Vec::new()) + .embed(|emb| { + emb + .color(COLOR_OKAY) + .description("Interaction expired, you may safely dismiss this messsage") }) - .await?; - } - return Ok(()); + .components(|cmps| cmps.set_action_rows(Vec::new())) + }) + .await?; } + return Ok(()); + } - // we can execute our diff now + // we can execute our diff now - let (entry, first_interaction) = { - let mut diff_cache = diff_cache_lock.lock().await; - let entry = diff_cache.remove(msg.user.id.as_u64()).unwrap(); - (entry.content, entry.first_interaction) - }; + let (entry, first_interaction) = { + let mut diff_cache = diff_cache_lock.lock().await; + let entry = diff_cache.remove(msg.user.id.as_u64()).unwrap(); + (entry.content, entry.first_interaction) + }; - if let Some((_, new_msg)) = msg.data.resolved.messages.iter().next() { - let content = get_code_block_or_content(&new_msg.content, &new_msg.author).await?; - let output = run_diff(&entry, &content); + if let Some((_, new_msg)) = msg.data.resolved.messages.iter().next() { + let content = get_code_block_or_content(&new_msg.content, &new_msg.author).await?; + let output = run_diff(&entry, &content); - first_interaction - .edit_original_interaction_response(&ctx.http, interactions::edit_to_dismiss_response) - .await?; + first_interaction + .edit_original_interaction_response(&ctx.http, interactions::edit_to_dismiss_response) + .await?; - msg.create_interaction_response(&ctx.http, |resp| { - interactions::create_diff_response(resp, &output) - }) - .await?; - } + msg + .create_interaction_response(&ctx.http, |resp| { + interactions::create_diff_response(resp, &output) + }) + .await?; + } - debug!("Command executed"); - Ok(()) + debug!("Command executed"); + Ok(()) } diff --git a/src/slashcmds/format.rs b/src/slashcmds/format.rs index 6f1dd49..13ec0f8 100644 --- a/src/slashcmds/format.rs +++ b/src/slashcmds/format.rs @@ -1,232 +1,220 @@ use crate::{ - cache::CompilerCache, utls::constants::COLOR_WARN, utls::discordhelpers::interactions, - utls::parser, utls::parser::ParserResult, + cache::CompilerCache, utls::constants::COLOR_WARN, utls::discordhelpers::interactions, + utls::parser, utls::parser::ParserResult, }; use futures_util::StreamExt; use godbolt::{Format, Godbolt}; use serenity::{ - builder::{CreateInteractionResponse, EditInteractionResponse}, - framework::standard::{CommandError, CommandResult}, - model::interactions::application_command::ApplicationCommandInteraction, - model::interactions::message_component::ButtonStyle, - model::prelude::*, - prelude::*, + builder::{CreateInteractionResponse, EditInteractionResponse}, + framework::standard::{CommandError, CommandResult}, + model::interactions::application_command::ApplicationCommandInteraction, + model::interactions::message_component::ButtonStyle, + model::prelude::*, + prelude::*, }; use std::time::Duration; pub async fn format(ctx: &Context, command: &ApplicationCommandInteraction) -> CommandResult { - let mut msg = None; - let mut parse_result = ParserResult::default(); - - 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); - } - - let data = ctx.data.read().await; - let comp_mgr = data.get::().unwrap().read().await; - if comp_mgr.gbolt.is_none() { - return Err(CommandError::from( - "Compiler Explorer service is currently down, please try again later.", - )); - } + let mut msg = None; + let mut parse_result = ParserResult::default(); - command - .create_interaction_response(&ctx.http, |response| { - create_formats_interaction(response, &comp_mgr.gbolt.as_ref().unwrap().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 cic = cib.build(); - let mut formatter = String::from("clangformat"); - let mut selected = false; - while let Some(interaction) = &cic.next().await { - match interaction.data.custom_id.as_str() { - "formatter" => { - formatter = interaction.data.values[0].clone(); - interaction.defer(&ctx.http).await?; - } - "select" => { - interaction.defer(&ctx.http).await?; - selected = true; - cic.stop(); - break; - } - _ => { - unreachable!("Cannot get here.."); - } - } + 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!")); } - - // interaction expired... - if !selected { - return Ok(()); + msg = Some(value); + } + + let data = ctx.data.read().await; + let comp_mgr = data.get::().unwrap().read().await; + if comp_mgr.gbolt.is_none() { + return Err(CommandError::from( + "Compiler Explorer service is currently down, please try again later.", + )); + } + + command + .create_interaction_response(&ctx.http, |response| { + create_formats_interaction(response, &comp_mgr.gbolt.as_ref().unwrap().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 cic = cib.build(); + let mut formatter = String::from("clangformat"); + let mut selected = false; + while let Some(interaction) = &cic.next().await { + match interaction.data.custom_id.as_str() { + "formatter" => { + formatter = interaction.data.values[0].clone(); + interaction.defer(&ctx.http).await?; + } + "select" => { + interaction.defer(&ctx.http).await?; + selected = true; + cic.stop(); + break; + } + _ => { + unreachable!("Cannot get here.."); + } } - - let styles = &comp_mgr - .gbolt - .as_ref() - .unwrap() - .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)); - cic = cib.build(); - selected = false; - let mut style = String::from("Google"); - while let Some(interaction) = &cic.next().await { - match interaction.data.custom_id.as_str() { - "style" => { - style = interaction.data.values[0].clone(); - interaction.defer(&ctx.http).await?; - } - "select" => { - selected = true; - cic.stop(); - break; - } - _ => { - unreachable!("Cannot get here.."); - } - } + } + + // interaction expired... + if !selected { + return Ok(()); + } + + let styles = &comp_mgr + .gbolt + .as_ref() + .unwrap() + .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)); + cic = cib.build(); + selected = false; + let mut style = String::from("Google"); + while let Some(interaction) = &cic.next().await { + match interaction.data.custom_id.as_str() { + "style" => { + style = interaction.data.values[0].clone(); + interaction.defer(&ctx.http).await?; + } + "select" => { + selected = true; + cic.stop(); + break; + } + _ => { + unreachable!("Cannot get here.."); + } } + } - // they let this expire - if !selected { - return Ok(()); - } + // they let this expire + if !selected { + return Ok(()); + } - 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))), - }; - - 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() - )) + 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))), + }; + + 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.") }) - .await?; + .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?; - Ok(()) + Ok(()) } fn create_styles_interaction<'a>( - response: &'a mut EditInteractionResponse, - styles: &Vec, + 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 == "Google" { - opt.default_selection(true); - } - opt - }); - } - opts - }) - }) - }) - .create_action_row(|row| { - row.create_button(|btn| { - btn.custom_id("select") - .label("Select") - .style(ButtonStyle::Primary) - }) + 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 == "Google" { + opt.default_selection(true); + } + opt + }); + } + opts + }) }) - }) + }) + .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, + 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 - }) - }) - }) - .create_action_row(|row| { - row.create_button(|btn| { - btn.custom_id("select") - .label("Select") - .style(ButtonStyle::Primary) - }) - }) + 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 }) + }) + }) + .create_action_row(|row| { + row.create_button(|btn| { + btn.custom_id("select").label("Select").style(ButtonStyle::Primary) + }) + }) }) + }, + ) } diff --git a/src/slashcmds/help.rs b/src/slashcmds/help.rs index dde2d71..10fd4ba 100644 --- a/src/slashcmds/help.rs +++ b/src/slashcmds/help.rs @@ -1,63 +1,57 @@ use serenity::{ - client::Context, framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, - model::prelude::message_component::ButtonStyle, + client::Context, framework::standard::CommandResult, + model::interactions::application_command::ApplicationCommandInteraction, + model::prelude::message_component::ButtonStyle, }; use crate::{cache::ConfigCache, utls::constants::*}; pub async fn help(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandResult { - let data = ctx.data.read().await; - let botinfo = data.get::().unwrap().read().await; - let invite_link = botinfo.get("INVITE_LINK").unwrap(); - let dbl_link = botinfo.get("DISCORDBOTS_LINK").unwrap(); - let github_link = botinfo.get("GITHUB_LINK").unwrap(); - let stats_link = botinfo.get("STATS_LINK").unwrap(); - msg.create_interaction_response(&ctx.http, |resp| { - resp.interaction_response_data(|data| { - data.embed(|emb| { - emb.color(COLOR_OKAY) - .description( - "Hello! I can compile code for you. To compile code, \ + let data = ctx.data.read().await; + let botinfo = data.get::().unwrap().read().await; + let invite_link = botinfo.get("INVITE_LINK").unwrap(); + let dbl_link = botinfo.get("DISCORDBOTS_LINK").unwrap(); + let github_link = botinfo.get("GITHUB_LINK").unwrap(); + let stats_link = botinfo.get("STATS_LINK").unwrap(); + msg + .create_interaction_response(&ctx.http, |resp| { + resp.interaction_response_data(|data| { + data + .embed(|emb| { + emb + .color(COLOR_OKAY) + .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!", - ) - .thumbnail(ICON_HELP) - }) - .embed(|emb| { - emb.color(COLOR_WARN).description( - "If you are unfamiliar with Markdown, codeblocks can be created by \ + ) + .thumbnail(ICON_HELP) + }) + .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?; - debug!("Command executed"); - Ok(()) + debug!("Command executed"); + Ok(()) } diff --git a/src/slashcmds/invite.rs b/src/slashcmds/invite.rs index 9dbe7ad..6bf3757 100644 --- a/src/slashcmds/invite.rs +++ b/src/slashcmds/invite.rs @@ -1,26 +1,28 @@ use serenity::{ - framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, model::prelude::*, - prelude::*, + framework::standard::CommandResult, + model::interactions::application_command::ApplicationCommandInteraction, model::prelude::*, + prelude::*, }; 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_cache = config.read().await; - config_cache.get("INVITE_LINK").unwrap().clone() - }; + let invite_link = { + let data = ctx.data.read().await; + let config = data.get::().unwrap(); + let config_cache = config.read().await; + config_cache.get("INVITE_LINK").unwrap().clone() + }; - let emb = embeds::build_invite_embed(&invite_link); + let emb = embeds::build_invite_embed(&invite_link); - msg.create_interaction_response(&ctx.http, |resp| { - resp.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|data| data.add_embed(emb)) + msg + .create_interaction_response(&ctx.http, |resp| { + resp + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|data| data.add_embed(emb)) }) .await?; - debug!("Command executed"); - Ok(()) + debug!("Command executed"); + Ok(()) } diff --git a/src/slashcmds/ping.rs b/src/slashcmds/ping.rs index f9ba694..f6d2ebe 100644 --- a/src/slashcmds/ping.rs +++ b/src/slashcmds/ping.rs @@ -1,24 +1,27 @@ use serenity::{ - framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, model::prelude::*, - prelude::*, + framework::standard::CommandResult, + model::interactions::application_command::ApplicationCommandInteraction, model::prelude::*, + prelude::*, }; 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...")) + let old = Instant::now(); + msg + .create_interaction_response(&ctx.http, |resp| { + resp + .kind(InteractionResponseType::ChannelMessageWithSource) + .interaction_response_data(|data| data.content("๐Ÿ“ Pong!\n...")) }) .await?; - let new = Instant::now(); + let new = Instant::now(); - msg.edit_original_interaction_response(&ctx.http, |resp| { - resp.content(format!("๐Ÿ“ Pong!\n{} ms", (new - old).as_millis())) + msg + .edit_original_interaction_response(&ctx.http, |resp| { + resp.content(format!("๐Ÿ“ Pong!\n{} ms", (new - old).as_millis())) }) .await?; - debug!("Command executed"); - Ok(()) + debug!("Command executed"); + Ok(()) } diff --git a/src/stats/structures.rs b/src/stats/structures.rs index 1e8e41e..3624d3d 100644 --- a/src/stats/structures.rs +++ b/src/stats/structures.rs @@ -7,109 +7,93 @@ use std::sync::Arc; #[async_trait] pub trait Sendable: Serialize { - fn endpoint(&self) -> &'static str; - fn set_key(&mut self, key: &str); - async fn send( - &self, - client: Arc, - url: &str, - ) -> Result { - let url = format!("{}/{}", url, self.endpoint()); - debug!("Sending request to: {}", &url); - client - .post(&url) - .json(&self) - .header(USER_AGENT, "godbolt-rust-crate") - .header(ACCEPT, "application/json; charset=utf-8") - .send() - .await - } + fn endpoint(&self) -> &'static str; + fn set_key(&mut self, key: &str); + async fn send( + &self, + client: Arc, + url: &str, + ) -> Result { + let url = format!("{}/{}", url, self.endpoint()); + debug!("Sending request to: {}", &url); + client + .post(&url) + .json(&self) + .header(USER_AGENT, "godbolt-rust-crate") + .header(ACCEPT, "application/json; charset=utf-8") + .send() + .await + } } #[derive(Serialize)] pub struct CommandRequest { - key: String, - command: String, - guild: String, + key: String, + command: String, + guild: String, } impl CommandRequest { - pub fn new(command: &str, guild: Option) -> CommandRequest { - let mut guild_str = String::default(); - if let Some(g) = guild { - guild_str = g.0.to_string(); - } - CommandRequest { - key: String::from(""), - command: String::from(command), - guild: guild_str, - } + pub fn new(command: &str, guild: Option) -> CommandRequest { + let mut guild_str = String::default(); + if let Some(g) = guild { + guild_str = g.0.to_string(); } + CommandRequest { key: String::from(""), command: String::from(command), guild: guild_str } + } } impl Sendable for CommandRequest { - #[inline] - fn endpoint(&self) -> &'static str { - "insert/command" - } + #[inline] + fn endpoint(&self) -> &'static str { + "insert/command" + } - fn set_key(&mut self, key: &str) { - self.key = String::from(key); - } + fn set_key(&mut self, key: &str) { + self.key = String::from(key); + } } #[derive(Serialize)] pub struct LanguageRequest { - key: String, - language: String, - fail: bool, + key: String, + language: String, + fail: bool, } impl LanguageRequest { - pub fn new(language: &str, fail: bool) -> LanguageRequest { - LanguageRequest { - key: String::from(""), - language: String::from(language), - fail, - } - } + pub fn new(language: &str, fail: bool) -> LanguageRequest { + LanguageRequest { key: String::from(""), language: String::from(language), fail } + } } impl Sendable for LanguageRequest { - #[inline] - fn endpoint(&self) -> &'static str { - "insert/language" - } - fn set_key(&mut self, key: &str) { - self.key = String::from(key); - } + #[inline] + fn endpoint(&self) -> &'static str { + "insert/language" + } + fn set_key(&mut self, key: &str) { + self.key = String::from(key); + } } #[derive(Serialize)] pub struct LegacyRequest { - key: String, - #[serde(rename = "type")] - request_type: String, - #[serde(skip_serializing_if = "Option::is_none")] - amount: Option, + key: String, + #[serde(rename = "type")] + request_type: String, + #[serde(skip_serializing_if = "Option::is_none")] + amount: Option, } impl LegacyRequest { - pub fn new(amount: Option) -> LegacyRequest { - let request_type = if amount.is_some() { - "servers" - } else { - "request" - }; + pub fn new(amount: Option) -> LegacyRequest { + let request_type = if amount.is_some() { "servers" } else { "request" }; - LegacyRequest { - key: String::from(""), - request_type: String::from(request_type), - amount, - } - } + LegacyRequest { key: String::from(""), request_type: String::from(request_type), amount } + } } impl Sendable for LegacyRequest { - #[inline] - fn endpoint(&self) -> &'static str { - "insert/legacy" - } - fn set_key(&mut self, key: &str) { - self.key = String::from(key); - } + #[inline] + fn endpoint(&self) -> &'static str { + "insert/legacy" + } + fn set_key(&mut self, key: &str) { + self.key = String::from(key); + } } diff --git a/src/tests/boilerplate/cpp.rs b/src/tests/boilerplate/cpp.rs index 432c4bc..3e2e4cf 100644 --- a/src/tests/boilerplate/cpp.rs +++ b/src/tests/boilerplate/cpp.rs @@ -4,83 +4,83 @@ use crate::boilerplate::generator::BoilerPlateGenerator; #[tokio::test] async fn standard_needs_boilerplate() { - let gen = CppGenerator::new( - "std::string str = \"test\";\n\ + let gen = CppGenerator::new( + "std::string str = \"test\";\n\ std::cout << str;", - ); - assert!(gen.needs_boilerplate()); + ); + 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()); + ); + 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()); + ); + 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()); + ); + 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()); + ); + assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn no_bplate_emptystrlit() { - let gen = CppGenerator::new( - "#include \"stdio.h\" + let gen = CppGenerator::new( + "#include \"stdio.h\" int x = sizeof \"\" + '0'; int main() { printf(\"%d\n\", x); }", - ); - assert!(!gen.needs_boilerplate()); + ); + assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn no_bplate_narfd_main() { - let gen = CppGenerator::new( - "#include \"stdio.h\" + let gen = CppGenerator::new( + "#include \"stdio.h\" #define NARF void #define ZORT signed int x = '1'; ZORT main (NARF) { printf(\"%d\n\", x); }", - ); - assert!(!gen.needs_boilerplate()); + ); + assert!(!gen.needs_boilerplate()); } diff --git a/src/tests/boilerplate/java.rs b/src/tests/boilerplate/java.rs index ae65364..86c8651 100644 --- a/src/tests/boilerplate/java.rs +++ b/src/tests/boilerplate/java.rs @@ -4,141 +4,141 @@ use crate::boilerplate::java::JavaGenerator; #[tokio::test] async fn standard_needs_boilerplate() { - let gen = JavaGenerator::new( - "String str = \"test\";\n\ + let gen = JavaGenerator::new( + "String str = \"test\";\n\ System.out.println(str)", - ); - assert!(gen.needs_boilerplate()); + ); + 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", - ); - assert!(!gen.needs_boilerplate()); + ); + 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", - ); - assert!(!gen.needs_boilerplate()); + ); + 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", - ); - assert!(!gen.needs_boilerplate()); + ); + 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", - ); - assert!(!gen.needs_boilerplate()); + ); + 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", - ); - assert!(!gen.needs_boilerplate()); + ); + assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate6() { - let gen = JavaGenerator::new( - "class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ static public final void main (String[] args) {\n\ }\n\ }\n", - ); - assert!(!gen.needs_boilerplate()); + ); + assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate7() { - let gen = JavaGenerator::new( - "class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ static final public void main (String[] args) {\n\ }\n\ }\n", - ); - assert!(!gen.needs_boilerplate()); + ); + assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate8() { - let gen = JavaGenerator::new( - "class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ final static public void main (String[] args) {\n\ }\n\ }\n", - ); - assert!(!gen.needs_boilerplate()); + ); + assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate9() { - let gen = JavaGenerator::new( - "class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ final public static void main (String[] args) {\n\ }\n\ }\n", - ); - assert!(!gen.needs_boilerplate()); + ); + assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate10() { - let gen = JavaGenerator::new( - "class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ public @SuppressWarnings(\"all\") final static void main ( final String args[] ) {\n\ }\n\ }\n", - ); - assert!(!gen.needs_boilerplate()); + ); + assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate11() { - let gen = JavaGenerator::new( - "class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ public @SuppressWarnings(\"all\") static final void main ( final String args[] ) {\n\ }\n\ }\n", - ); - assert!(!gen.needs_boilerplate()); + ); + assert!(!gen.needs_boilerplate()); } #[tokio::test] async fn standard_doesnt_need_boilerplate12() { - let gen = JavaGenerator::new( - "class Test {\n\ + let gen = JavaGenerator::new( + "class Test {\n\ public @SuppressWarnings(\"all\") static void main ( final String args[] ) {\n\ }\n\ }\n", - ); - assert!(!gen.needs_boilerplate()); + ); + assert!(!gen.needs_boilerplate()); } diff --git a/src/tests/cpp.rs b/src/tests/cpp.rs index 04990a2..4dc4ea8 100644 --- a/src/tests/cpp.rs +++ b/src/tests/cpp.rs @@ -3,69 +3,69 @@ use crate::cppeval::eval::CppEval; #[tokio::test] async fn eval_output() { - let mut eval = CppEval::new("<< \"test\""); - assert!(eval.evaluate().is_ok()); + let mut eval = CppEval::new("<< \"test\""); + assert!(eval.evaluate().is_ok()); } #[tokio::test] async fn eval_output_implicit_main() { - let mut eval = CppEval::new("{cout << \"test\"}"); - assert!(eval.evaluate().is_ok()); + let mut eval = CppEval::new("{cout << \"test\"}"); + assert!(eval.evaluate().is_ok()); } #[tokio::test] async fn eval_output_explicit_main() { - let mut eval = CppEval::new("int main(void)\n{cout << \"test\";}"); - assert!(eval.evaluate().is_ok()); + let mut eval = CppEval::new("int main(void)\n{cout << \"test\";}"); + assert!(eval.evaluate().is_ok()); } #[tokio::test] async fn eval_output_balance_brace() { - let mut eval = CppEval::new("{cout << \"{{{{{\";}"); - assert!(eval.evaluate().is_ok()); + let mut eval = CppEval::new("{cout << \"{{{{{\";}"); + assert!(eval.evaluate().is_ok()); } #[tokio::test] async fn eval_output_balance_brace2() { - let mut eval = CppEval::new("{cout << \"}}}}}\";}"); - assert!(eval.evaluate().is_ok()); + let mut eval = CppEval::new("{cout << \"}}}}}\";}"); + assert!(eval.evaluate().is_ok()); } #[tokio::test] async fn eval_output_balance_brace_fail() { - let mut eval = CppEval::new("{ {cout << \"}}}}}\";}"); - assert!(eval.evaluate().is_err()); // expecting error + let mut eval = CppEval::new("{ {cout << \"}}}}}\";}"); + assert!(eval.evaluate().is_err()); // expecting error } #[tokio::test] async fn eval_output_custom_func() { - let mut eval = CppEval::new("<< f(2); int f(int a) { return a * 4; }"); - assert!(eval.evaluate().is_ok()); + let mut eval = CppEval::new("<< f(2); int f(int a) { return a * 4; }"); + assert!(eval.evaluate().is_ok()); } #[tokio::test] async fn eval_output_discord_escape() { - let mut eval = CppEval::new("`<< f(2); int f(int a) { return a * 4; }`"); - assert!(eval.evaluate().is_ok()); + let mut eval = CppEval::new("`<< f(2); int f(int a) { return a * 4; }`"); + assert!(eval.evaluate().is_ok()); } #[tokio::test] async fn eval_output_conditional() { - let mut eval = CppEval::new("{ int a = 4; if (a > 3) { cout << \"true\"; } }"); - assert!(eval.evaluate().is_ok()); + let mut eval = CppEval::new("{ int a = 4; if (a > 3) { cout << \"true\"; } }"); + assert!(eval.evaluate().is_ok()); } #[tokio::test] async fn eval_output_balance_inline() { - let mut eval = CppEval::new("{ // {{{{\n }"); - assert!(eval.evaluate().is_ok()); + let mut eval = CppEval::new("{ // {{{{\n }"); + assert!(eval.evaluate().is_ok()); } #[tokio::test] async fn eval_output_balance() { - let mut eval = CppEval::new("{ /* {{{ */ }"); - if let Err(e) = eval.evaluate() { - println!("{}", e); - panic!("Parser failed") - } + let mut eval = CppEval::new("{ /* {{{ */ }"); + if let Err(e) = eval.evaluate() { + println!("{}", e); + panic!("Parser failed") + } } diff --git a/src/tests/parser.rs b/src/tests/parser.rs index 305c2a2..3f6b312 100644 --- a/src/tests/parser.rs +++ b/src/tests/parser.rs @@ -7,292 +7,292 @@ use tokio::sync::RwLock; #[tokio::test] async fn standard_parse() { - let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile c++ + let dummy_user = User::default(); + let input = indoc::indoc!( + ";compile c++ ``` int main() {} ```" - ); - - let reply = None; - let result = get_components(input, &dummy_user, None, &reply).await; - if result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - assert_eq!(parser_result.target, "c++"); - assert_eq!(parser_result.args.len(), 0); - assert_eq!(parser_result.options.len(), 0); - assert_eq!(parser_result.stdin, ""); - assert_eq!(parser_result.url, ""); - assert_eq!(parser_result.code, "int main() {}\n"); + ); + + let reply = None; + let result = get_components(input, &dummy_user, None, &reply).await; + if result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + assert_eq!(parser_result.target, "c++"); + assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.options.len(), 0); + assert_eq!(parser_result.stdin, ""); + assert_eq!(parser_result.url, ""); + assert_eq!(parser_result.code, "int main() {}\n"); } #[tokio::test] async fn standard_parse_args() { - let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile c++ -O3 -Wall -Werror + let dummy_user = User::default(); + let input = indoc::indoc!( + ";compile c++ -O3 -Wall -Werror ``` int main() {} ```" - ); - - let reply = None; - let result = get_components(input, &dummy_user, None, &reply).await; - if result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - assert_eq!(parser_result.target, "c++"); - assert_eq!(parser_result.args.len(), 0); - assert_eq!(parser_result.options, ["-O3", "-Wall", "-Werror"]); - assert_eq!(parser_result.stdin, ""); - assert_eq!(parser_result.url, ""); - assert_eq!(parser_result.code, "int main() {}\n"); + ); + + let reply = None; + let result = get_components(input, &dummy_user, None, &reply).await; + if result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + assert_eq!(parser_result.target, "c++"); + assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.options, ["-O3", "-Wall", "-Werror"]); + assert_eq!(parser_result.stdin, ""); + assert_eq!(parser_result.url, ""); + assert_eq!(parser_result.code, "int main() {}\n"); } #[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 reply = None; - let result = get_components(input, &dummy_user, None, &reply).await; - if result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - println!("{:?}", &parser_result); - assert_eq!(parser_result.target, "c++"); - assert_eq!(parser_result.args, ["test1", "test2"]); - assert_eq!(parser_result.options.len(), 0); - assert_eq!(parser_result.stdin, ""); - assert_eq!(parser_result.url, "https://pastebin.com/raw/ERqDRZva"); - assert_eq!(parser_result.code, "int main() {}"); + let dummy_user = User::default(); + 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 result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + println!("{:?}", &parser_result); + assert_eq!(parser_result.target, "c++"); + assert_eq!(parser_result.args, ["test1", "test2"]); + assert_eq!(parser_result.options.len(), 0); + assert_eq!(parser_result.stdin, ""); + assert_eq!(parser_result.url, "https://pastebin.com/raw/ERqDRZva"); + assert_eq!(parser_result.code, "int main() {}"); } #[tokio::test] async fn standard_parse_url() { - let dummy_user = User::default(); - 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 result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - assert_eq!(parser_result.target, "c++"); - assert_eq!(parser_result.args.len(), 0); - assert_eq!(parser_result.options.len(), 0); - assert_eq!(parser_result.stdin, ""); - assert_eq!(parser_result.url, "https://pastebin.com/raw/ERqDRZva"); - assert_eq!(parser_result.code, "int main() {}"); + let dummy_user = User::default(); + 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 result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + assert_eq!(parser_result.target, "c++"); + assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.options.len(), 0); + assert_eq!(parser_result.stdin, ""); + assert_eq!(parser_result.url, "https://pastebin.com/raw/ERqDRZva"); + assert_eq!(parser_result.code, "int main() {}"); } #[tokio::test] async fn standard_parse_stdin() { - let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile c++ | testing 1 2 3 + let dummy_user = User::default(); + let input = indoc::indoc!( + ";compile c++ | testing 1 2 3 ``` int main() {} ```" - ); - - let reply = None; - let result = get_components(input, &dummy_user, None, &reply).await; - if result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - assert_eq!(parser_result.target, "c++"); - assert_eq!(parser_result.args.len(), 0); - assert_eq!(parser_result.options.len(), 0); - assert_eq!(parser_result.stdin, "testing 1 2 3"); - assert_eq!(parser_result.url, ""); - assert_eq!(parser_result.code, "int main() {}\n"); + ); + + let reply = None; + let result = get_components(input, &dummy_user, None, &reply).await; + if result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + assert_eq!(parser_result.target, "c++"); + assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.options.len(), 0); + assert_eq!(parser_result.stdin, "testing 1 2 3"); + assert_eq!(parser_result.url, ""); + assert_eq!(parser_result.code, "int main() {}\n"); } #[tokio::test] async fn standard_parse_block_stdin() { - let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile c++ + let dummy_user = User::default(); + let input = indoc::indoc!( + ";compile c++ ``` testing 1 2 3 ``` ``` int main() {} ```" - ); - - let reply = None; - let result = get_components(input, &dummy_user, None, &reply).await; - if result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - assert_eq!(parser_result.target, "c++"); - assert_eq!(parser_result.args.len(), 0); - assert_eq!(parser_result.options.len(), 0); - assert_eq!(parser_result.stdin, "testing 1 2 3\n"); - assert_eq!(parser_result.url, ""); - assert_eq!(parser_result.code, "int main() {}\n"); + ); + + let reply = None; + let result = get_components(input, &dummy_user, None, &reply).await; + if result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + assert_eq!(parser_result.target, "c++"); + assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.options.len(), 0); + assert_eq!(parser_result.stdin, "testing 1 2 3\n"); + assert_eq!(parser_result.url, ""); + assert_eq!(parser_result.code, "int main() {}\n"); } #[tokio::test] async fn standard_parse_deduce_compiler() { - let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile c++ + let dummy_user = User::default(); + let input = indoc::indoc!( + ";compile c++ ``` int main() {} ```" - ); - - 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 result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - assert_eq!(parser_result.target, "c++"); - assert_eq!(parser_result.args.len(), 0); - assert_eq!(parser_result.options.len(), 0); - assert_eq!(parser_result.stdin, ""); - assert_eq!(parser_result.url, ""); - assert_eq!(parser_result.code, "int main() {}\n"); + ); + + 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 result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + assert_eq!(parser_result.target, "c++"); + assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.options.len(), 0); + assert_eq!(parser_result.stdin, ""); + assert_eq!(parser_result.url, ""); + assert_eq!(parser_result.code, "int main() {}\n"); } #[tokio::test] async fn standard_parse_deduce_compiler_upper_case() { - let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile JAVASCRIPT + let dummy_user = User::default(); + let input = indoc::indoc!( + ";compile JAVASCRIPT ``` 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 result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - assert_eq!(parser_result.target, "javascript"); - assert_eq!(parser_result.args.len(), 0); - assert_eq!(parser_result.options.len(), 0); - assert_eq!(parser_result.stdin, ""); - assert_eq!(parser_result.url, ""); - assert_eq!(parser_result.code, "console.log(\"beehee\");\n"); + ); + + 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 result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + assert_eq!(parser_result.target, "javascript"); + assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.options.len(), 0); + assert_eq!(parser_result.stdin, ""); + assert_eq!(parser_result.url, ""); + assert_eq!(parser_result.code, "console.log(\"beehee\");\n"); } #[tokio::test] async fn standard_parse_late_deduce_compiler() { - let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile + let dummy_user = User::default(); + 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 result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - assert_eq!(parser_result.target, "javascript"); - assert_eq!(parser_result.args.len(), 0); - assert_eq!(parser_result.options.len(), 0); - assert_eq!(parser_result.stdin, ""); - assert_eq!(parser_result.url, ""); - assert_eq!(parser_result.code, "console.log(\"beehee\");\n"); + ); + + 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 result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + assert_eq!(parser_result.target, "javascript"); + assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.options.len(), 0); + assert_eq!(parser_result.stdin, ""); + assert_eq!(parser_result.url, ""); + assert_eq!(parser_result.code, "console.log(\"beehee\");\n"); } #[tokio::test] async fn standard_parse_late_deduce_compiler_block_stdin() { - let dummy_user = User::default(); - let input = indoc::indoc!( - ";compile + let dummy_user = User::default(); + let input = indoc::indoc!( + ";compile ``` testing 1 2 3 ``` ```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 result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - assert_eq!(parser_result.target, "javascript"); - assert_eq!(parser_result.args.len(), 0); - assert_eq!(parser_result.options.len(), 0); - assert_eq!(parser_result.stdin, "testing 1 2 3\n"); - assert_eq!(parser_result.url, ""); - assert_eq!(parser_result.code, "console.log(\"beehee\");\n"); + ); + + 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 result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + assert_eq!(parser_result.target, "javascript"); + assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.options.len(), 0); + assert_eq!(parser_result.stdin, "testing 1 2 3\n"); + assert_eq!(parser_result.url, ""); + assert_eq!(parser_result.code, "console.log(\"beehee\");\n"); } #[tokio::test] async fn standard_parse_one_line() { - let dummy_user = User::default(); - 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 result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - assert_eq!(parser_result.target, "javascript"); - assert_eq!(parser_result.args.len(), 0); - assert_eq!(parser_result.options.len(), 0); - assert_eq!(parser_result.stdin, ""); - assert_eq!(parser_result.url, ""); - assert_eq!(parser_result.code, "console.log(\"beehee\");"); + let dummy_user = User::default(); + 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 result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + assert_eq!(parser_result.target, "javascript"); + assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.options.len(), 0); + assert_eq!(parser_result.stdin, ""); + assert_eq!(parser_result.url, ""); + assert_eq!(parser_result.code, "console.log(\"beehee\");"); } #[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 reply = None; - let result = get_components(input, &dummy_user, None, &reply).await; - if result.is_err() { - panic!("Parser failed."); - } - - let parser_result = result.unwrap(); - assert_eq!(parser_result.target, "c"); - assert_eq!(parser_result.args.len(), 0); - assert_eq!(parser_result.options, ["-O3"]); - assert_eq!(parser_result.stdin, ""); - assert_eq!(parser_result.url, ""); - assert_eq!(parser_result.code, "int main() {return 232;}"); + let dummy_user = User::default(); + 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 result.is_err() { + panic!("Parser failed."); + } + + let parser_result = result.unwrap(); + assert_eq!(parser_result.target, "c"); + assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.options, ["-O3"]); + assert_eq!(parser_result.stdin, ""); + assert_eq!(parser_result.url, ""); + assert_eq!(parser_result.code, "int main() {return 232;}"); } diff --git a/src/utls/blocklist.rs b/src/utls/blocklist.rs index d24d3b1..d04656b 100644 --- a/src/utls/blocklist.rs +++ b/src/utls/blocklist.rs @@ -4,50 +4,48 @@ use serde::*; #[derive(Serialize, Deserialize, Default)] pub struct Blocklist { - list: Vec, + list: Vec, } impl Blocklist { - pub fn new() -> Blocklist { - let path = std::path::Path::new("blocklist.json"); - if !path.exists() { - return Blocklist::create_blocklist(); - } + pub fn new() -> Blocklist { + let path = std::path::Path::new("blocklist.json"); + if !path.exists() { + 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"); - list - } + let list: Blocklist = + serde_json::from_str(&json).expect("Unable to deserialize blocklist.json"); + list + } - pub fn contains(&self, snowflake: u64) -> bool { - self.list.contains(&snowflake.to_string()) - } + pub fn contains(&self, snowflake: u64) -> bool { + self.list.contains(&snowflake.to_string()) + } - pub fn block(&mut self, snowflake: u64) { - let snowflake = snowflake.to_string(); - self.list.push(snowflake); - self.write(); - } + 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) { - let snowflake = snowflake.to_string(); - self.list.retain(|x| *x != snowflake); - self.write(); - } + 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"); + pub fn write(&self) { + 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.write(); - list - } + fn create_blocklist() -> Blocklist { + let list = Blocklist { list: Default::default() }; + list.write(); + list + } } diff --git a/src/utls/constants.rs b/src/utls/constants.rs index abdeaba..dca2725 100644 --- a/src/utls/constants.rs +++ b/src/utls/constants.rs @@ -16,30 +16,25 @@ pub const COMPILER_ICON: &str = "http://i.michaelwflaherty.com/u/XedLoQWCVc.png" pub const MAX_OUTPUT_LEN: usize = 1 << 8; pub const MAX_ERROR_LEN: usize = 1 << 8; 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", -]; + 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"]; // 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 PHP_START_REGEX: Regex = - Regex::new("\"[^\"]*?\"|(?P<\\?php)").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(); + pub static ref PHP_START_REGEX: Regex = Regex::new("\"[^\"]*?\"|(?P<\\?php)").unwrap(); } // Other Regexes lazy_static! { - pub static ref JAVA_PUBLIC_CLASS_REGEX: Regex = - Regex::new("\"[^\"]*?\"|(?Ppublic)[\\s]+?class[\\s]*?").unwrap(); + pub static ref JAVA_PUBLIC_CLASS_REGEX: Regex = + Regex::new("\"[^\"]*?\"|(?Ppublic)[\\s]+?class[\\s]*?").unwrap(); } /* @@ -49,102 +44,102 @@ lazy_static! { 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"], - ["x86-64 clang 3.4.1", "clang341"], - ["x86-64 clang 3.8", "clang380"], - ["x86-64 clang 6.0.1", "clang601"], - ["x86-64 clang 7.1.0", "clang710"], - ["x86-64 clang 8.0.1", "clang801"], - ["x86-64 clang 9.0.1", "clang901"], - ["x86-64 gcc (trunk)", "gsnapshot"], - ["x86-64 gcc 11.2", "g112"], - ["x86-64 gcc 4.9.4", "g494"], - ["x86-64 gcc 5.5", "g550"], - ["x86-64 gcc 7.5", "g75"], - ["x86-64 gcc 9.1", "g91"], - ["x86-64 gcc 9.4", "g94"], - ["x86 msvc v19.31", "vcpp_v19_31_x86"], - ["x64 msvc v19.31", "vcpp_v19_31_x64"], - ["ARM gcc 9.3 (linux)", "arm930"], - ["ARM64 gcc 9.3", "arm64g930"], - ["arm64 msvc v19.31", "vcpp_v19_31_arm64"], - ["armv7-a clang 11.0.1", "armv7-clang1101"], - ["armv8-a clang 10.0.1", "armv8-clang1001"], - ["mips gcc 11.2.0", "mips1120"], - ["mips64 gcc 11.2.0", "mips112064"], + ["x86-64 clang (trunk)", "clang_trunk"], + ["x86-64 clang 13.0.1", "clang1301"], + ["x86-64 clang 14.0.0", "clang1400"], + ["x86-64 clang 3.4.1", "clang341"], + ["x86-64 clang 3.8", "clang380"], + ["x86-64 clang 6.0.1", "clang601"], + ["x86-64 clang 7.1.0", "clang710"], + ["x86-64 clang 8.0.1", "clang801"], + ["x86-64 clang 9.0.1", "clang901"], + ["x86-64 gcc (trunk)", "gsnapshot"], + ["x86-64 gcc 11.2", "g112"], + ["x86-64 gcc 4.9.4", "g494"], + ["x86-64 gcc 5.5", "g550"], + ["x86-64 gcc 7.5", "g75"], + ["x86-64 gcc 9.1", "g91"], + ["x86-64 gcc 9.4", "g94"], + ["x86 msvc v19.31", "vcpp_v19_31_x86"], + ["x64 msvc v19.31", "vcpp_v19_31_x64"], + ["ARM gcc 9.3 (linux)", "arm930"], + ["ARM64 gcc 9.3", "arm64g930"], + ["arm64 msvc v19.31", "vcpp_v19_31_arm64"], + ["armv7-a clang 11.0.1", "armv7-clang1101"], + ["armv8-a clang 10.0.1", "armv8-clang1001"], + ["mips gcc 11.2.0", "mips1120"], + ["mips64 gcc 11.2.0", "mips112064"], ]; 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"], - ["x86-64 clang 3.4.1", "clang341"], - ["x86-64 clang 3.8", "clang380"], - ["x86-64 clang 6.0.1", "clang601"], - ["x86-64 clang 7.1.0", "clang710"], - ["x86-64 clang 8.0.1", "clang801"], - ["x86-64 clang 9.0.1", "clang901"], - ["x86-64 gcc (trunk)", "gsnapshot"], - ["x86-64 gcc 11.2", "g112"], - ["x86-64 gcc 4.9.4", "g494"], - ["x86-64 gcc 5.5", "g550"], - ["x86-64 gcc 7.5", "g75"], - ["x86-64 gcc 9.1", "g91"], - ["x86-64 gcc 9.4", "g94"], - ["x86 msvc v19.31", "vcpp_v19_31_x86"], - ["x64 msvc v19.31", "vcpp_v19_31_x64"], + ["x86-64 clang (trunk)", "clang_trunk"], + ["x86-64 clang 13.0.1", "clang1301"], + ["x86-64 clang 14.0.0", "clang1400"], + ["x86-64 clang 3.4.1", "clang341"], + ["x86-64 clang 3.8", "clang380"], + ["x86-64 clang 6.0.1", "clang601"], + ["x86-64 clang 7.1.0", "clang710"], + ["x86-64 clang 8.0.1", "clang801"], + ["x86-64 clang 9.0.1", "clang901"], + ["x86-64 gcc (trunk)", "gsnapshot"], + ["x86-64 gcc 11.2", "g112"], + ["x86-64 gcc 4.9.4", "g494"], + ["x86-64 gcc 5.5", "g550"], + ["x86-64 gcc 7.5", "g75"], + ["x86-64 gcc 9.1", "g91"], + ["x86-64 gcc 9.4", "g94"], + ["x86 msvc v19.31", "vcpp_v19_31_x86"], + ["x64 msvc v19.31", "vcpp_v19_31_x64"], ]; 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"], - ["x86-64 clang 3.4.1", "cclang341"], - ["x86-64 clang 3.8", "cclang380"], - ["x86-64 clang 3.8.1", "cclang381"], - ["x86-64 clang 6.0.1", "cclang601"], - ["x86-64 clang 7.1.0", "cclang710"], - ["x86-64 clang 8.0.1", "cclang801"], - ["x86-64 clang 9.0.1", "cclang901"], - ["x86-64 gcc (trunk)", "cgsnapshot"], - ["x86-64 gcc 11.2", "cg112"], - ["x86-64 gcc 4.9.4", "cg494"], - ["x86-64 gcc 5.4", "cg540"], - ["x86-64 gcc 7.4", "cg74"], - ["x86-64 gcc 9.1", "cg91"], - ["x86-64 gcc 9.4", "cg94"], - ["ARM gcc 9.3 (linux)", "carm930"], - ["ARM64 gcc 9.3", "carm64g930"], - ["armv7-a clang 11.0.0", "armv7-cclang1100"], - ["armv8-a clang 10.0.1", "armv8-cclang1001"], - ["mips gcc 11.2.0", "cmips1120"], - ["mips64 gcc 11.2.0", "cmips112064"], + ["x86-64 clang (trunk)", "cclang_trunk"], + ["x86-64 clang 13.0.1", "cclang1301"], + ["x86-64 clang 14.0.0", "cclang1400"], + ["x86-64 clang 3.4.1", "cclang341"], + ["x86-64 clang 3.8", "cclang380"], + ["x86-64 clang 3.8.1", "cclang381"], + ["x86-64 clang 6.0.1", "cclang601"], + ["x86-64 clang 7.1.0", "cclang710"], + ["x86-64 clang 8.0.1", "cclang801"], + ["x86-64 clang 9.0.1", "cclang901"], + ["x86-64 gcc (trunk)", "cgsnapshot"], + ["x86-64 gcc 11.2", "cg112"], + ["x86-64 gcc 4.9.4", "cg494"], + ["x86-64 gcc 5.4", "cg540"], + ["x86-64 gcc 7.4", "cg74"], + ["x86-64 gcc 9.1", "cg91"], + ["x86-64 gcc 9.4", "cg94"], + ["ARM gcc 9.3 (linux)", "carm930"], + ["ARM64 gcc 9.3", "carm64g930"], + ["armv7-a clang 11.0.0", "armv7-cclang1100"], + ["armv8-a clang 10.0.1", "armv8-cclang1001"], + ["mips gcc 11.2.0", "cmips1120"], + ["mips64 gcc 11.2.0", "cmips112064"], ]; 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"], - ["x86-64 clang 3.4.1", "cclang341"], - ["x86-64 clang 3.8", "cclang380"], - ["x86-64 clang 3.8.1", "cclang381"], - ["x86-64 clang 6.0.1", "cclang601"], - ["x86-64 clang 7.1.0", "cclang710"], - ["x86-64 clang 8.0.1", "cclang801"], - ["x86-64 clang 9.0.1", "cclang901"], - ["x86-64 gcc (trunk)", "cgsnapshot"], - ["x86-64 gcc 11.2", "cg112"], - ["x86-64 gcc 4.9.4", "cg494"], - ["x86-64 gcc 5.4", "cg540"], - ["x86-64 gcc 7.4", "cg74"], - ["x86-64 gcc 9.1", "cg91"], - ["x86-64 gcc 9.4", "cg94"], - ["ARM gcc 9.3 (linux)", "carm930"], - ["ARM64 gcc 9.3", "carm64g930"], - ["armv7-a clang 11.0.0", "armv7-cclang1100"], - ["armv8-a clang 10.0.1", "armv8-cclang1001"], - ["mips gcc 11.2.0", "cmips1120"], - ["mips64 gcc 11.2.0", "cmips112064"], + ["x86-64 clang (trunk)", "cclang_trunk"], + ["x86-64 clang 13.0.1", "cclang1301"], + ["x86-64 clang 14.0.0", "cclang1400"], + ["x86-64 clang 3.4.1", "cclang341"], + ["x86-64 clang 3.8", "cclang380"], + ["x86-64 clang 3.8.1", "cclang381"], + ["x86-64 clang 6.0.1", "cclang601"], + ["x86-64 clang 7.1.0", "cclang710"], + ["x86-64 clang 8.0.1", "cclang801"], + ["x86-64 clang 9.0.1", "cclang901"], + ["x86-64 gcc (trunk)", "cgsnapshot"], + ["x86-64 gcc 11.2", "cg112"], + ["x86-64 gcc 4.9.4", "cg494"], + ["x86-64 gcc 5.4", "cg540"], + ["x86-64 gcc 7.4", "cg74"], + ["x86-64 gcc 9.1", "cg91"], + ["x86-64 gcc 9.4", "cg94"], + ["ARM gcc 9.3 (linux)", "carm930"], + ["ARM64 gcc 9.3", "carm64g930"], + ["armv7-a clang 11.0.0", "armv7-cclang1100"], + ["armv8-a clang 10.0.1", "armv8-cclang1001"], + ["mips gcc 11.2.0", "cmips1120"], + ["mips64 gcc 11.2.0", "cmips112064"], ]; diff --git a/src/utls/discordhelpers/embeds.rs b/src/utls/discordhelpers/embeds.rs index 1852723..cf1c86a 100644 --- a/src/utls/discordhelpers/embeds.rs +++ b/src/utls/discordhelpers/embeds.rs @@ -2,9 +2,9 @@ use std::fmt::Write as _; use std::{env, str}; use serenity::{ - builder::{CreateEmbed, CreateMessage}, - client::Context, - model::prelude::*, + builder::{CreateEmbed, CreateMessage}, + client::Context, + model::prelude::*, }; use wandbox::*; @@ -14,364 +14,353 @@ use crate::utls::discordhelpers; #[derive(Default)] pub struct EmbedOptions { - pub is_assembly: bool, - pub lang: String, - pub compiler: String, + pub is_assembly: bool, + pub lang: String, + pub compiler: String, } impl EmbedOptions { - pub fn new(is_assembly: bool, lang: String, compiler: String) -> Self { - EmbedOptions { - is_assembly, - lang, - compiler, - } - } + pub fn new(is_assembly: bool, lang: String, compiler: String) -> Self { + EmbedOptions { is_assembly, lang, compiler } + } } pub trait ToEmbed { - fn to_embed(self, author: &User, options: &EmbedOptions) -> CreateEmbed; + fn to_embed(self, author: &User, options: &EmbedOptions) -> CreateEmbed; } impl ToEmbed for wandbox::CompilationResult { - fn to_embed(self, author: &User, options: &EmbedOptions) -> CreateEmbed { - let mut embed = CreateEmbed::default(); - - if !self.status.is_empty() { - if self.status != "0" { - embed.color(COLOR_FAIL); - } else { - embed.color(COLOR_OKAY); - } - } + fn to_embed(self, author: &User, options: &EmbedOptions) -> CreateEmbed { + let mut embed = CreateEmbed::default(); - if !self.signal.is_empty() { - // If we received 'Signal', then the application successfully ran, but was timed out - // by wandbox. We should skin this as successful, so we set status to 0 (success). - // This is done to ensure that the checkmark is added at the end of the compile - // command hook. - embed.color(COLOR_OKAY); - } - if !self.compiler_all.is_empty() { - 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() { - let str = discordhelpers::conform_external_str(&self.program_all, MAX_OUTPUT_LEN); - embed.field("Program Output", format!("```\n{}\n```", str), false); - } - if !self.url.is_empty() { - embed.field("URL", &self.url, false); - } + if !self.status.is_empty() { + if self.status != "0" { + embed.color(COLOR_FAIL); + } else { + embed.color(COLOR_OKAY); + } + } - embed.footer(|f| { - let mut text = author.tag(); + if !self.signal.is_empty() { + // If we received 'Signal', then the application successfully ran, but was timed out + // by wandbox. We should skin this as successful, so we set status to 0 (success). + // This is done to ensure that the checkmark is added at the end of the compile + // command hook. + embed.color(COLOR_OKAY); + } + if !self.compiler_all.is_empty() { + 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() { + let str = discordhelpers::conform_external_str(&self.program_all, MAX_OUTPUT_LEN); + embed.field("Program Output", format!("```\n{}\n```", str), false); + } + if !self.url.is_empty() { + embed.field("URL", &self.url, false); + } - if !options.lang.is_empty() { - text = format!("{} | {}", text, options.lang); - } - if !options.compiler.is_empty() { - text = format!("{} | {}", text, options.compiler); - } + embed.footer(|f| { + let mut text = author.tag(); - text = format!("{} | CV NSight", text); - f.text(text) - }); - embed - } + if !options.lang.is_empty() { + text = format!("{} | {}", text, options.lang); + } + if !options.compiler.is_empty() { + text = format!("{} | {}", text, options.compiler); + } + + text = format!("{} | CV NSight", text); + f.text(text) + }); + embed + } } impl ToEmbed for godbolt::GodboltResponse { - fn to_embed(self, author: &User, options: &EmbedOptions) -> CreateEmbed { - let mut embed = CreateEmbed::default(); - - if self.code == 0 { - embed.color(COLOR_OKAY); - } else { - embed.color(COLOR_FAIL); - - // if it's an assembly request let's just handle the error case here. - if options.is_assembly { - let mut errs = String::new(); - for err_res in &self.stderr { - let line = format!("{}\n", &err_res.text); - errs.push_str(&line); - } - - let compliant_str = discordhelpers::conform_external_str(&errs, MAX_ERROR_LEN); - embed.field( - "Compilation Errors", - format!("```\n{}```", compliant_str), - false, - ); - return embed; - } - }; - - if options.is_assembly { - let mut pieces: Vec = Vec::new(); - let mut append: String = String::new(); - if let Some(vec) = &self.asm { - for asm in vec { - if let Some(text) = &asm.text { - if append.len() + text.len() > 1000 { - pieces.push(append.clone()); - append.clear() - } - // append.push_str(&format!("{}\n", text)); - writeln!(append, "{}", text).unwrap(); - } - } - } + fn to_embed(self, author: &User, options: &EmbedOptions) -> CreateEmbed { + let mut embed = CreateEmbed::default(); - let mut output = false; - let mut i = 1; - for str in pieces { - let title = format!("Assembly Output Pt. {}", i); - // let piece = discordhelpers::conform_external_str(&str, MAX_OUTPUT_LEN); - let piece = str.replace('`', "\u{200B}`"); - embed.field(&title, format!("```x86asm\n{}\n```", &piece), false); - output = true; - i += 1; - } - if !append.is_empty() { - let title = if i > 1 { - format!("Assembly Output Pt. {}", i) - } else { - String::from("Assembly Output") - }; - - // let piece = discordhelpers::conform_external_str(&append, MAX_OUTPUT_LEN); - let piece = append.replace('`', "\u{200B}`"); - embed.field(&title, format!("```x86asm\n{}\n```", &piece), false); - output = true; - } + if self.code == 0 { + embed.color(COLOR_OKAY); + } else { + embed.color(COLOR_FAIL); + + // if it's an assembly request let's just handle the error case here. + if options.is_assembly { + let mut errs = String::new(); + for err_res in &self.stderr { + let line = format!("{}\n", &err_res.text); + errs.push_str(&line); + } - if !output { - embed.title("Compilation successful"); - embed.description("No assembly generated."); + let compliant_str = discordhelpers::conform_external_str(&errs, MAX_ERROR_LEN); + embed.field("Compilation Errors", format!("```\n{}```", compliant_str), false); + return embed; + } + }; + + if options.is_assembly { + let mut pieces: Vec = Vec::new(); + let mut append: String = String::new(); + if let Some(vec) = &self.asm { + for asm in vec { + if let Some(text) = &asm.text { + if append.len() + text.len() > 1000 { + pieces.push(append.clone()); + append.clear() } + // append.push_str(&format!("{}\n", text)); + writeln!(append, "{}", text).unwrap(); + } + } + } + + let mut output = false; + let mut i = 1; + for str in pieces { + let title = format!("Assembly Output Pt. {}", i); + // let piece = discordhelpers::conform_external_str(&str, MAX_OUTPUT_LEN); + let piece = str.replace('`', "\u{200B}`"); + embed.field(&title, format!("```x86asm\n{}\n```", &piece), false); + output = true; + i += 1; + } + if !append.is_empty() { + let title = if i > 1 { + format!("Assembly Output Pt. {}", i) } else { - let mut output = String::default(); - for line in &self.stdout { - // output.push_str(&format!("{}\n", line.text)); - writeln!(output, "{}", line.text).unwrap(); - } - - let mut errs = String::default(); - if let Some(build_result) = self.build_result { - if let Some(errors) = build_result.stderr { - for line in errors { - // errs.push_str(&format!("{}\n", line.text)); - writeln!(errs, "{}", line.text).unwrap(); - } - } - } - for line in &self.stderr { - // errs.push_str(&format!("{}\n", line.text)); - writeln!(errs, "{}", line.text).unwrap(); - } - - let stdout = output.trim(); - let stderr = errs.trim(); - let mut output = false; - if !stdout.is_empty() { - let str = discordhelpers::conform_external_str(stdout, MAX_OUTPUT_LEN); - embed.field("Program Output", format!("```\n{}\n```", str), false); - output = true; - } - if !stderr.is_empty() { - output = true; - let str = discordhelpers::conform_external_str(stderr, MAX_ERROR_LEN); - embed.field("Compiler Output", format!("```\n{}\n```", str), false); - } - - if !output { - embed.title("Compilation successful"); - embed.description("No output."); - } + String::from("Assembly Output") + }; - // Execution time can be displayed here, but I don't think it's useful enough - // to show... - //embed.field("Execution Time", format!("`{}ms`", self.execution_time), true); - } + // let piece = discordhelpers::conform_external_str(&append, MAX_OUTPUT_LEN); + let piece = append.replace('`', "\u{200B}`"); + embed.field(&title, format!("```x86asm\n{}\n```", &piece), false); + output = true; + } - let mut appendstr = author.tag(); - if let Some(time) = self.execution_time { - appendstr = format!("{} | {}ms", appendstr, time); - } - if !options.lang.is_empty() { - appendstr = format!("{} | {}", appendstr, options.lang); - } - if !options.compiler.is_empty() { - appendstr = format!("{} | {}", appendstr, options.compiler); + if !output { + embed.title("Compilation successful"); + embed.description("No assembly generated."); + } + } else { + let mut output = String::default(); + for line in &self.stdout { + // output.push_str(&format!("{}\n", line.text)); + writeln!(output, "{}", line.text).unwrap(); + } + + let mut errs = String::default(); + if let Some(build_result) = self.build_result { + if let Some(errors) = build_result.stderr { + for line in errors { + // errs.push_str(&format!("{}\n", line.text)); + writeln!(errs, "{}", line.text).unwrap(); + } } + } + for line in &self.stderr { + // errs.push_str(&format!("{}\n", line.text)); + writeln!(errs, "{}", line.text).unwrap(); + } + + let stdout = output.trim(); + let stderr = errs.trim(); + let mut output = false; + if !stdout.is_empty() { + let str = discordhelpers::conform_external_str(stdout, MAX_OUTPUT_LEN); + embed.field("Program Output", format!("```\n{}\n```", str), false); + output = true; + } + if !stderr.is_empty() { + output = true; + let str = discordhelpers::conform_external_str(stderr, MAX_ERROR_LEN); + embed.field("Compiler Output", format!("```\n{}\n```", str), false); + } + + if !output { + embed.title("Compilation successful"); + embed.description("No output."); + } + + // Execution time can be displayed here, but I don't think it's useful enough + // to show... + //embed.field("Execution Time", format!("`{}ms`", self.execution_time), true); + } - embed.footer(|f| f.text(format!("{} | MS Azure", appendstr))); - embed + let mut appendstr = author.tag(); + if let Some(time) = self.execution_time { + appendstr = format!("{} | {}ms", appendstr, time); } + if !options.lang.is_empty() { + appendstr = format!("{} | {}", appendstr, options.lang); + } + if !options.compiler.is_empty() { + appendstr = format!("{} | {}", appendstr, options.compiler); + } + + embed.footer(|f| f.text(format!("{} | MS Azure", 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; + 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 { - let mut embed = CreateEmbed::default(); - if res.status != "0" { - embed.color(COLOR_FAIL); - } else { - embed.color(COLOR_OKAY); - } - - if !res.compiler_all.is_empty() { - let str = discordhelpers::conform_external_str(&res.compiler_all, MAX_ERROR_LEN); - embed.field("Compiler Output", format!("```{}\n```", str), false); - } - if !res.program_all.is_empty() { - let str = discordhelpers::conform_external_str(&res.program_all, MAX_OUTPUT_LEN); - embed.description(format!("```\n{}\n```", str)); - } - embed.footer(|f| { - f.text(format!( - "Requested by: {} | Powered by NVidia", - author.tag() - )) - }); - - embed + let mut embed = CreateEmbed::default(); + if res.status != "0" { + embed.color(COLOR_FAIL); + } else { + embed.color(COLOR_OKAY); + } + + if !res.compiler_all.is_empty() { + let str = discordhelpers::conform_external_str(&res.compiler_all, MAX_ERROR_LEN); + embed.field("Compiler Output", format!("```{}\n```", str), false); + } + if !res.program_all.is_empty() { + let str = discordhelpers::conform_external_str(&res.program_all, MAX_OUTPUT_LEN); + embed.description(format!("```\n{}\n```", str)); + } + embed.footer(|f| f.text(format!("Requested by: {} | Powered by NVidia", author.tag()))); + + embed } pub fn embed_message(emb: CreateEmbed) -> CreateMessage<'static> { - let mut msg = CreateMessage::default(); - msg.embed(|e| { - e.0 = emb.0; - e - }); - msg + let mut msg = CreateMessage::default(); + msg.embed(|e| { + e.0 = emb.0; + e + }); + msg } pub fn build_dblvote_embed(tag: String) -> CreateEmbed { - let mut embed = CreateEmbed::default(); - embed.color(COLOR_OKAY); - embed.description(format!("{} voted for us on top.gg!", tag)); - embed.thumbnail(ICON_VOTE); - embed + let mut embed = CreateEmbed::default(); + embed.color(COLOR_OKAY); + embed.description(format!("{} voted for us on top.gg!", tag)); + embed.thumbnail(ICON_VOTE); + embed } pub fn panic_embed(panic_info: String) -> CreateEmbed { - let mut embed = CreateEmbed::default(); - embed.title("Oopsie"); - embed.description(format!("```\n{}\n```", panic_info)); - embed + let mut embed = CreateEmbed::default(); + embed.title("Oopsie"); + embed.description(format!("```\n{}\n```", panic_info)); + embed } pub fn build_welcome_embed() -> CreateEmbed { - let mut embed = CreateEmbed::default(); - let prefix = env::var("BOT_PREFIX").expect("Bot prefix is not set!"); - embed.title("Discord Compiler"); - embed.color(COLOR_OKAY); - 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", - format!("{}compile python\n```py\nprint('hello world')\n```", prefix), - true, - ); - embed.field("Learning Time!", "If you like reading the manuals of things, read our [getting started](https://github.com/ThomasByr/discord-compiler-bot/wiki) 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 [GitHub](https://github.com/ThomasByr/discord-compiler-bot) and we'll give you a hand.", true); - embed.footer(|f| f.text("powered by Azure & NVidia // created by ThomasByr")); - embed + let mut embed = CreateEmbed::default(); + let prefix = env::var("BOT_PREFIX").expect("Bot prefix is not set!"); + embed.title("Discord Compiler"); + embed.color(COLOR_OKAY); + 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", + format!("{}compile python\n```py\nprint('hello world')\n```", prefix), + true, + ); + embed.field("Learning Time!", "If you like reading the manuals of things, read our [getting started](https://github.com/ThomasByr/discord-compiler-bot/wiki) 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 [GitHub](https://github.com/ThomasByr/discord-compiler-bot) and we'll give you a hand.", true); + embed.footer(|f| f.text("powered by Azure & NVidia // created by ThomasByr")); + embed } 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 - ); - embed.description(description); - embed + 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); + embed.description(description); + embed } pub fn build_join_embed(guild: &Guild) -> CreateEmbed { - let mut embed = CreateEmbed::default(); - embed.title("Guild joined"); - embed.color(COLOR_OKAY); - embed.field("Name", guild.name.clone(), true); - embed.field("Members", guild.member_count, true); - embed.field("Channels", guild.channels.len(), true); - if let Some(icon) = guild.icon_url() { - embed.thumbnail(icon); - } - embed.field("Guild ID", guild.id, true); - embed + let mut embed = CreateEmbed::default(); + embed.title("Guild joined"); + embed.color(COLOR_OKAY); + embed.field("Name", guild.name.clone(), true); + embed.field("Members", guild.member_count, true); + embed.field("Channels", guild.channels.len(), true); + if let Some(icon) = guild.icon_url() { + embed.thumbnail(icon); + } + embed.field("Guild ID", guild.id, true); + embed } pub fn build_leave_embed(guild: &GuildId) -> CreateEmbed { - let mut embed = CreateEmbed::default(); - embed.title("Guild left"); - embed.color(COLOR_FAIL); - embed.field("ID", format!("{}", guild.0), true); - embed + let mut embed = CreateEmbed::default(); + embed.title("Guild left"); + embed.color(COLOR_FAIL); + embed.field("ID", format!("{}", guild.0), true); + embed } pub fn build_complog_embed( - success: bool, - input_code: &str, - lang: &str, - tag: &str, - id: u64, - guild: &str, + success: bool, + input_code: &str, + lang: &str, + tag: &str, + id: u64, + guild: &str, ) -> CreateEmbed { - let mut embed = CreateEmbed::default(); - if !success { - embed.color(COLOR_FAIL); - } else { - embed.color(COLOR_OKAY); - } - embed.title("Compilation requested"); - embed.field("Language", lang, true); - embed.field("Author", tag, true); - embed.field("Author ID", id, true); - embed.field("Guild", guild, true); - let mut code = String::from(input_code); - if code.len() > MAX_OUTPUT_LEN { - code = code.chars().take(MAX_OUTPUT_LEN).collect() - } - embed.field("Code", format!("```{}\n{}\n```", lang, code), false); - - embed + let mut embed = CreateEmbed::default(); + if !success { + embed.color(COLOR_FAIL); + } else { + embed.color(COLOR_OKAY); + } + embed.title("Compilation requested"); + embed.field("Language", lang, true); + embed.field("Author", tag, true); + embed.field("Author ID", id, true); + embed.field("Guild", guild, true); + let mut code = String::from(input_code); + if code.len() > MAX_OUTPUT_LEN { + code = code.chars().take(MAX_OUTPUT_LEN).collect() + } + embed.field("Code", format!("```{}\n{}\n```", lang, code), false); + + embed } pub fn build_fail_embed(author: &User, err: &str) -> CreateEmbed { - let mut embed = CreateEmbed::default(); - embed.color(COLOR_FAIL); - embed.title("Critical error:"); - embed.description(err); - embed.thumbnail(ICON_FAIL); - embed.footer(|f| f.text(format!("Requested by: {}", author.tag()))); - embed + let mut embed = CreateEmbed::default(); + embed.color(COLOR_FAIL); + embed.title("Critical error:"); + embed.description(err); + embed.thumbnail(ICON_FAIL); + embed.footer(|f| f.text(format!("Requested by: {}", author.tag()))); + embed } 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 \ + 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 \ If you are unhappy with your results please start a new compilation request \ and dismiss this message.", - ); - embed + ); + embed } diff --git a/src/utls/discordhelpers/interactions.rs b/src/utls/discordhelpers/interactions.rs index 8ef5c77..1e65043 100644 --- a/src/utls/discordhelpers/interactions.rs +++ b/src/utls/discordhelpers/interactions.rs @@ -3,597 +3,553 @@ 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::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, + builder::{CreateComponents, CreateEmbed, CreateInteractionResponse, CreateSelectMenuOption}, + client::Context, + framework::standard::CommandError, + 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, }; use crate::cache::ConfigCache; use crate::{ - cache::CompilerCache, - cache::StatsManagerCache, - managers::compilation::RequestHandler, - utls::constants::COLOR_WARN, - 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::parser::ParserResult, - utls::{discordhelpers, parser}, + cache::CompilerCache, + cache::StatsManagerCache, + managers::compilation::RequestHandler, + utls::constants::COLOR_WARN, + 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::parser::ParserResult, + utls::{discordhelpers, parser}, }; 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)) - }) + 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)) + }) + }) + .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 + }); + components } pub async fn create_more_options_panel( - ctx: &Context, - interaction: Arc, - parse_result: &mut ParserResult, + 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) - }) - }) - }) + 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) }) - }) - .await - .unwrap(); - - println!("awaiting response..."); - let msg = interaction.get_interaction_response(&ctx.http).await?; - println!("response got..."); - if let Some(resp) = msg.await_modal_interaction(&ctx.shard).await { - println!("response: {:?}", resp.kind); - if let ActionRowComponent::InputText(input) = &resp.data.components[0].components[0] { - parse_result.options = input - .value - .clone() - .split(' ') - .map(|p| p.to_owned()) - .collect(); - } - if let ActionRowComponent::InputText(input) = &resp.data.components[1].components[0] { - parse_result.args = input.value.split(' ').map(|p| p.to_owned()).collect(); - } - if let ActionRowComponent::InputText(input) = &resp.data.components[2].components[0] { - parse_result.stdin = input.value.clone(); - } - resp.defer(&ctx.http).await?; - Ok(Some(resp.clone())) - } else { - Ok(None) + }) + .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(); + + println!("awaiting response..."); + let msg = interaction.get_interaction_response(&ctx.http).await?; + println!("response got..."); + if let Some(resp) = msg.await_modal_interaction(&ctx.shard).await { + println!("response: {:?}", resp.kind); + if let ActionRowComponent::InputText(input) = &resp.data.components[0].components[0] { + parse_result.options = input.value.clone().split(' ').map(|p| p.to_owned()).collect(); + } + if let ActionRowComponent::InputText(input) = &resp.data.components[1].components[0] { + parse_result.args = input.value.split(' ').map(|p| p.to_owned()).collect(); + } + if let ActionRowComponent::InputText(input) = &resp.data.components[2].components[0] { + parse_result.stdin = input.value.clone(); } + resp.defer(&ctx.http).await?; + Ok(Some(resp.clone())) + } else { + Ok(None) + } } pub async fn create_compiler_options( - ctx: &Context, - language: &str, - is_assembly: bool, + ctx: &Context, + language: &str, + is_assembly: bool, ) -> Result, CommandError> { - let mut options = Vec::new(); - - let data = ctx.data.read().await; - let compilers = data.get::().unwrap().read().await; - let target = compilers.resolve_target(language); - match target { - RequestHandler::None => { - return Err(CommandError::from(format!( - "Unsupported language: {}", - language - ))) + let mut options = Vec::new(); + + let data = ctx.data.read().await; + let compilers = data.get::().unwrap().read().await; + let target = compilers.resolve_target(language); + match target { + RequestHandler::None => { + return Err(CommandError::from(format!("Unsupported language: {}", language))) + } + RequestHandler::WandBox => { + let wbox = compilers.wbox.as_ref().unwrap(); + let compilers = wbox.get_compilers(language).unwrap(); + let mut first = true; + for compiler in if compilers.len() > 25 { &compilers[..24] } else { &compilers } { + let mut option = CreateSelectMenuOption::default(); + if first { + option.default_selection(true); + first = !first; } - RequestHandler::WandBox => { - let wbox = compilers.wbox.as_ref().unwrap(); - let compilers = wbox.get_compilers(language).unwrap(); - let mut first = true; - for compiler in if compilers.len() > 25 { - &compilers[..24] - } else { - &compilers - } { - let mut option = CreateSelectMenuOption::default(); - if first { - option.default_selection(true); - first = !first; - } - option.label(&compiler.name); - option.value(&compiler.name); - option.description(&compiler.version); + option.label(&compiler.name); + option.value(&compiler.name); + option.description(&compiler.version); - options.push(option); - } + options.push(option); + } + } + RequestHandler::CompilerExplorer => { + let mut default = None; + let mut list = None; + + for cache in &compilers.gbolt.as_ref().unwrap().cache { + if cache.language.name.to_lowercase() == language { + list = Some(cache.compilers.iter().map(|c| [c.name.as_str(), c.id.as_str()]).collect()); + default = Some(&cache.language.default_compiler) } - RequestHandler::CompilerExplorer => { - let mut default = None; - let mut list = None; - - for cache in &compilers.gbolt.as_ref().unwrap().cache { - if cache.language.name.to_lowercase() == language { - list = Some( - cache - .compilers - .iter() - .map(|c| [c.name.as_str(), c.id.as_str()]) - .collect(), - ); - default = Some(&cache.language.default_compiler) - } - } - - // override list for languages with plenty of compilers - if language == "c++" { - if is_assembly { - list = Some(CPP_ASM_COMPILERS.to_vec()); - } else { - list = Some(CPP_EXEC_COMPILERS.to_vec()); - } - } else if language == "c" { - if is_assembly { - list = Some(C_ASM_COMPILERS.to_vec()); - } else { - list = Some(C_EXEC_COMPILERS.to_vec()); - } - } - - if list.is_none() { - warn!("No suitable compilers found for: {}", &language); - return Err(CommandError::from(format!( - "No suitable compilers found for: {}", - language - ))); - } - - for compiler in list.unwrap() { - let mut option = CreateSelectMenuOption::default(); - option.label(compiler[0]); - option.value(compiler[1]); - if let Some(def) = default { - if def.as_str() == compiler[1] { - option.default_selection(true); - } - } - options.push(option); - } + } + + // override list for languages with plenty of compilers + if language == "c++" { + if is_assembly { + list = Some(CPP_ASM_COMPILERS.to_vec()); + } else { + list = Some(CPP_EXEC_COMPILERS.to_vec()); + } + } else if language == "c" { + if is_assembly { + list = Some(C_ASM_COMPILERS.to_vec()); + } else { + list = Some(C_EXEC_COMPILERS.to_vec()); + } + } + + if list.is_none() { + warn!("No suitable compilers found for: {}", &language); + return Err(CommandError::from(format!("No suitable compilers found for: {}", language))); + } + + for compiler in list.unwrap() { + let mut option = CreateSelectMenuOption::default(); + option.label(compiler[0]); + option.value(compiler[1]); + if let Some(def) = default { + if def.as_str() == compiler[1] { + option.default_selection(true); + } } + options.push(option); + } } + } - if options.len() > 25 { - options.drain(25..); - } + if options.len() > 25 { + options.drain(25..); + } - Ok(options) + Ok(options) } pub fn edit_to_confirmation_interaction<'a>( - result: &CreateEmbed, - resp: &'a mut EditInteractionResponse, + 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()) - .create_action_row(|row| { - row.create_button(|btn| { - btn.custom_id("publish") - .label("Publish") - .style(ButtonStyle::Primary) - }) - }) - }) + resp.set_embeds(Vec::from([build_publish_embed(), result.clone()])).components(|components| { + components.set_action_rows(Vec::new()).create_action_row(|row| { + row.create_button(|btn| btn.custom_id("publish").label("Publish").style(ButtonStyle::Primary)) + }) + }) } pub fn create_language_interaction<'this, 'a>( - resp: &'this mut CreateInteractionResponse<'a>, - languages: &[&str], + resp: &'this mut CreateInteractionResponse<'a>, + languages: &[&str], ) -> &'this mut CreateInteractionResponse<'a> { - resp.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|data| { - 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); - option.label(language); - opts.add_option(option); - } - - opts - }) - }) - }) - }) + resp.kind(InteractionResponseType::ChannelMessageWithSource).interaction_response_data(|data| { + 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); + option.label(language); + opts.add_option(option); + } + + opts + }) + }) }) + }) + }) } pub fn create_dismiss_response<'this, 'a>( - resp: &'this mut CreateInteractionResponse<'a>, + resp: &'this mut CreateInteractionResponse<'a>, ) -> &'this mut CreateInteractionResponse<'a> { - resp.kind(InteractionResponseType::UpdateMessage) - .interaction_response_data(|data| { - data.set_embeds(Vec::new()) - .embed(|emb| { - emb.color(COLOR_OKAY) - .description("Interaction completed, you may safely dismiss this message.") - }) - .components(|components| components.set_action_rows(Vec::new())) - }) + resp.kind(InteractionResponseType::UpdateMessage).interaction_response_data(|data| { + data + .set_embeds(Vec::new()) + .embed(|emb| { + emb + .color(COLOR_OKAY) + .description("Interaction completed, you may safely dismiss this message.") + }) + .components(|components| components.set_action_rows(Vec::new())) + }) } pub fn edit_to_dismiss_response( - edit: &mut EditInteractionResponse, + edit: &mut EditInteractionResponse, ) -> &mut EditInteractionResponse { - edit.set_embeds(Vec::new()) - .embed(|emb| { - emb.color(COLOR_OKAY) - .description("Interaction completed, you may safely dismiss this message.") - }) - .components(|components| components.set_action_rows(Vec::new())) + edit + .set_embeds(Vec::new()) + .embed(|emb| { + emb + .color(COLOR_OKAY) + .description("Interaction completed, you may safely dismiss this message.") + }) + .components(|components| components.set_action_rows(Vec::new())) } pub fn create_diff_select_response<'this, 'a>( - resp: &'this mut CreateInteractionResponse<'a>, + resp: &'this mut CreateInteractionResponse<'a>, ) -> &'this mut CreateInteractionResponse<'a> { - resp.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|data| { - data.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) - .embed(|emb| { - emb.color(COLOR_WARN).description( - "Please re-run this command on another message to generate a diff", - ) - }) - .components(|components| { - components.create_action_row(|row| { - row.create_button(|btn| { - btn.custom_id("cancel") - .label("Cancel") - .style(ButtonStyle::Danger) - }) - }) - }) + resp.kind(InteractionResponseType::ChannelMessageWithSource).interaction_response_data(|data| { + data + .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + .embed(|emb| { + emb + .color(COLOR_WARN) + .description("Please re-run this command on another message to generate a diff") + }) + .components(|components| { + components.create_action_row(|row| { + row + .create_button(|btn| btn.custom_id("cancel").label("Cancel").style(ButtonStyle::Danger)) }) + }) + }) } pub fn create_diff_response<'this, 'a>( - resp: &'this mut CreateInteractionResponse<'a>, - output: &str, + resp: &'this mut CreateInteractionResponse<'a>, + output: &str, ) -> &'this mut CreateInteractionResponse<'a> { - resp.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|data| { - data.embed(|emb| { - emb.color(COLOR_OKAY) - .title("Diff completed") - .description(format!("```diff\n{}\n```", output)) - }) - }) + resp.kind(InteractionResponseType::ChannelMessageWithSource).interaction_response_data(|data| { + data.embed(|emb| { + emb.color(COLOR_OKAY).title("Diff completed").description(format!("```diff\n{}\n```", output)) + }) + }) } pub(crate) fn create_think_interaction( - resp: &mut EditInteractionResponse, + resp: &mut EditInteractionResponse, ) -> &mut EditInteractionResponse { - resp.content("") - .components(|cmps| cmps.set_action_rows(Vec::new())) - .embed(|emb| emb.color(COLOR_WARN).description("Processing request...")) + 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, + ctx: &Context, + command: &ApplicationCommandInteraction, + languages: &[&str], + is_asm: bool, + get_result: F, ) -> Result<(), CommandError> where - F: FnOnce(ParserResult) -> Fut, - Fut: Future>, + F: FnOnce(ParserResult) -> Fut, + Fut: Future>, { - 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!")); - // } - // msg = Some(value); - // break; - // } - - 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); - } - - // 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) + 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!")); + // } + // msg = Some(value); + // break; + // } + + 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?; - - let resp = command.get_interaction_response(&ctx.http).await?; - let selection = match resp - .await_component_interaction(ctx) - .timeout(Duration::from_secs(30)) - .await - { - Some(s) => s, - None => return Ok(()), - }; - - sent_interaction = true; - parse_result.target = selection.data.values.get(0).unwrap().to_lowercase(); - selection.defer(&ctx.http).await?; + }) + .await?; + return Err(CommandError::from("Unable to find a codeblock to compile!")); } + msg = Some(value); + } - let language = parse_result.target.clone(); - 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); - - 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?; - } + // 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?; let resp = command.get_interaction_response(&ctx.http).await?; - let cib = resp - .await_component_interactions(&ctx.shard) - .timeout(Duration::from_secs(30)); - let mut cic = cib.build(); - - // collect compiler into var - parse_result.target = language.to_owned(); - - let mut last_interaction = None; - let mut more_options_response = None; - while let Some(interaction) = &cic.next().await { - last_interaction = Some(interaction.clone()); - match interaction.data.custom_id.as_str() { - "compiler_select" => { - parse_result.target = interaction.data.values[0].clone(); - interaction.defer(&ctx.http).await?; - } - "2" => { - 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", - ) - }) - }) - .await?; - - more_options_response = - create_more_options_panel(ctx, interaction.clone(), &mut parse_result).await?; - if more_options_response.is_some() { - cic.stop(); - break; - } - } - "1" => { - cic.stop(); - break; - } - _ => { - unreachable!("Cannot get here.."); - } - } - } + let selection = + match resp.await_component_interaction(ctx).timeout(Duration::from_secs(30)).await { + Some(s) => s, + None => return Ok(()), + }; - // exit, they let this expire - if last_interaction.is_none() && more_options_response.is_none() { - return Ok(()); - } + sent_interaction = true; + parse_result.target = selection.data.values.get(0).unwrap().to_lowercase(); + selection.defer(&ctx.http).await?; + } - command - .edit_original_interaction_response(&ctx.http, create_think_interaction) - .await - .unwrap(); - - let data = ctx.data.read().await; - let config = data.get::().unwrap(); - let config_lock = config.read().await; - let comp_log_id = config_lock.get("COMPILE_LOG"); - - let result = get_result(parse_result.clone()).await?; - let is_success = result.0["color"] == COLOR_OKAY; - //statistics - { - let stats_manager = data.get::().unwrap().lock().await; - if !is_asm && stats_manager.should_track() { - stats_manager.compilation(&language, is_success).await; - } - } + let language = parse_result.target.clone(); + let options = create_compiler_options(ctx, &language, is_asm).await?; - 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 emb = embeds::build_complog_embed( - is_success, - &parse_result.code, - &parse_result.target, - &command.user.tag(), - command.user.id.0, - &guild, - ); - discordhelpers::manual_dispatch(ctx.http.clone(), id, emb).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); + + 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?; + } + + let resp = command.get_interaction_response(&ctx.http).await?; + let cib = resp.await_component_interactions(&ctx.shard).timeout(Duration::from_secs(30)); + let mut cic = cib.build(); + + // collect compiler into var + parse_result.target = language.to_owned(); + + let mut last_interaction = None; + let mut more_options_response = None; + while let Some(interaction) = &cic.next().await { + last_interaction = Some(interaction.clone()); + match interaction.data.custom_id.as_str() { + "compiler_select" => { + parse_result.target = interaction.data.values[0].clone(); + interaction.defer(&ctx.http).await?; + } + "2" => { + 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", + ) + }, + ) + }) + .await?; + + more_options_response = + create_more_options_panel(ctx, interaction.clone(), &mut parse_result).await?; + if more_options_response.is_some() { + cic.stop(); + break; } + } + "1" => { + cic.stop(); + break; + } + _ => { + unreachable!("Cannot get here.."); + } } - - if let Err(err) = command - .edit_original_interaction_response(&ctx.http, |resp| { - edit_to_confirmation_interaction(&result, resp) - }) - .await - { - return Err(CommandError::from(format!( - "Unable to update response: {}", - err - ))); + } + + // exit, they let this expire + if last_interaction.is_none() && more_options_response.is_none() { + return Ok(()); + } + + command.edit_original_interaction_response(&ctx.http, create_think_interaction).await.unwrap(); + + let data = ctx.data.read().await; + let config = data.get::().unwrap(); + let config_lock = config.read().await; + let comp_log_id = config_lock.get("COMPILE_LOG"); + + let result = get_result(parse_result.clone()).await?; + let is_success = result.0["color"] == COLOR_OKAY; + //statistics + { + let stats_manager = data.get::().unwrap().lock().await; + if !is_asm && stats_manager.should_track() { + stats_manager.compilation(&language, is_success).await; } - - 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, 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?; + } + + 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 emb = embeds::build_complog_embed( + is_success, + &parse_result.code, + &parse_result.target, + &command.user.tag(), + command.user.id.0, + &guild, + ); + discordhelpers::manual_dispatch(ctx.http.clone(), id, emb).await; } - Ok(()) + } + + if let Err(err) = command + .edit_original_interaction_response(&ctx.http, |resp| { + edit_to_confirmation_interaction(&result, resp) + }) + .await + { + return Err(CommandError::from(format!("Unable to update response: {}", err))); + } + + 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, 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?; + } + Ok(()) } pub async fn send_error_msg( - ctx: &Context, - command: &ApplicationCommandInteraction, - edit: bool, - fail_embed: CreateEmbed, + 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?; - 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 - } + 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?; + 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 + } } diff --git a/src/utls/discordhelpers/menu.rs b/src/utls/discordhelpers/menu.rs index edc7698..b5f68a4 100644 --- a/src/utls/discordhelpers/menu.rs +++ b/src/utls/discordhelpers/menu.rs @@ -7,84 +7,72 @@ 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: &[CreateEmbed]) -> Menu { - Menu { - ctx: ctx.clone(), - msg: msg.clone(), - pages: items.to_vec(), - page: 0, - components: Menu::build_components(), - } + pub fn new(ctx: &Context, msg: &Message, items: &[CreateEmbed]) -> Menu { + Menu { + ctx: ctx.clone(), + msg: msg.clone(), + pages: items.to_vec(), + 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?; + 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 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 { - match int.data.custom_id.as_str() { - "left" => { - if self.page > 0 { - self.page -= 1; - } else { - self.page = self.pages.len() - 1; - } - } - "right" => { - self.page += 1; - if self.page == self.pages.len() { - self.page = 0; - } - } - _ => {} - } - int.defer(&self.ctx.http).await?; - self.update_msg(&mut m).await?; + 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 { + match int.data.custom_id.as_str() { + "left" => { + if self.page > 0 { + self.page -= 1; + } else { + self.page = self.pages.len() - 1; + } + } + "right" => { + self.page += 1; + if self.page == self.pages.len() { + self.page = 0; + } } + _ => {} + } + int.defer(&self.ctx.http).await?; + self.update_msg(&mut m).await?; + } - m.edit(&self.ctx.http, |edit| { - edit.components(|cmps| cmps.set_action_rows(Vec::new())) - }) - .await?; + m.edit(&self.ctx.http, |edit| edit.components(|cmps| cmps.set_action_rows(Vec::new()))).await?; - Ok(()) - } + Ok(()) + } - 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 - } + 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 + } - 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")) - .create_button(|btn| { - btn.style(ButtonStyle::Primary) - .label("โžก") - .custom_id("right") - }) - }); - c - } + 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")) + .create_button(|btn| btn.style(ButtonStyle::Primary).label("โžก").custom_id("right")) + }); + c + } } diff --git a/src/utls/discordhelpers/mod.rs b/src/utls/discordhelpers/mod.rs index 8b27ea8..ac1e36f 100644 --- a/src/utls/discordhelpers/mod.rs +++ b/src/utls/discordhelpers/mod.rs @@ -18,241 +18,201 @@ use tokio::sync::MutexGuard; use std::fmt::Write as _; pub fn build_menu_items( - items: Vec, - items_per_page: usize, - title: &str, - avatar: &str, - author: &str, - desc: &str, + items: Vec, + items_per_page: usize, + title: &str, + avatar: &str, + author: &str, + desc: &str, ) -> Vec { - let mut pages = Vec::new(); - let num_pages = items.len() / items_per_page; - - let mut current_page = 0; - while current_page < num_pages + 1 { - let start = current_page * items_per_page; - let mut end = start + items_per_page; - if end > items.len() { - end = items.len(); - } - let mut emb = CreateEmbed::default(); - let mut description = format!("{}\n", desc); - for (i, item) in items[current_page * items_per_page..end].iter().enumerate() { - if i > items_per_page { - break; - } - // description.push_str(&format!( - // "**{}**) {}\n", - // current_page * items_per_page + i + 1, - // item - // )) - writeln!( - description, - "**{}**) {}", - current_page * items_per_page + i + 1, - item - ) - .unwrap(); - } - emb.color(COLOR_OKAY); - emb.title(title); - emb.description(description); - emb.footer(|f| { - f.text(&format!( - "Requested by {} | Page {}/{}", - author, - current_page + 1, - num_pages + 1 - )) - }); - emb.thumbnail(avatar); - - pages.push(emb); - current_page += 1; + let mut pages = Vec::new(); + let num_pages = items.len() / items_per_page; + + let mut current_page = 0; + while current_page < num_pages + 1 { + let start = current_page * items_per_page; + let mut end = start + items_per_page; + if end > items.len() { + end = items.len(); } - - pages + let mut emb = CreateEmbed::default(); + let mut description = format!("{}\n", desc); + for (i, item) in items[current_page * items_per_page..end].iter().enumerate() { + if i > items_per_page { + break; + } + // description.push_str(&format!( + // "**{}**) {}\n", + // current_page * items_per_page + i + 1, + // item + // )) + writeln!(description, "**{}**) {}", current_page * items_per_page + i + 1, item).unwrap(); + } + emb.color(COLOR_OKAY); + emb.title(title); + emb.description(description); + emb.footer(|f| { + f.text(&format!("Requested by {} | Page {}/{}", author, current_page + 1, num_pages + 1)) + }); + emb.thumbnail(avatar); + + pages.push(emb); + current_page += 1; + } + + pages } pub fn build_reaction(emoji_id: u64, emoji_name: &str) -> ReactionType { - ReactionType::Custom { - animated: false, // todo: Make this configurable - id: EmojiId::from(emoji_id), - name: Some(String::from(emoji_name)), - } + ReactionType::Custom { + animated: false, // todo: Make this configurable + id: EmojiId::from(emoji_id), + name: Some(String::from(emoji_name)), + } } pub async fn handle_edit( - ctx: &Context, - content: String, - author: User, - mut old: Message, - original_message: Message, + 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; - info.get("BOT_PREFIX").unwrap().to_owned() - }; - - // try to clear reactions - // let _ = old.delete_reactions(&ctx).await; - if let Ok(updated_message) = old.channel_id.message(&ctx.http, old.id.0).await { - for reaction in &updated_message.reactions { - if reaction.me { - let _ = discordhelpers::delete_bot_reacts( - ctx, - &updated_message, - reaction.reaction_type.clone(), - ) - .await; - } - } + let prefix = { + let data = ctx.data.read().await; + let info = data.get::().unwrap().read().await; + info.get("BOT_PREFIX").unwrap().to_owned() + }; + + // try to clear reactions + // let _ = old.delete_reactions(&ctx).await; + if let Ok(updated_message) = old.channel_id.message(&ctx.http, old.id.0).await { + for reaction in &updated_message.reactions { + if reaction.me { + let _ = + discordhelpers::delete_bot_reacts(ctx, &updated_message, reaction.reaction_type.clone()) + .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 - { - let err = embeds::build_fail_embed(&author, &e.to_string()); - 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 - { - let err = embeds::build_fail_embed(&author, &e.to_string()); - 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 - { - let err = embeds::build_fail_embed(&author, &e.to_string()); - embeds::edit_message_embed(ctx, &mut old, err).await; - } - } else { - let err = embeds::build_fail_embed(&author, "Invalid command for edit functionality!"); - embeds::edit_message_embed(ctx, &mut old, err).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 + { + let err = embeds::build_fail_embed(&author, &e.to_string()); + 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 + { + let err = embeds::build_fail_embed(&author, &e.to_string()); + 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 + { + let err = embeds::build_fail_embed(&author, &e.to_string()); + embeds::edit_message_embed(ctx, &mut old, err).await; } + } else { + let err = embeds::build_fail_embed(&author, "Invalid command for edit functionality!"); + 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, + 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 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?; + 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; - Ok(()) + 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, + 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 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?; + 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; - Ok(()) + 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, + 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 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; + let success = emb.0.get("color").unwrap() == COLOR_OKAY; + embeds::edit_message_embed(ctx, &mut old, emb).await; - send_completion_react(ctx, &old, success).await?; - Ok(()) + send_completion_react(ctx, &old, success).await?; + Ok(()) } pub fn is_success_embed(embed: &CreateEmbed) -> bool { - if let Some(color) = embed.0.get("color") { - color == COLOR_OKAY - } else { - false - } + if let Some(color) = embed.0.get("color") { + color == COLOR_OKAY + } else { + false + } } pub async fn send_completion_react( - ctx: &Context, - msg: &Message, - success: bool, + ctx: &Context, + msg: &Message, + success: bool, ) -> Result { - let reaction; - let data = ctx.data.read().await; - let botinfo_lock = data.get::().unwrap(); - let botinfo = botinfo_lock.read().await; - match success { - true => { - 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 { - reaction = ReactionType::Unicode(String::from("โœ…")); - } - } - false => { - if let Some(fail_id) = botinfo.get("FAIL_EMOJI_ID") { - let fail_name = botinfo - .get("FAIL_EMOJI_NAME") - .expect("Unable to find fail emoji name") - .clone(); - reaction = - discordhelpers::build_reaction(fail_id.parse::().unwrap(), &fail_name); - } else { - reaction = ReactionType::Unicode(String::from("โŒ")); - } - } + let reaction; + let data = ctx.data.read().await; + let botinfo_lock = data.get::().unwrap(); + let botinfo = botinfo_lock.read().await; + match success { + true => { + 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 { + reaction = ReactionType::Unicode(String::from("โœ…")); + } } - msg.react(&ctx.http, reaction).await + false => { + if let Some(fail_id) = botinfo.get("FAIL_EMOJI_ID") { + let fail_name = + botinfo.get("FAIL_EMOJI_NAME").expect("Unable to find fail emoji name").clone(); + reaction = discordhelpers::build_reaction(fail_id.parse::().unwrap(), &fail_name); + } else { + reaction = ReactionType::Unicode(String::from("โŒ")); + } + } + } + msg.react(&ctx.http, reaction).await } // Certain compiler outputs use unicode control characters that @@ -262,72 +222,67 @@ pub async fn send_completion_react( // 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 = if let Ok(vec) = strip_ansi_escapes::strip(input) { - String::from_utf8_lossy(&vec).to_string() - } else { - 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}`"); - - // Conform our string. - if str.len() > MAX_OUTPUT_LEN { - str.chars().take(max_len).collect() - } else { - str - } + let mut str = if let Ok(vec) = strip_ansi_escapes::strip(input) { + String::from_utf8_lossy(&vec).to_string() + } else { + 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}`"); + + // Conform our string. + if str.len() > MAX_OUTPUT_LEN { + str.chars().take(max_len).collect() + } else { + str + } } pub async fn manual_dispatch(http: Arc, id: u64, emb: CreateEmbed) { - if let Err(e) = serenity::model::id::ChannelId(id) - .send_message(&http, |m| { - m.embed(|mut e| { - e.0 = emb.0; - e - }) - }) - .await - { - error!("Unable to dispatch manually: {}", e); - } + if let Err(e) = serenity::model::id::ChannelId(id) + .send_message(&http, |m| { + m.embed(|mut e| { + e.0 = emb.0; + e + }) + }) + .await + { + error!("Unable to dispatch manually: {}", e); + } } pub async fn send_global_presence(shard_manager: &MutexGuard<'_, ShardManager>, sum: u64) { - let server_count = { - if sum < 10000 { - sum.to_string() - } else { - format!("{:.1}k", sum / 1000) - } - }; - - // update shard guild count & presence - let final_s = if sum > 1 { "s" } else { "" }; - let presence_str = format!("{} guild{} | ;help", server_count, final_s); - - let runners = shard_manager.runners.lock().await; - for (_, v) in runners.iter() { - v.runner_tx.set_presence( - Some(Activity::watching(&presence_str)), - OnlineStatus::Online, - ); + let server_count = { + if sum < 10000 { + sum.to_string() + } else { + format!("{:.1}k", sum / 1000) } + }; + + // update shard guild count & presence + let final_s = if sum > 1 { "s" } else { "" }; + let presence_str = format!("{} guild{} | ;help", server_count, final_s); + + let runners = shard_manager.runners.lock().await; + for (_, v) in runners.iter() { + v.runner_tx.set_presence(Some(Activity::watching(&presence_str)), OnlineStatus::Online); + } } 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(); - let botinfo = botinfo_lock.read().await; - let id = botinfo.get("BOT_ID").unwrap(); - UserId::from(id.parse::().unwrap()) - }; - - msg.channel_id - .delete_reaction(&ctx.http, msg.id, Some(bot_id), react) - .await?; - Ok(()) + let bot_id = { + let data_read = ctx.data.read().await; + let botinfo_lock = data_read.get::().unwrap(); + let botinfo = botinfo_lock.read().await; + let id = botinfo.get("BOT_ID").unwrap(); + UserId::from(id.parse::().unwrap()) + }; + + msg.channel_id.delete_reaction(&ctx.http, msg.id, Some(bot_id), react).await?; + Ok(()) } diff --git a/src/utls/parser.rs b/src/utls/parser.rs index 01dd9b8..b3a8101 100644 --- a/src/utls/parser.rs +++ b/src/utls/parser.rs @@ -12,321 +12,309 @@ use tokio::sync::RwLock; // Allows us to convert some common aliases to other programming languages 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. - "cpp" | "hpp" | "h++" => "c++", - "h" => "c", - "rs" => "rust", - "js" => "javascript", - "ts" => "typescript", - "csharp" | "cs" => "c#", - "py" => "python", - "bash" | "sh" => "bash script", - "rb" => "ruby", - "kt" => "kotlin", - "golang" => "go", - "fs" | "f#" => "fsharp", - "hs" | "lhs" => "haskell", - "jl" => "julia", - "gvy" => "groovy", - _ => language, - } + match language { + // Replace cpp with c++ since we removed the c pre-processor + // support for wandbox. This is okay for godbolt requests, too. + "cpp" | "hpp" | "h++" => "c++", + "h" => "c", + "rs" => "rust", + "js" => "javascript", + "ts" => "typescript", + "csharp" | "cs" => "c#", + "py" => "python", + "bash" | "sh" => "bash script", + "rb" => "ruby", + "kt" => "kotlin", + "golang" => "go", + "fs" | "f#" => "fsharp", + "hs" | "lhs" => "haskell", + "jl" => "julia", + "gvy" => "groovy", + _ => language, + } } #[derive(Debug, Default, Clone)] pub struct ParserResult { - pub url: String, - pub stdin: String, - pub target: String, - pub code: String, - pub options: Vec, - pub args: Vec, + pub url: String, + pub stdin: String, + pub target: String, + pub code: String, + pub options: Vec, + pub args: Vec, } #[allow(clippy::while_let_on_iterator)] pub async fn get_components( - input: &str, - author: &User, - compilation_manager: Option<&Arc>>, - reply: &Option>, + input: &str, + author: &User, + compilation_manager: Option<&Arc>>, + reply: &Option>, ) -> Result { - let mut result = ParserResult::default(); + 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') { - end_point = parse_stop; + // 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') { + end_point = parse_stop; + } + if let Some(index) = input.find('`') { + if end_point == 0 || index < end_point { + // if the ` character is found before \n we should use the ` as our parse stop point + end_point = index; } - if let Some(index) = input.find('`') { - if end_point == 0 || index < end_point { - // if the ` character is found before \n we should use the ` as our parse stop point - end_point = index; - } + } + + let mut args: Vec<&str> = input[..end_point].split_whitespace().collect(); + // ditch command str (;compile, ;asm) + args.remove(0); + + // Check to see if we were given a valid target... if not we'll check + // the syntax highlighting str later. + if let Some(comp_mngr) = compilation_manager { + let lang_lookup = comp_mngr.read().await; + if let Some(param) = args.first() { + let lower_param = param.trim().to_lowercase(); + let language = shortname_to_qualified(&lower_param); + 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 + if let Some(param) = args.first() { + let lower_param = param.trim().to_lowercase(); + let language = shortname_to_qualified(&lower_param); + args.remove(0); + result.target = language.to_owned(); } + } - let mut args: Vec<&str> = input[..end_point].split_whitespace().collect(); - // ditch command str (;compile, ;asm) - args.remove(0); + // looping every argument + let mut iter = args.iter(); + while let Some(c) = iter.next() { + if c.contains('\n') || c.contains('`') { + break; + } - // Check to see if we were given a valid target... if not we'll check - // the syntax highlighting str later. - if let Some(comp_mngr) = compilation_manager { - let lang_lookup = comp_mngr.read().await; - if let Some(param) = args.first() { - let lower_param = param.trim().to_lowercase(); - let language = shortname_to_qualified(&lower_param); - if !matches!(lang_lookup.resolve_target(language), RequestHandler::None) { - args.remove(0); - result.target = language.to_owned(); - } + if *c == "<" { + let link = match iter.next() { + Some(link) => link, + None => { + return Err(CommandError::from( + "'<' operator requires a url\n\nUsage: `;compile c++ < http://foo.bar/code.txt`", + )) } - } else { - // no compilation manager, just assume target is supplied - if let Some(param) = args.first() { - let lower_param = param.trim().to_lowercase(); - let language = shortname_to_qualified(&lower_param); - args.remove(0); - result.target = language.to_owned(); + }; + result.url = link.trim().to_string(); + } else if *c == "|" { + let mut input: String = String::new(); + while let Some(stdin) = iter.next() { + if stdin.contains("```") { + break; } - } - - // looping every argument - let mut iter = args.iter(); - while let Some(c) = iter.next() { - if c.contains('\n') || c.contains('`') { - break; + if *stdin == "<" { + return Err(CommandError::from("`|`` operator should be last, unable to continue")); } + input.push_str(stdin); + input.push(' '); + } - if *c == "<" { - let link = match iter.next() { - Some(link) => link, - None => return Err(CommandError::from("'<' operator requires a url\n\nUsage: `;compile c++ < http://foo.bar/code.txt`")) - }; - result.url = link.trim().to_string(); - } else if *c == "|" { - let mut input: String = String::new(); - while let Some(stdin) = iter.next() { - if stdin.contains("```") { - break; - } - if *stdin == "<" { - return Err(CommandError::from( - "`|`` operator should be last, unable to continue", - )); - } - input.push_str(stdin); - input.push(' '); - } - - result.stdin = input.trim().to_owned(); - } else { - result.options.push(c.trim().to_string()); - } + result.stdin = input.trim().to_owned(); + } else { + result.options.push(c.trim().to_string()); } + } - let cmdline_args; - if let Some(codeblock_start) = input.find('`') { - if end_point < codeblock_start { - cmdline_args = String::from(input[end_point..codeblock_start].trim()); - } else { - cmdline_args = String::default(); - } + let cmdline_args; + if let Some(codeblock_start) = input.find('`') { + if end_point < codeblock_start { + cmdline_args = String::from(input[end_point..codeblock_start].trim()); } else { - cmdline_args = String::from(input[end_point..].trim()); + cmdline_args = String::default(); } - result.args = shell_words::split(&cmdline_args)?; + } 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? { - // 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 quoting is the code. - // Anything else probably doesn't make sense. - if let Some(replied_msg) = reply { - result.stdin = result.code; - result.code = String::default(); + 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? { + // 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 quoting is the code. + // Anything else probably doesn't make sense. + if let Some(replied_msg) = reply { + result.stdin = result.code; + result.code = String::default(); - let attachment = get_message_attachment(&replied_msg.attachments).await?; - if !attachment.0.is_empty() { - if !result.target.is_empty() { - result.target = attachment.1; - } - result.code = attachment.0; - } else if !find_code_block(&mut result, &replied_msg.content, author).await? { - return Err(CommandError::from( - "Cannot find code to compile assuming your code block is the program's stdin.", - )); - } + let attachment = get_message_attachment(&replied_msg.attachments).await?; + if !attachment.0.is_empty() { + if !result.target.is_empty() { + result.target = attachment.1; } - } 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 { - let attachment = get_message_attachment(&replied_msg.attachments).await?; - if !attachment.0.is_empty() { - if !result.target.is_empty() { - result.target = attachment.1; - } - result.code = attachment.0; - } - // no reply in the attachment, lets check for a code-block.. - else if !find_code_block(&mut result, &replied_msg.content, author).await? { - return Err(CommandError::from( + result.code = attachment.0; + } else if !find_code_block(&mut result, &replied_msg.content, author).await? { + return Err(CommandError::from( + "Cannot find code to compile assuming your code block is the program's stdin.", + )); + } + } + } 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 { + let attachment = get_message_attachment(&replied_msg.attachments).await?; + if !attachment.0.is_empty() { + if !result.target.is_empty() { + result.target = attachment.1; + } + result.code = attachment.0; + } + // no reply in the attachment, lets check for a code-block.. + 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 quote a message that has one.", )); - } - } else { - // We were really given nothing, lets fail now. - return Err(CommandError::from( + } + } 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\\`\\`\\`")); - } + 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\\`\\`\\`")); + } - Ok(result) + Ok(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, - }; + let url = match reqwest::Url::parse(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")); - } + let host = url.host(); + if host.is_none() { + 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")); - } + 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")); + } - let response = match reqwest::get(url).await { - Ok(b) => b, - Err(_e) => { - return Err(CommandError::from( - "GET request failed, perhaps your link is unreachable?", - )) - } - }; - - match response.text().await { - Ok(t) => Ok(t), - Err(_e) => Err(CommandError::from("Unable to grab resource")), + let response = match reqwest::get(url).await { + Ok(b) => b, + Err(_e) => { + return Err(CommandError::from("GET request failed, perhaps your link is unreachable?")) } + }; + + match response.text().await { + Ok(t) => Ok(t), + Err(_e) => Err(CommandError::from("Unable to grab resource")), + } } pub async fn find_code_block( - result: &mut ParserResult, - haystack: &str, - author: &User, + 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); + let re = regex::Regex::new(r"```(?:(?P[^\s`]*)\r?\n)?(?P[\s\S]*?)```").unwrap(); + let matches = re.captures_iter(haystack); - let mut captures = Vec::new(); - let list = matches.enumerate(); - for (_, cap) in list { - captures.push(cap); - } + let mut captures = Vec::new(); + let list = matches.enumerate(); + for (_, cap) in list { + captures.push(cap); + } - // support for stdin codeblocks - let code_index; // index into captures where we might find our target lang - match captures.len() { - len if len > 1 => { - result.stdin = String::from(captures[0].name("code").unwrap().as_str()); - result.code = String::from(captures[1].name("code").unwrap().as_str()); + // support for stdin codeblocks + let code_index; // index into captures where we might find our target lang + match captures.len() { + len if len > 1 => { + result.stdin = String::from(captures[0].name("code").unwrap().as_str()); + result.code = String::from(captures[1].name("code").unwrap().as_str()); - code_index = 1; - } - 1 => { - result.code = String::from(captures[0].name("code").unwrap().as_str()); + code_index = 1; + } + 1 => { + result.code = String::from(captures[0].name("code").unwrap().as_str()); - code_index = 0; - } - _ => return Ok(false), + code_index = 0; } + _ => 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") { - let include_stmt = statement.as_str(); - let url = cap.name("url").unwrap().as_str(); - if let Ok(code) = get_url_code(url, author).await { - println!("Replacing {} with {}", include_stmt, &code); - result.code = result.code.replace(include_stmt, &code); - } - } + 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") { + let include_stmt = statement.as_str(); + let url = cap.name("url").unwrap().as_str(); + if let Ok(code) = get_url_code(url, author).await { + println!("Replacing {} with {}", include_stmt, &code); + result.code = result.code.replace(include_stmt, &code); + } } + } - // if we still don't have our language target, lets try the language for syntax highlighting - if result.target.is_empty() { - if let Some(lang_match) = captures[code_index].name("language") { - result.target = shortname_to_qualified(lang_match.as_str()).to_owned(); - } + // if we still don't have our language target, lets try the language for syntax highlighting + if result.target.is_empty() { + if let Some(lang_match) = captures[code_index].name("language") { + result.target = shortname_to_qualified(lang_match.as_str()).to_owned(); } + } - Ok(true) + Ok(true) } pub async fn get_message_attachment( - attachments: &[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 !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))); + } + return match reqwest::get(&attached.url).await { + Ok(r) => { + let bytes = r.bytes().await.unwrap(); + let cnt_type = content_inspector::inspect(&bytes); + if cnt_type.is_binary() { + return Err(CommandError::from("Invalid file type")); } - return match reqwest::get(&attached.url).await { - Ok(r) => { - let bytes = r.bytes().await.unwrap(); - let cnt_type = content_inspector::inspect(&bytes); - if cnt_type.is_binary() { - return Err(CommandError::from("Invalid file type")); - } - match String::from_utf8(bytes.to_vec()) { - Ok(str) => { - let mut extension = String::from(""); - if let Some(ext) = Path::new(&attached.filename).extension() { - extension = ext.to_string_lossy().to_string(); - } - Ok((str, extension)) - } - Err(e) => Err(CommandError::from(format!( - "UTF8 Error occurred while parsing file: {}", - e - ))), - } + match String::from_utf8(bytes.to_vec()) { + Ok(str) => { + let mut extension = String::from(""); + if let Some(ext) = Path::new(&attached.filename).extension() { + extension = ext.to_string_lossy().to_string(); } - Err(e) => Err(CommandError::from(format!( - "Failure when downloading attachment: {}", - e - ))), - }; - } - Ok((String::new(), String::new())) + Ok((str, extension)) + } + Err(e) => { + Err(CommandError::from(format!("UTF8 Error occurred while parsing file: {}", e))) + } + } + } + Err(e) => Err(CommandError::from(format!("Failure when downloading attachment: {}", e))), + }; + } + Ok((String::new(), String::new())) } From 0d5e73b6297482609feb04ba04c99793502bac16 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 27 Feb 2023 13:54:22 +0100 Subject: [PATCH 62/81] =?UTF-8?q?coding=20standards=20=F0=9F=A4=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .rustfmt.toml | 14 ++++++++++++++ .rusty-hook.toml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .rustfmt.toml diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..19e2346 --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,14 @@ +# Fuchsia Format Style +# last reviewed: Feb 27, 2023 +# Fuchsia uses mostly 2021 edition +edition = "2021" +# The "Default" setting has a heuristic which splits lines too aggresively. +# We are willing to revisit this setting in future versions of rustfmt. +# Bugs: +# * https://github.com/rust-lang/rustfmt/issues/3119 +# * https://github.com/rust-lang/rustfmt/issues/3120 +use_small_heuristics = "Max" +# Prevent carriage returns +newline_style = "Unix" +# We use 2 spaces for indentation +tab_spaces = 2 diff --git a/.rusty-hook.toml b/.rusty-hook.toml index a8be34f..1610bb6 100644 --- a/.rusty-hook.toml +++ b/.rusty-hook.toml @@ -1,5 +1,5 @@ [hooks] -pre-commit = "cargo fmt -- --check && cargo clippy --all-targets --all-features -- -D clippy::all && cargo test" +pre-commit = "cargo fmt --all --check && cargo clippy --all-targets --all-features -- -D clippy::all && cargo test --all" [logging] verbose = true From 4bcf066feeac40af0b6b1be59eedb9bfba3d3031 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 4 Mar 2023 17:17:35 +0100 Subject: [PATCH 63/81] bumped serenity to `0.11.5` --- src/cache.rs | 2 +- src/commands/asm.rs | 10 +++- src/commands/compile.rs | 10 +++- src/commands/cpp.rs | 10 +++- src/commands/formats.rs | 10 +++- src/commands/help.rs | 10 +++- src/commands/invite.rs | 10 +++- src/events.rs | 68 ++++++++++++++++++------- src/managers/command.rs | 45 +++++++--------- src/slashcmds/asm.rs | 2 +- src/slashcmds/compile.rs | 2 +- src/slashcmds/cpp.rs | 10 ++-- src/slashcmds/diff.rs | 14 ++--- src/slashcmds/diff_msg.rs | 2 +- src/slashcmds/format.rs | 52 +++++++++---------- src/slashcmds/help.rs | 5 +- src/slashcmds/invite.rs | 4 +- src/slashcmds/ping.rs | 4 +- src/utls/discordhelpers/interactions.rs | 51 +++++++++---------- src/utls/discordhelpers/menu.rs | 2 +- src/utls/discordhelpers/mod.rs | 4 +- 21 files changed, 191 insertions(+), 136 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index 6b84243..8e25266 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -16,8 +16,8 @@ use crate::utls::blocklist::Blocklist; use crate::managers::command::CommandManager; use crate::managers::compilation::CompilationManager; use lru_cache::LruCache; +use serenity::model::application::interaction::application_command::ApplicationCommandInteraction; use serenity::model::channel::Message; -use serenity::model::interactions::application_command::ApplicationCommandInteraction; /** Caching **/ diff --git a/src/commands/asm.rs b/src/commands/asm.rs index d78a341..a47f39b 100644 --- a/src/commands/asm.rs +++ b/src/commands/asm.rs @@ -20,8 +20,14 @@ use crate::utls::parser; #[bucket = "nospam"] pub async fn asm(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let emb = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; - let mut emb_msg = embeds::embed_message(emb); - let asm_embed = msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await?; + let emb_msg = embeds::embed_message(emb); + let asm_embed = msg + .channel_id + .send_message(&ctx.http, |e| { + *e = emb_msg; + e + }) + .await?; // Success/fail react let compilation_successful = asm_embed.embeds[0].colour.unwrap().0 == COLOR_OKAY; diff --git a/src/commands/compile.rs b/src/commands/compile.rs index 4423492..f755ac5 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -27,8 +27,14 @@ pub async fn compile(ctx: &Context, msg: &Message, _args: Args) -> CommandResult let embed = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; // Send our final embed - let mut message = embeds::embed_message(embed); - let compilation_embed = msg.channel_id.send_message(&ctx.http, |_| &mut message).await?; + let message = embeds::embed_message(embed); + let compilation_embed = msg + .channel_id + .send_message(&ctx.http, |e| { + *e = message; + e + }) + .await?; // Success/fail react let compilation_successful = compilation_embed.embeds[0].colour.unwrap().0 == COLOR_OKAY; diff --git a/src/commands/cpp.rs b/src/commands/cpp.rs index 6e1b202..d42513d 100644 --- a/src/commands/cpp.rs +++ b/src/commands/cpp.rs @@ -20,10 +20,16 @@ use crate::{ #[bucket = "nospam"] pub async fn cpp(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let emb = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; - let mut emb_msg = embeds::embed_message(emb); + let emb_msg = embeds::embed_message(emb); // Dispatch our request - let compilation_embed = msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await?; + let compilation_embed = msg + .channel_id + .send_message(&ctx.http, |e| { + *e = emb_msg; + e + }) + .await?; // add delete cache let data_read = ctx.data.read().await; diff --git a/src/commands/formats.rs b/src/commands/formats.rs index 6a40e16..c40d236 100644 --- a/src/commands/formats.rs +++ b/src/commands/formats.rs @@ -44,8 +44,14 @@ pub async fn formats(ctx: &Context, msg: &Message, _args: Args) -> CommandResult emb.field(&format.format_type, &output, false); } - let mut emb_msg = embeds::embed_message(emb); - msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await?; + let emb_msg = embeds::embed_message(emb); + msg + .channel_id + .send_message(&ctx.http, |e| { + *e = emb_msg; + e + }) + .await?; return Ok(()); } diff --git a/src/commands/help.rs b/src/commands/help.rs index 2d11655..f93e66e 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -121,8 +121,14 @@ pub async fn help(ctx: &Context, msg: &Message, args: Args) -> CommandResult { emb.description(description); - let mut emb_msg = embeds::embed_message(emb); - msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await?; + let emb_msg = embeds::embed_message(emb); + msg + .channel_id + .send_message(&ctx.http, |e| { + *e = emb_msg; + e + }) + .await?; return Ok(()); } diff --git a/src/commands/invite.rs b/src/commands/invite.rs index e8e4df2..60cfc98 100644 --- a/src/commands/invite.rs +++ b/src/commands/invite.rs @@ -12,8 +12,14 @@ 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?; + let emb_msg = embeds::embed_message(emb); + msg + .channel_id + .send_message(&ctx.http, |e| { + *e = emb_msg; + e + }) + .await?; Ok(()) } diff --git a/src/events.rs b/src/events.rs index ebf85bc..c1b8b62 100644 --- a/src/events.rs +++ b/src/events.rs @@ -5,9 +5,9 @@ use serenity::{ collector::CollectReaction, framework::{standard::macros::hook, standard::CommandResult, standard::DispatchError}, model::{ - channel::Message, channel::ReactionType, event::MessageUpdateEvent, gateway::Ready, - guild::Guild, id::ChannelId, id::GuildId, id::MessageId, interactions::Interaction, - prelude::UnavailableGuild, + application::interaction::Interaction, channel::Message, channel::ReactionType, + event::MessageUpdateEvent, gateway::Ready, guild::Guild, id::ChannelId, id::GuildId, + id::MessageId, prelude::UnavailableGuild, }, prelude::*, }; @@ -111,8 +111,13 @@ impl EventHandler for Handler { info!("Joining {}", guild.name); 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 message = embeds::embed_message(embeds::build_welcome_embed()); + let _ = system_channel + .send_message(&ctx.http, |e| { + *e = message; + e + }) + .await; } } } @@ -200,9 +205,14 @@ impl EventHandler for Handler { Ok(emb) => emb, Err(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 emb_msg = embeds::embed_message(emb); + if let Ok(sent) = new_message + .channel_id + .send_message(&ctx.http, |e| { + *e = emb_msg; + e + }) + .await { let mut message_cache = data.get::().unwrap().lock().await; message_cache.insert(new_message.id.0, MessageCacheEntry::new(sent, new_message)); @@ -212,7 +222,13 @@ impl EventHandler for Handler { }; let mut emb_msg = embeds::embed_message(emb); emb_msg.reference_message(&new_message); - let _ = new_message.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await; + let _ = new_message + .channel_id + .send_message(&ctx.http, |e| { + *e = emb_msg; + e + }) + .await; } } } @@ -335,8 +351,15 @@ pub async fn before(ctx: &Context, msg: &Message, _: &str) -> bool { 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() { + let emb_msg = embeds::embed_message(emb); + let msg_response = msg + .channel_id + .send_message(&ctx.http, |e| { + *e = emb_msg; + e + }) + .await; + if msg_response.is_ok() { if author_blocklisted { warn!("Blocked user {} [{}]", msg.author.tag(), msg.author.id.0); } else { @@ -361,8 +384,15 @@ pub async fn after( if let Err(e) = command_result { let emb = embeds::build_fail_embed(&msg.author, &format!("{}", e)); - let mut emb_msg = embeds::embed_message(emb); - if let Ok(sent) = msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await { + let emb_msg = embeds::embed_message(emb); + if let Ok(sent) = msg + .channel_id + .send_message(&ctx.http, |e| { + *e = emb_msg; + e + }) + .await + { let mut message_cache = data.get::().unwrap().lock().await; message_cache.insert(msg.id.0, MessageCacheEntry::new(sent, msg.clone())); } @@ -379,9 +409,13 @@ pub async fn after( 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 mut emb_msg = embeds::embed_message(emb); - if msg.channel_id.send_message(&ctx.http, |_| &mut emb_msg).await.is_err() { - panic!("Failed to send ratelimit message"); - } + let emb_msg = embeds::embed_message(emb); + let _ = msg + .channel_id + .send_message(&ctx.http, |e| { + *e = emb_msg; + e + }) + .await; } } diff --git a/src/managers/command.rs b/src/managers/command.rs index baa7699..3e6a6ed 100644 --- a/src/managers/command.rs +++ b/src/managers/command.rs @@ -2,15 +2,11 @@ use crate::cache::StatsManagerCache; use crate::slashcmds; use serenity::{ - builder::CreateApplicationCommand, - client::Context, - framework::standard::CommandResult, - model::{ - guild::Guild, interactions::application_command::ApplicationCommand, - interactions::application_command::ApplicationCommandInteraction, - interactions::application_command::ApplicationCommandOptionType, - interactions::application_command::ApplicationCommandType, - }, + builder::CreateApplicationCommand, client::Context, framework::standard::CommandResult, + model::application::command::Command, model::application::command::CommandOptionType, + model::application::command::CommandType, + model::application::interaction::application_command::ApplicationCommandInteraction, + model::guild::Guild, }; pub struct CommandManager { @@ -47,7 +43,7 @@ impl CommandManager { "invite" => slashcmds::invite::invite(ctx, command).await, "format" | "format [beta]" => slashcmds::format::format(ctx, command).await, "diff" | "diff [beta]" => { - if command.data.kind == ApplicationCommandType::Message { + if command.data.kind == CommandType::Message { slashcmds::diff_msg::diff_msg(ctx, command).await } else { slashcmds::diff::diff(ctx, command).await @@ -78,7 +74,7 @@ impl CommandManager { } self.commands_registered = true; - match ApplicationCommand::set_global_application_commands(&ctx.http, |setter| { + match Command::set_global_application_commands(&ctx.http, |setter| { setter.set_application_commands(self.commands.clone()) }) .await @@ -93,80 +89,77 @@ impl CommandManager { let mut cmd = CreateApplicationCommand::default(); cmd - .kind(ApplicationCommandType::Message) + .kind(CommandType::Message) .name(format!("Compile{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); cmds.push(cmd); cmd = CreateApplicationCommand::default(); cmd - .kind(ApplicationCommandType::Message) + .kind(CommandType::Message) .name(format!("Assembly{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); cmds.push(cmd); cmd = CreateApplicationCommand::default(); cmd - .kind(ApplicationCommandType::Message) + .kind(CommandType::Message) .name(format!("Format{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); cmds.push(cmd); cmd = CreateApplicationCommand::default(); cmd - .kind(ApplicationCommandType::Message) + .kind(CommandType::Message) .name(format!("Diff{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); cmds.push(cmd); cmd = CreateApplicationCommand::default(); cmd - .kind(ApplicationCommandType::ChatInput) + .kind(CommandType::ChatInput) .name("help") .description("Information on how to use the compiler"); cmds.push(cmd); cmd = CreateApplicationCommand::default(); cmd - .kind(ApplicationCommandType::ChatInput) + .kind(CommandType::ChatInput) .name("invite") .description("Grab my invite link to invite me to your server"); cmds.push(cmd); cmd = CreateApplicationCommand::default(); - cmd - .kind(ApplicationCommandType::ChatInput) - .name("ping") - .description("Test my ping to Discord's endpoint"); + cmd.kind(CommandType::ChatInput).name("ping").description("Test my ping to Discord's endpoint"); cmds.push(cmd); cmd = CreateApplicationCommand::default(); cmd - .kind(ApplicationCommandType::ChatInput) + .kind(CommandType::ChatInput) .name("cpp") .description("Shorthand C++ compilation using geordi-like syntax") .create_option(|opt| { opt .required(false) .name("input") - .kind(ApplicationCommandOptionType::String) + .kind(CommandOptionType::String) .description("Geordi-like input") }); cmds.push(cmd); cmd = CreateApplicationCommand::default(); cmd - .kind(ApplicationCommandType::ChatInput) + .kind(CommandType::ChatInput) .name("diff") .description("Posts a diff of two message code blocks") .create_option(|opt| { opt .required(true) .name("message1") - .kind(ApplicationCommandOptionType::String) + .kind(CommandOptionType::String) .description("Message id of first code-block") }) .create_option(|opt| { opt .required(true) .name("message2") - .kind(ApplicationCommandOptionType::String) + .kind(CommandOptionType::String) .description("Message id of second code-block") }); cmds.push(cmd); diff --git a/src/slashcmds/asm.rs b/src/slashcmds/asm.rs index c841cbc..8306470 100644 --- a/src/slashcmds/asm.rs +++ b/src/slashcmds/asm.rs @@ -1,7 +1,7 @@ use serenity::{ client::Context, framework::standard::{CommandError, CommandResult}, - model::interactions::application_command::ApplicationCommandInteraction, + model::application::interaction::application_command::ApplicationCommandInteraction, }; use crate::{ diff --git a/src/slashcmds/compile.rs b/src/slashcmds/compile.rs index 9a7f577..cd4fe27 100644 --- a/src/slashcmds/compile.rs +++ b/src/slashcmds/compile.rs @@ -1,6 +1,6 @@ use serenity::{ client::Context, framework::standard::CommandError, framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, + model::application::interaction::application_command::ApplicationCommandInteraction, }; use tokio::sync::RwLockReadGuard; diff --git a/src/slashcmds/cpp.rs b/src/slashcmds/cpp.rs index f1008c6..e684bd9 100644 --- a/src/slashcmds/cpp.rs +++ b/src/slashcmds/cpp.rs @@ -1,8 +1,8 @@ use serenity::{ - framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, - model::interactions::application_command::ApplicationCommandInteractionDataOptionValue, - model::prelude::*, prelude::*, + client::Context, framework::standard::CommandResult, + model::application::interaction::application_command::ApplicationCommandInteraction, + model::application::interaction::application_command::CommandDataOptionValue, + model::application::interaction::InteractionResponseType, }; use crate::utls::discordhelpers::embeds::EmbedOptions; @@ -45,7 +45,7 @@ pub async fn cpp(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandR .as_ref() .expect("Expected data option value"); - if let ApplicationCommandInteractionDataOptionValue::String(input) = geordi_input { + if let CommandDataOptionValue::String(input) = geordi_input { let mut eval = CppEval::new(input); let out = eval.evaluate()?; diff --git a/src/slashcmds/diff.rs b/src/slashcmds/diff.rs index 72e8db9..eb1c95f 100644 --- a/src/slashcmds/diff.rs +++ b/src/slashcmds/diff.rs @@ -1,9 +1,9 @@ use serenity::framework::standard::CommandError; use serenity::{ framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, - model::interactions::application_command::ApplicationCommandInteractionDataOptionValue, - model::prelude::*, prelude::*, + model::application::interaction::application_command::ApplicationCommandInteraction, + model::application::interaction::application_command::CommandDataOptionValue, + model::application::interaction::MessageFlags, model::prelude::*, prelude::*, }; use similar::ChangeTag; @@ -35,11 +35,11 @@ pub async fn diff(ctx: &Context, msg: &ApplicationCommandInteraction) -> Command .expect("Expected data option value"); let mut message1_parse = None; - if let ApplicationCommandInteractionDataOptionValue::String(input) = message1 { + if let CommandDataOptionValue::String(input) = message1 { message1_parse = input.parse::().ok(); } let mut message2_parse = None; - if let ApplicationCommandInteractionDataOptionValue::String(input) = message2 { + if let CommandDataOptionValue::String(input) = message2 { message2_parse = input.parse::().ok(); } @@ -56,7 +56,7 @@ pub async fn diff(ctx: &Context, msg: &ApplicationCommandInteraction) -> Command User Settings > Advanced tab", ) }) - .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + .flags(MessageFlags::EPHEMERAL) }) }) .await?; @@ -76,7 +76,7 @@ pub async fn diff(ctx: &Context, msg: &ApplicationCommandInteraction) -> Command this channel and the Message IDs are correct.", ) }) - .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + .flags(MessageFlags::EPHEMERAL) }) }) .await?; diff --git a/src/slashcmds/diff_msg.rs b/src/slashcmds/diff_msg.rs index b9fa0f8..2d9a828 100644 --- a/src/slashcmds/diff_msg.rs +++ b/src/slashcmds/diff_msg.rs @@ -2,7 +2,7 @@ use std::time::Duration; use serenity::{ framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, prelude::*, + model::application::interaction::application_command::ApplicationCommandInteraction, prelude::*, }; use crate::slashcmds::diff::run_diff; diff --git a/src/slashcmds/format.rs b/src/slashcmds/format.rs index 13ec0f8..179d497 100644 --- a/src/slashcmds/format.rs +++ b/src/slashcmds/format.rs @@ -7,9 +7,10 @@ use godbolt::{Format, Godbolt}; use serenity::{ builder::{CreateInteractionResponse, EditInteractionResponse}, framework::standard::{CommandError, CommandResult}, - model::interactions::application_command::ApplicationCommandInteraction, - model::interactions::message_component::ButtonStyle, - model::prelude::*, + model::application::component::ButtonStyle, + model::application::interaction::application_command::ApplicationCommandInteraction, + model::application::interaction::InteractionResponseType, + model::application::interaction::MessageFlags, prelude::*, }; use std::time::Duration; @@ -188,33 +189,30 @@ fn create_formats_interaction<'this, 'a>( ) -> &'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 - }) + data.content("Select a formatter to use:").flags(MessageFlags::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) }) - }) + }) + }) }, ) } diff --git a/src/slashcmds/help.rs b/src/slashcmds/help.rs index 10fd4ba..57f6de1 100644 --- a/src/slashcmds/help.rs +++ b/src/slashcmds/help.rs @@ -1,7 +1,6 @@ use serenity::{ - client::Context, framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, - model::prelude::message_component::ButtonStyle, + client::Context, framework::standard::CommandResult, model::application::component::ButtonStyle, + model::application::interaction::application_command::ApplicationCommandInteraction, }; use crate::{cache::ConfigCache, utls::constants::*}; diff --git a/src/slashcmds/invite.rs b/src/slashcmds/invite.rs index 6bf3757..278166b 100644 --- a/src/slashcmds/invite.rs +++ b/src/slashcmds/invite.rs @@ -1,7 +1,7 @@ use serenity::{ framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, model::prelude::*, - prelude::*, + model::application::interaction::application_command::ApplicationCommandInteraction, + model::application::interaction::InteractionResponseType, prelude::*, }; use crate::{cache::ConfigCache, utls::discordhelpers::embeds}; diff --git a/src/slashcmds/ping.rs b/src/slashcmds/ping.rs index f6d2ebe..96f85c5 100644 --- a/src/slashcmds/ping.rs +++ b/src/slashcmds/ping.rs @@ -1,7 +1,7 @@ use serenity::{ framework::standard::CommandResult, - model::interactions::application_command::ApplicationCommandInteraction, model::prelude::*, - prelude::*, + model::application::interaction::application_command::ApplicationCommandInteraction, + model::application::interaction::InteractionResponseType, prelude::*, }; use std::time::Instant; diff --git a/src/utls/discordhelpers/interactions.rs b/src/utls/discordhelpers/interactions.rs index 1e65043..b0ca2d1 100644 --- a/src/utls/discordhelpers/interactions.rs +++ b/src/utls/discordhelpers/interactions.rs @@ -7,11 +7,11 @@ use serenity::{ builder::{CreateComponents, CreateEmbed, CreateInteractionResponse, CreateSelectMenuOption}, client::Context, framework::standard::CommandError, - 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, + model::application::component::{ActionRowComponent, ButtonStyle, InputTextStyle}, + model::application::interaction::application_command::ApplicationCommandInteraction, + model::application::interaction::message_component::MessageComponentInteraction, + model::application::interaction::modal::ModalSubmitInteraction, + model::application::interaction::{InteractionResponseType, MessageFlags}, }; use crate::cache::ConfigCache; @@ -56,7 +56,7 @@ pub async fn create_more_options_panel( .create_interaction_response(&ctx.http, |resp| { resp.kind(InteractionResponseType::Modal).interaction_response_data(|data| { data - .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + .flags(MessageFlags::EPHEMERAL) .custom_id("more_options_panel") .content("Select a compiler:") .title("More options") @@ -219,25 +219,22 @@ pub fn create_language_interaction<'this, 'a>( languages: &[&str], ) -> &'this mut CreateInteractionResponse<'a> { resp.kind(InteractionResponseType::ChannelMessageWithSource).interaction_response_data(|data| { - 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); - option.label(language); - opts.add_option(option); - } - - opts - }) + data.flags(MessageFlags::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); + option.label(language); + opts.add_option(option); + } + + opts }) }) }) + }) }) } @@ -274,7 +271,7 @@ pub fn create_diff_select_response<'this, 'a>( ) -> &'this mut CreateInteractionResponse<'a> { resp.kind(InteractionResponseType::ChannelMessageWithSource).interaction_response_data(|data| { data - .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + .flags(MessageFlags::EPHEMERAL) .embed(|emb| { emb .color(COLOR_WARN) @@ -329,7 +326,7 @@ where // .create_interaction_response(&ctx.http, |resp| { // resp.kind(InteractionResponseType::DeferredChannelMessageWithSource) // .interaction_response_data(|data| { - // data.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + // data.flags(MessageFlags::EPHEMERAL) // }) // }) // .await?; @@ -345,9 +342,7 @@ where .create_interaction_response(&ctx.http, |resp| { resp .kind(InteractionResponseType::DeferredChannelMessageWithSource) - .interaction_response_data(|data| { - data.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) - }) + .interaction_response_data(|data| data.flags(MessageFlags::EPHEMERAL)) }) .await?; return Err(CommandError::from("Unable to find a codeblock to compile!")); @@ -387,7 +382,7 @@ where let compile_components = create_compile_panel(options); data - .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) + .flags(MessageFlags::EPHEMERAL) .content("Select a compiler:") .set_components(compile_components) }, diff --git a/src/utls/discordhelpers/menu.rs b/src/utls/discordhelpers/menu.rs index b5f68a4..cc56d59 100644 --- a/src/utls/discordhelpers/menu.rs +++ b/src/utls/discordhelpers/menu.rs @@ -2,8 +2,8 @@ use futures_util::StreamExt; use serenity::builder::{CreateComponents, CreateEmbed}; use serenity::client::Context; use serenity::framework::standard::CommandError; +use serenity::model::application::component::ButtonStyle; use serenity::model::channel::Message; -use serenity::model::interactions::message_component::ButtonStyle; use std::time::Duration; pub struct Menu { diff --git a/src/utls/discordhelpers/mod.rs b/src/utls/discordhelpers/mod.rs index ac1e36f..e58fe24 100644 --- a/src/utls/discordhelpers/mod.rs +++ b/src/utls/discordhelpers/mod.rs @@ -244,8 +244,8 @@ pub fn conform_external_str(input: &str, max_len: usize) -> String { pub async fn manual_dispatch(http: Arc, id: u64, emb: CreateEmbed) { if let Err(e) = serenity::model::id::ChannelId(id) .send_message(&http, |m| { - m.embed(|mut e| { - e.0 = emb.0; + m.embed(|e| { + *e = emb; e }) }) From 6b2fd3063364ac506b05d8e3613435d9c0b7067f Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 4 Mar 2023 17:19:08 +0100 Subject: [PATCH 64/81] =?UTF-8?q?cargo=20stuff=20[arm]=20[x86=5F64]=20[ver?= =?UTF-8?q?sion]=20=E2=9A=99=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5354a04..6c7ae9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "discord-compiler-bot" description = "๐Ÿค– discord bot to compile your spaghetti code" -version = "1.3.1" +version = "1.4.2" authors = ["ThomasByr"] edition = "2021" build = "src/build.rs" @@ -15,7 +15,7 @@ reqwest = { version = "0.11" } dotenv = "0.15.0" regex = "1" log = "0.4" -pretty_env_logger = "0.3" +pretty_env_logger = "0.4" strip-ansi-escapes = "0.1.0" serde = { version = "1.0.*", features = ["derive"] } serde_json = "1.0" @@ -27,7 +27,7 @@ const_format = "0.2" lazy_static = "1.4.0" similar = "2.1.0" #tests -indoc = "1.0.3" +indoc = "2.0" test-context = "0.1" #dbl dbl-rs = "0.3" @@ -39,7 +39,7 @@ chrono = "0.4.19" openssl = { version = "0.10", features = ["vendored"] } [dependencies.serenity] -version = "=0.11.1" +version = "=0.11.5" default-features = false features = [ "collector", From 34ca42df3a30d5af9701c2ee635bf51a3b1839f2 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 4 Mar 2023 17:19:32 +0100 Subject: [PATCH 65/81] the full history, or so was I told... --- changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changelog.md b/changelog.md index ad20659..c02a246 100644 --- a/changelog.md +++ b/changelog.md @@ -75,3 +75,8 @@ - added `c#` and `cs` aliases for `csharp` - ... and many more + +**v1.4** dependencies + +- added `.rustfmt.toml` to enforce a consistent style +- updated dependencies (notably serenity to `0.11.5`) From 2b2544ca20ab4ef16b977dc024bc799e2927713c Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sat, 4 Mar 2023 17:26:22 +0100 Subject: [PATCH 66/81] =?UTF-8?q?latest=20version=20of=20`README`=20file?= =?UTF-8?q?=20=F0=9F=91=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 94e53ee..66e297b 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,11 @@ fn main() -> Result<(), Box> { 2. [๐Ÿ‘ฉโ€๐Ÿซ Usage](#-usage) 3. [๐Ÿ’ Get Help](#-get-help) 4. [๐Ÿ”ฐ Support](#-support) -5. [โš–๏ธ License](#๏ธ-license) -6. [๐Ÿ”„ Changelog and contributing](#-changelog-and-contributing) -7. [๐Ÿ› Bugs and TODO](#-bugs-and-todo) +5. [๐Ÿงช Testing](#-testing) +6. [๐Ÿง‘โ€๐Ÿซ Contributing](#-contributing) +7. [โš–๏ธ License](#๏ธ-license) +8. [๐Ÿ”„ Changelog](#-changelog) +9. [๐Ÿ› Bugs and TODO](#-bugs-and-todo) ## โœ๏ธ In short @@ -89,6 +91,35 @@ On a side note, support has been added for ARM architectures. Meaning you can no > [Create a new issue](https://github.com/ThomasByr/discord-compiler-bot/issues/new) +## ๐Ÿงช Testing + +Oh god... please don't. + +Still, make sure you have `valgrind` up and ready and then run : + +```bash +cargo test --all +``` + +## ๐Ÿง‘โ€๐Ÿซ Contributing + +If you ever want to contribute, either request the contributor status, or, more manually, fork the repo and make a full request !. On a more generic note, please do respect the [Rust Coding Conventions](https://rustc-dev-guide.rust-lang.org/conventions.html) and wait for your PR to be reviewed. Make sure you respect and read the [Contributing Guidelines](.github/CONTRIBUTING.md), make pull requests and be kind. + +> The standard procedure is : +> +> ```txt +> fork -> git branch -> push -> pull request +> ``` +> +> Note that we won't accept any PR : +> +> - that does not follow our Contributing Guidelines +> - that is not sufficiently commented or isn't well formated +> - without any proper test suite +> - with a failing or incomplete test suite + +Happy coding ! ๐Ÿ™‚ + ## โš–๏ธ License This project is licensed under the AGPL-3.0 new or revised license. Please read the [LICENSE](LICENSE) file. @@ -101,12 +132,10 @@ This project is licensed under the AGPL-3.0 new or revised license. Please read THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -## ๐Ÿ”„ Changelog and contributing +## ๐Ÿ”„ Changelog Please read the [changelog](changelog.md) file for the full history ! -If you ever want to contribute to this project, either request the contributor status, or, more manually, fork the repo and make a full request ! On a more generic note, please do respect the [Rust Coding Conventions](https://rustc-dev-guide.rust-lang.org/conventions.html) and wait for your PR to be reviewed. Make sure you respect and read the [contributing](.github/CONTRIBUTING.md) guideline, make pull requests and be kind. -
First stable release version (click here to expand) @@ -136,6 +165,11 @@ If you ever want to contribute to this project, either request the contributor s - added `c#` and `cs` aliases for `csharp` - ... and many more +**v1.4** dependencies + +- added `.rustfmt.toml` to enforce a consistent style +- updated dependencies (notably serenity to `0.11.5`) +
## ๐Ÿ› Bugs and TODO @@ -152,3 +186,4 @@ If you ever want to contribute to this project, either request the contributor s - ~~`;botinfo` command not working~~ (v0.1.0) - ~~total number of servers joined always showing 0~~ (v0.1.1) - ~~debug commands not showing up in console (might be linked to previous bug)~~ (v0.1.1) +- some shards are randomly disconnecting From f3d7ec22bbd4e6d4ed37d2725fb484fbacec530d Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 5 Mar 2023 12:36:17 +0100 Subject: [PATCH 67/81] fix format for methods with no alternative styles --- src/slashcmds/format.rs | 44 ++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/slashcmds/format.rs b/src/slashcmds/format.rs index 179d497..a68efc4 100644 --- a/src/slashcmds/format.rs +++ b/src/slashcmds/format.rs @@ -78,28 +78,32 @@ pub async fn format(ctx: &Context, command: &ApplicationCommandInteraction) -> C .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)); - cic = cib.build(); - selected = false; + // Google is our default value for clang-fmt let mut style = String::from("Google"); - while let Some(interaction) = &cic.next().await { - match interaction.data.custom_id.as_str() { - "style" => { - style = interaction.data.values[0].clone(); - interaction.defer(&ctx.http).await?; - } - "select" => { - selected = true; - cic.stop(); - break; - } - _ => { - unreachable!("Cannot get here.."); + if !styles.is_empty() { + 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)); + cic = cib.build(); + selected = false; + while let Some(interaction) = &cic.next().await { + match interaction.data.custom_id.as_str() { + "style" => { + style = interaction.data.values[0].clone(); + interaction.defer(&ctx.http).await?; + } + "select" => { + selected = true; + cic.stop(); + break; + } + _ => { + unreachable!("Cannot get here.."); + } } } } From bdfbda4721c047bb342849c43bc540de74e194cd Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 5 Mar 2023 12:36:54 +0100 Subject: [PATCH 68/81] unify embed sending --- src/commands/asm.rs | 9 +--- src/commands/compile.rs | 9 +--- src/commands/cpp.rs | 9 +--- src/commands/formats.rs | 9 +--- src/commands/help.rs | 10 +---- src/commands/invite.rs | 10 +---- src/events.rs | 70 +++++++------------------------ src/utls/discordhelpers/embeds.rs | 15 +++++++ 8 files changed, 35 insertions(+), 106 deletions(-) diff --git a/src/commands/asm.rs b/src/commands/asm.rs index a47f39b..1b37061 100644 --- a/src/commands/asm.rs +++ b/src/commands/asm.rs @@ -20,14 +20,7 @@ use crate::utls::parser; #[bucket = "nospam"] pub async fn asm(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let emb = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; - let emb_msg = embeds::embed_message(emb); - let asm_embed = msg - .channel_id - .send_message(&ctx.http, |e| { - *e = emb_msg; - e - }) - .await?; + let asm_embed = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; // Success/fail react let compilation_successful = asm_embed.embeds[0].colour.unwrap().0 == COLOR_OKAY; diff --git a/src/commands/compile.rs b/src/commands/compile.rs index f755ac5..8d639cc 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -27,14 +27,7 @@ pub async fn compile(ctx: &Context, msg: &Message, _args: Args) -> CommandResult let embed = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; // Send our final embed - let message = embeds::embed_message(embed); - let compilation_embed = msg - .channel_id - .send_message(&ctx.http, |e| { - *e = message; - e - }) - .await?; + let compilation_embed = embeds::dispatch_embed(&ctx.http, msg.channel_id, embed).await?; // Success/fail react let compilation_successful = compilation_embed.embeds[0].colour.unwrap().0 == COLOR_OKAY; diff --git a/src/commands/cpp.rs b/src/commands/cpp.rs index d42513d..f28b61d 100644 --- a/src/commands/cpp.rs +++ b/src/commands/cpp.rs @@ -20,16 +20,9 @@ use crate::{ #[bucket = "nospam"] pub async fn cpp(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let emb = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; - let emb_msg = embeds::embed_message(emb); // Dispatch our request - let compilation_embed = msg - .channel_id - .send_message(&ctx.http, |e| { - *e = emb_msg; - e - }) - .await?; + let compilation_embed = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; // add delete cache let data_read = ctx.data.read().await; diff --git a/src/commands/formats.rs b/src/commands/formats.rs index c40d236..1578e53 100644 --- a/src/commands/formats.rs +++ b/src/commands/formats.rs @@ -44,14 +44,7 @@ pub async fn formats(ctx: &Context, msg: &Message, _args: Args) -> CommandResult emb.field(&format.format_type, &output, false); } - let emb_msg = embeds::embed_message(emb); - msg - .channel_id - .send_message(&ctx.http, |e| { - *e = emb_msg; - e - }) - .await?; + embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; return Ok(()); } diff --git a/src/commands/help.rs b/src/commands/help.rs index f93e66e..491b54b 100644 --- a/src/commands/help.rs +++ b/src/commands/help.rs @@ -120,15 +120,7 @@ pub async fn help(ctx: &Context, msg: &Message, args: Args) -> CommandResult { }; emb.description(description); - - let emb_msg = embeds::embed_message(emb); - msg - .channel_id - .send_message(&ctx.http, |e| { - *e = emb_msg; - e - }) - .await?; + embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; return Ok(()); } diff --git a/src/commands/invite.rs b/src/commands/invite.rs index 60cfc98..aee7b8e 100644 --- a/src/commands/invite.rs +++ b/src/commands/invite.rs @@ -11,15 +11,7 @@ pub async fn invite(ctx: &Context, msg: &Message, _: Args) -> CommandResult { let invite = env::var("INVITE_LINK").expect("Expected invite link envvar"); let emb = embeds::build_invite_embed(&invite); - - let emb_msg = embeds::embed_message(emb); - msg - .channel_id - .send_message(&ctx.http, |e| { - *e = emb_msg; - e - }) - .await?; + embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; Ok(()) } diff --git a/src/events.rs b/src/events.rs index c1b8b62..fc238ca 100644 --- a/src/events.rs +++ b/src/events.rs @@ -111,13 +111,8 @@ impl EventHandler for Handler { info!("Joining {}", guild.name); if let Some(system_channel) = guild.system_channel_id { - let message = embeds::embed_message(embeds::build_welcome_embed()); - let _ = system_channel - .send_message(&ctx.http, |e| { - *e = message; - e - }) - .await; + let _ = + embeds::dispatch_embed(&ctx.http, system_channel, embeds::build_welcome_embed()).await; } } } @@ -205,30 +200,16 @@ impl EventHandler for Handler { Ok(emb) => emb, Err(e) => { let emb = embeds::build_fail_embed(&new_message.author, &format!("{}", e)); - let emb_msg = embeds::embed_message(emb); - if let Ok(sent) = new_message - .channel_id - .send_message(&ctx.http, |e| { - *e = emb_msg; - e - }) - .await - { + let sent_fail = + embeds::dispatch_embed(&ctx.http, new_message.channel_id, emb).await; + if let Ok(sent) = sent_fail { 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 = embeds::embed_message(emb); - emb_msg.reference_message(&new_message); - let _ = new_message - .channel_id - .send_message(&ctx.http, |e| { - *e = emb_msg; - e - }) - .await; + let _ = embeds::dispatch_embed(&ctx.http, new_message.channel_id, emb).await; } } } @@ -351,20 +332,11 @@ pub async fn before(ctx: &Context, msg: &Message, _: &str) -> bool { If you feel that this has been done in error, request an unban in the support server.", ); - let emb_msg = embeds::embed_message(emb); - let msg_response = msg - .channel_id - .send_message(&ctx.http, |e| { - *e = emb_msg; - e - }) - .await; - if msg_response.is_ok() { - if author_blocklisted { - warn!("Blocked user {} [{}]", msg.author.tag(), msg.author.id.0); - } else { - warn!("Blocked guild {}", guild_id); - } + let _ = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await; + if author_blocklisted { + warn!("Blocked user {} [{}]", msg.author.tag(), msg.author.id.0); + } else { + warn!("Blocked guild {}", guild_id); } return false; } @@ -384,15 +356,8 @@ pub async fn after( if let Err(e) = command_result { let emb = embeds::build_fail_embed(&msg.author, &format!("{}", e)); - let emb_msg = embeds::embed_message(emb); - if let Ok(sent) = msg - .channel_id - .send_message(&ctx.http, |e| { - *e = emb_msg; - e - }) - .await - { + let sent_fail = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await; + if let Ok(sent) = sent_fail { let mut message_cache = data.get::().unwrap().lock().await; message_cache.insert(msg.id.0, MessageCacheEntry::new(sent, msg.clone())); } @@ -409,13 +374,6 @@ pub async fn after( 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_msg = embeds::embed_message(emb); - let _ = msg - .channel_id - .send_message(&ctx.http, |e| { - *e = emb_msg; - e - }) - .await; + let _ = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await; } } diff --git a/src/utls/discordhelpers/embeds.rs b/src/utls/discordhelpers/embeds.rs index cf1c86a..0683454 100644 --- a/src/utls/discordhelpers/embeds.rs +++ b/src/utls/discordhelpers/embeds.rs @@ -1,6 +1,7 @@ use std::fmt::Write as _; use std::{env, str}; +use serenity::http::Http; use serenity::{ builder::{CreateEmbed, CreateMessage}, client::Context, @@ -246,6 +247,20 @@ pub fn embed_message(emb: CreateEmbed) -> CreateMessage<'static> { msg } +pub async fn dispatch_embed( + http: impl AsRef, + channel: ChannelId, + emb: CreateEmbed, +) -> serenity::Result { + let emb_msg = embed_message(emb); + channel + .send_message(http, |e| { + *e = emb_msg; + e + }) + .await +} + pub fn build_dblvote_embed(tag: String) -> CreateEmbed { let mut embed = CreateEmbed::default(); embed.color(COLOR_OKAY); From c1a4648ce426f699ca6895be7458d614d6363a2b Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 5 Mar 2023 12:42:11 +0100 Subject: [PATCH 69/81] the full history, or so was I told... --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index c02a246..403093b 100644 --- a/changelog.md +++ b/changelog.md @@ -80,3 +80,4 @@ - added `.rustfmt.toml` to enforce a consistent style - updated dependencies (notably serenity to `0.11.5`) +- finally unified embed dispatching From d6484c35f55eca1910c8c4b0b879fed7b966f93c Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 5 Mar 2023 12:42:30 +0100 Subject: [PATCH 70/81] =?UTF-8?q?latest=20version=20of=20`README`=20file?= =?UTF-8?q?=20=F0=9F=91=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 66e297b..c3cef28 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,7 @@ Please read the [changelog](changelog.md) file for the full history ! - added `.rustfmt.toml` to enforce a consistent style - updated dependencies (notably serenity to `0.11.5`) +- finally unified embed dispatching From 42606ad20fd0dfc6d30c910083f7c655c7b8085a Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Sun, 5 Mar 2023 17:11:22 +0100 Subject: [PATCH 71/81] `debug!` for each command --- src/commands/block.rs | 1 + src/commands/cpp.rs | 1 + src/commands/format.rs | 2 ++ src/commands/formats.rs | 1 + src/commands/invite.rs | 1 + src/commands/unblock.rs | 1 + src/slashcmds/format.rs | 1 + 7 files changed, 8 insertions(+) diff --git a/src/commands/block.rs b/src/commands/block.rs index ccf935e..f0e2272 100644 --- a/src/commands/block.rs +++ b/src/commands/block.rs @@ -19,5 +19,6 @@ 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?; + debug!("Command executed"); Ok(()) } diff --git a/src/commands/cpp.rs b/src/commands/cpp.rs index f28b61d..6004389 100644 --- a/src/commands/cpp.rs +++ b/src/commands/cpp.rs @@ -29,6 +29,7 @@ pub async fn cpp(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let mut delete_cache = data_read.get::().unwrap().lock().await; delete_cache.insert(msg.id.0, MessageCacheEntry::new(compilation_embed, msg.clone())); + debug!("Command executed"); Ok(()) } diff --git a/src/commands/format.rs b/src/commands/format.rs index c2b5dbf..df4fbc4 100644 --- a/src/commands/format.rs +++ b/src/commands/format.rs @@ -129,5 +129,7 @@ pub async fn format(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu .reply(&ctx.http, format!("\n```{}\n{}```\n*Powered by MS Azure*", lang_code, answer)) .await?; } + + debug!("Command executed"); Ok(()) } diff --git a/src/commands/formats.rs b/src/commands/formats.rs index 1578e53..d7d3f51 100644 --- a/src/commands/formats.rs +++ b/src/commands/formats.rs @@ -46,5 +46,6 @@ pub async fn formats(ctx: &Context, msg: &Message, _args: Args) -> CommandResult embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; + debug!("Command executed"); return Ok(()); } diff --git a/src/commands/invite.rs b/src/commands/invite.rs index aee7b8e..1fc001a 100644 --- a/src/commands/invite.rs +++ b/src/commands/invite.rs @@ -13,5 +13,6 @@ pub async fn invite(ctx: &Context, msg: &Message, _: Args) -> CommandResult { let emb = embeds::build_invite_embed(&invite); embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; + debug!("Command executed"); Ok(()) } diff --git a/src/commands/unblock.rs b/src/commands/unblock.rs index e57b86e..8911d36 100644 --- a/src/commands/unblock.rs +++ b/src/commands/unblock.rs @@ -19,5 +19,6 @@ 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?; + debug!("Command executed"); Ok(()) } diff --git a/src/slashcmds/format.rs b/src/slashcmds/format.rs index a68efc4..aa9a92b 100644 --- a/src/slashcmds/format.rs +++ b/src/slashcmds/format.rs @@ -156,6 +156,7 @@ pub async fn format(ctx: &Context, command: &ApplicationCommandInteraction) -> C }) .await?; + debug!("Command executed"); Ok(()) } From 6b96e22d313bef747b80db26a9ec72e55f44a92c Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 6 Mar 2023 21:45:20 +0100 Subject: [PATCH 72/81] godbolt api : local dependency [https] --- godbolt/Cargo.toml | 4 +- godbolt/src/lib.rs | 84 ++++++++++++++++++++++++++++++++++++-- godbolt/src/tests/tests.rs | 73 +++++++-------------------------- 3 files changed, 98 insertions(+), 63 deletions(-) diff --git a/godbolt/Cargo.toml b/godbolt/Cargo.toml index bbfa528..16070f9 100644 --- a/godbolt/Cargo.toml +++ b/godbolt/Cargo.toml @@ -3,13 +3,15 @@ name = "godbolt" version = "0.1.0" authors = ["ThomasByr"] edition = "2021" -license = "LGPL-3.0" +license = "AGPL-3.0" readme = "README.md" keywords = ["Godbolt"] categories = ["api-bindings", "web-programming::http-client"] [dependencies] serde_json = "1.0" +base64 = "0.21" +urlencoding = "2.1" serde = { version = "1.0.*", features = ["derive"] } reqwest = { version = "0.11", features = ["json"] } tokio = { version = "1", features = ["macros"] } diff --git a/godbolt/src/lib.rs b/godbolt/src/lib.rs index c212c0f..8b5e218 100644 --- a/godbolt/src/lib.rs +++ b/godbolt/src/lib.rs @@ -1,3 +1,4 @@ +use base64::{engine, Engine}; use reqwest::header::{ACCEPT, USER_AGENT}; use serde::*; use std::error::Error; @@ -229,6 +230,40 @@ pub struct GodboltError { details: String, } +#[derive(Clone, Debug, Serialize, Default)] +pub struct ClientState { + pub sessions: Vec, +} + +#[derive(Clone, Debug, Serialize, Default)] +pub struct Session { + pub id: i32, + pub language: String, + pub source: String, + pub compilers: Vec, + pub executors: Vec, +} + +#[derive(Clone, Debug, Serialize, Default)] +pub struct SessionCompiler { + pub id: String, + pub options: String, +} + +#[derive(Clone, Debug, Serialize, Default)] +pub struct Executor { + pub arguments: String, + pub compiler: ExecutorCompiler, + pub stdin: String, +} + +#[derive(Clone, Debug, Serialize, Default)] +pub struct ExecutorCompiler { + pub id: String, + pub libs: Vec, + pub options: String, +} + impl GodboltError { fn new(msg: &str) -> GodboltError { GodboltError { details: msg.to_string() } @@ -309,8 +344,6 @@ impl Godbolt { let client = reqwest::Client::new(); let endpoint = format!("https://godbolt.org/api/compiler/{}/compile", c.id); - //println!("Sent: {}", serde_json::to_string(&req).unwrap()); - let result = match client .post(&endpoint) .json(&req) @@ -328,7 +361,6 @@ impl Godbolt { Err(e) => return Err(GodboltError::new(&format!("{}", e))), }; - //println!("Recieved: {}", text); let res = match serde_json::from_str::(&text) { Ok(res) => res, Err(e) => return Err(GodboltError::new(&format!("{}", e))), @@ -337,6 +369,52 @@ impl Godbolt { Ok(res) } + pub fn get_base64( + c: &Compiler, + source: &str, + options: RequestOptions, + ) -> Result { + let cstate = ClientState { + sessions: vec![Session { + id: 0, + language: c.lang.clone(), + source: source.to_string(), + compilers: vec![SessionCompiler { + id: c.id.clone(), + options: options.user_arguments.clone(), + }], + executors: vec![Executor { + arguments: options.execute_parameters.args.join(" "), + compiler: ExecutorCompiler { + id: c.id.clone(), + libs: vec![], + options: options.user_arguments, + }, + stdin: options.execute_parameters.stdin, + }], + }], + }; + + let str = match serde_json::to_string::(&cstate) { + Ok(str) => str, + Err(e) => { + return Err(GodboltError::new(&e.to_string())); + } + }; + + let mut buf = Vec::new(); + buf.resize(str.len() * 4 / 3 + 4, 0); + + match engine::general_purpose::STANDARD.encode_slice(str, &mut buf) { + Ok(_) => { + let str = String::from_utf8(buf).unwrap(); + Ok(String::from(str)) + } + Err(e) => { + return Err(GodboltError::new(&e.to_string())); + } + } + } /// Retrieves a vector of languages pub async fn get_languages() -> Result, Box> { static LANGUAGE_ENDPOINT: &str = diff --git a/godbolt/src/tests/tests.rs b/godbolt/src/tests/tests.rs index 8ff6b0e..75d6850 100644 --- a/godbolt/src/tests/tests.rs +++ b/godbolt/src/tests/tests.rs @@ -1,4 +1,4 @@ -use crate::{CompilationFilters, Godbolt}; +use crate::{CompilationFilters, CompilerOptions, ExecuteParameters, Godbolt, RequestOptions}; use std::error::Error; #[tokio::test] @@ -30,38 +30,9 @@ async fn get_libraries_for() -> Result<(), Box> { } #[tokio::test] -async fn godbolt_exec_asm() -> Result<(), Box> { +async fn base64() -> Result<(), Box> { let gbolt = Godbolt::new().await?; - let cplusplus = gbolt.cache.iter().find(|p| p.language.name == "C++").unwrap(); - let compiler = &cplusplus.compilers[0]; - - let mut filters = CompilationFilters::default(); - filters.execute = Some(true); - - Godbolt::send_request(compiler, "int sum(int a, int b) { return a + b; }", "-O3", &filters) - .await?; - Ok(()) -} - -#[tokio::test] -async fn godbolt_exec_asm_fail() -> Result<(), Box> { - let gbolt = Godbolt::new().await?; - let cplusplus = gbolt.cache.iter().find(|p| p.language.name == "C++").unwrap(); - let compiler = &cplusplus.compilers[0]; - - let mut filters = CompilationFilters::default(); - filters.execute = Some(true); - - Godbolt::send_request(compiler, "int sum(iwnt a, int b) { return a + b; }", "-O3", &filters) - .await?; - Ok(()) -} - -#[tokio::test] -async fn godbolt_exec_asm_filters() -> Result<(), Box> { - let gbolt = Godbolt::new().await?; - let cplusplus = gbolt.cache.iter().find(|p| p.language.name == "C++").unwrap(); - let compiler = &cplusplus.compilers[0]; + let c = gbolt.resolve("clang1000").unwrap(); let filters = CompilationFilters { binary: None, @@ -74,35 +45,19 @@ async fn godbolt_exec_asm_filters() -> Result<(), Box> { library_code: None, trim: None, }; - Godbolt::send_request(compiler, "int sum(int a, int b) { return a + b; }", "-O3", &filters) - .await?; - Ok(()) -} - -#[tokio::test] -async fn godbolt_exec_asm_filters_fail() -> Result<(), Box> { - let gbolt = Godbolt::new().await?; - let cplusplus = gbolt.cache.iter().find(|p| p.language.name == "C++").unwrap(); - let compiler = &cplusplus.compilers[0]; - let filters = CompilationFilters { - binary: None, - comment_only: Some(true), - demangle: Some(true), - directives: Some(true), - execute: None, - intel: Some(true), - labels: Some(true), - library_code: None, - trim: None, + let opts = RequestOptions { + user_arguments: "-O3".to_string(), + compiler_options: CompilerOptions { skip_asm: false, executor_request: true }, + execute_parameters: ExecuteParameters { + args: vec![String::from("awd")], + stdin: "teststdin".to_string(), + }, + filters, }; - Godbolt::send_request( - compiler, - "#include \nint main() {\nstd::cout << \"Test\";\n}", - "-O3", - &filters, - ) - .await?; + let str = + Godbolt::get_base64(&c, "#include \nint main() {\nstd::cout << \"๐Ÿ˜‚\";\n}", opts)?; + assert!(str.len() > 0); Ok(()) } From 6f654b3fc22dbce4a8bd52119eb6d38afec61720 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 6 Mar 2023 21:46:47 +0100 Subject: [PATCH 73/81] added redirect button & updated compile service --- src/apis/mod.rs | 1 + src/apis/quick_link.rs | 47 +++++++++++++++ src/boilerplate/c.rs | 26 +------- src/boilerplate/cpp.rs | 2 +- src/boilerplate/csharp.rs | 2 +- src/boilerplate/java.rs | 7 +-- src/cache.rs | 20 ++++++- src/commands/asm.rs | 47 ++++++++++++--- src/commands/compile.rs | 57 +++++++++++++----- src/commands/cpp.rs | 12 ++-- src/commands/format.rs | 5 +- src/commands/formats.rs | 8 +-- src/commands/invite.rs | 1 - src/cppeval/eval.rs | 4 +- src/events.rs | 39 ++++++++++-- src/main.rs | 2 - src/managers/compilation.rs | 40 ++++++++----- src/slashcmds/cpp.rs | 3 +- src/slashcmds/diff.rs | 5 +- src/slashcmds/diff_msg.rs | 1 - src/slashcmds/format.rs | 2 - src/tests/cpp.rs | 2 +- src/tests/mod.rs | 1 + src/tests/parser.rs | 14 ++--- src/utls/constants.rs | 11 ++-- src/utls/discordhelpers/embeds.rs | 80 +++++++++++++++++-------- src/utls/discordhelpers/interactions.rs | 19 ------ src/utls/discordhelpers/menu.rs | 2 +- src/utls/discordhelpers/mod.rs | 32 +++++----- src/utls/parser.rs | 28 +++++---- 30 files changed, 326 insertions(+), 194 deletions(-) create mode 100644 src/apis/quick_link.rs diff --git a/src/apis/mod.rs b/src/apis/mod.rs index 0f1cd3d..e290c85 100644 --- a/src/apis/mod.rs +++ b/src/apis/mod.rs @@ -1 +1,2 @@ pub mod dbl; +pub mod quick_link; diff --git a/src/apis/quick_link.rs b/src/apis/quick_link.rs new file mode 100644 index 0000000..3fdf316 --- /dev/null +++ b/src/apis/quick_link.rs @@ -0,0 +1,47 @@ +use reqwest::StatusCode; + +pub struct LinkAPI { + client: reqwest::Client, + request_base: String, + redirect_base: String, +} + +impl LinkAPI { + pub fn new(request_base: &str, redirect_base: &str) -> Self { + LinkAPI { + client: reqwest::Client::new(), + request_base: request_base.to_string(), + redirect_base: redirect_base.to_string(), + } + } + + pub async fn get_link(&self, url: String) -> Option { + let trimmed = url.trim_end().to_string(); + let req_result = self + .client + .post(&self.request_base) + .header("Content-Type", "text/plain") + .body(trimmed) + .send() + .await; + if let Err(e) = req_result { + warn!("Quick link request failure: {}", e); + return None; + } + let req = req_result.unwrap(); + if req.status() != StatusCode::OK { + warn!("Received non-ok status code."); + return None; + } + + let body = req.text().await; + if let Err(e) = body { + warn!("Unable to get quick link: {}", e); + None + } else { + let url = format!("{}{}", self.redirect_base, body.unwrap()); + info!("Generated url: {}", &url); + Some(url) + } + } +} diff --git a/src/boilerplate/c.rs b/src/boilerplate/c.rs index 98eda88..42a76d0 100644 --- a/src/boilerplate/c.rs +++ b/src/boilerplate/c.rs @@ -1,6 +1,6 @@ -use crate::boilerplate::generator::BoilerPlateGenerator; use std::fmt::Write as _; +use crate::boilerplate::generator::BoilerPlateGenerator; use crate::utls::constants::C_LIKE_MAIN_REGEX; pub struct CGenerator { @@ -31,29 +31,9 @@ impl BoilerPlateGenerator for CGenerator { } } - if (main_body.contains("printf") || main_body.contains("scanf")) && !header.contains("stdio.h") - { - header.push_str("#include \n"); - } - if (main_body.contains("malloc") - || main_body.contains("free") - || main_body.contains("realloc") - || main_body.contains("calloc") - || main_body.contains("EXIT_FAILURE") - || main_body.contains("EXIT_SUCCESS")) - && !header.contains("stdlib.h") - { - header.push_str("#include "); + if main_body.contains("printf") && !header.contains("stdio.h") { + header.push_str("#include \n") } - if (main_body.contains("strlen") || main_body.contains("strcmp")) - && !header.contains("string.h") - { - header.push_str("#include \n"); - } - if (main_body.contains("time") || main_body.contains("ctime")) && !header.contains("time.h") { - header.push_str("#include \n"); - } - format!("{}\nint main(void) {{\n{}return 0;\n}}", header, main_body) } diff --git a/src/boilerplate/cpp.rs b/src/boilerplate/cpp.rs index 01afd7f..dfa226a 100644 --- a/src/boilerplate/cpp.rs +++ b/src/boilerplate/cpp.rs @@ -1,6 +1,6 @@ -use crate::boilerplate::generator::BoilerPlateGenerator; use std::fmt::Write as _; +use crate::boilerplate::generator::BoilerPlateGenerator; use crate::utls::constants::C_LIKE_MAIN_REGEX; pub struct CppGenerator { diff --git a/src/boilerplate/csharp.rs b/src/boilerplate/csharp.rs index dd6c132..43437d1 100644 --- a/src/boilerplate/csharp.rs +++ b/src/boilerplate/csharp.rs @@ -1,6 +1,6 @@ -use crate::boilerplate::generator::BoilerPlateGenerator; use std::fmt::Write as _; +use crate::boilerplate::generator::BoilerPlateGenerator; use crate::utls::constants::CSHARP_MAIN_REGEX; pub struct CSharpGenerator { diff --git a/src/boilerplate/java.rs b/src/boilerplate/java.rs index cbc8f02..cbc8d68 100644 --- a/src/boilerplate/java.rs +++ b/src/boilerplate/java.rs @@ -1,6 +1,6 @@ -use crate::boilerplate::generator::BoilerPlateGenerator; use std::fmt::Write as _; +use crate::boilerplate::generator::BoilerPlateGenerator; use crate::utls::constants::JAVA_MAIN_REGEX; pub struct JavaGenerator { @@ -31,11 +31,6 @@ impl BoilerPlateGenerator for JavaGenerator { } } - // if they included nothing, we can just manually include everything - if !header.contains("import") { - header.push_str("import java.util.*;\n"); - } - format!( "{}\nclass Main{{\npublic static void main(String[] args) throws Exception {{\n{}}}}}", header, main_body diff --git a/src/cache.rs b/src/cache.rs index 8e25266..a74ec77 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -13,6 +13,7 @@ use serenity::prelude::{TypeMap, TypeMapKey}; use crate::managers::stats::StatsManager; use crate::utls::blocklist::Blocklist; +use crate::apis::quick_link::LinkAPI; use crate::managers::command::CommandManager; use crate::managers::compilation::CompilationManager; use lru_cache::LruCache; @@ -57,6 +58,12 @@ impl TypeMapKey for ShardManagerCache { type Value = Arc>; } +/// Contains the quick link api - used for godbolt button +pub struct LinkAPICache; +impl TypeMapKey for LinkAPICache { + type Value = Arc>; +} + pub struct MessageCacheEntry { pub our_msg: Message, pub original_msg: Message, @@ -119,12 +126,12 @@ pub async fn fill( let emoji_identifiers = [ "SUCCESS_EMOJI_ID", "SUCCESS_EMOJI_NAME", - "FAIL_EMOJI_ID", "FAIL_EMOJI_NAME", + "FAIL_EMOJI_ID", "LOADING_EMOJI_ID", "LOADING_EMOJI_NAME", - "LOGO_EMOJI_ID", "LOGO_EMOJI_NAME", + "LOGO_EMOJI_ID", ]; for id in &emoji_identifiers { if let Ok(envvar) = env::var(id) { @@ -168,6 +175,15 @@ pub async fn fill( data.insert::(Arc::new(RwLock::new(client))); } + // DBL + if let Ok(redirect_base) = env::var("QUICK_LINK_URL") { + if let Ok(request_base) = env::var("QUICK_LINK_POST") { + info!("Registered quick link api"); + let link_man = LinkAPI::new(&request_base, &redirect_base); + data.insert::(Arc::new(RwLock::new(link_man))); + } + } + // Stats tracking let stats = StatsManager::new(); if stats.should_track() { diff --git a/src/commands/asm.rs b/src/commands/asm.rs index 1b37061..084b1c7 100644 --- a/src/commands/asm.rs +++ b/src/commands/asm.rs @@ -1,26 +1,55 @@ +use std::fmt::Write as _; + use serenity::{ client::Context, framework::standard::{macros::command, Args, CommandError, CommandResult}, }; -use crate::cache::{CompilerCache, ConfigCache, MessageCache, MessageCacheEntry}; +use crate::cache::{CompilerCache, ConfigCache, LinkAPICache, MessageCache, MessageCacheEntry}; use crate::utls::constants::*; use crate::utls::discordhelpers; use crate::utls::discordhelpers::embeds; +use crate::managers::compilation::CompilationDetails; use serenity::builder::CreateEmbed; +use serenity::model::application::component::ButtonStyle; use serenity::model::channel::{Message, ReactionType}; use serenity::model::user::User; -use std::fmt::Write as _; - use crate::utls::parser; #[command] #[bucket = "nospam"] pub async fn asm(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let emb = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; - let asm_embed = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; + let (embed, compilation_details) = + handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; + + // Send our final embed + let mut new_msg = embeds::embed_message(embed); + let data = ctx.data.read().await; + if let Some(link_cache) = data.get::() { + if let Some(b64) = compilation_details.base64 { + let long_url = format!("https://godbolt.org/clientstate/{}", b64); + let link_cache_lock = link_cache.read().await; + if let Some(url) = link_cache_lock.get_link(long_url).await { + new_msg.components(|cmp| { + cmp.create_action_row(|row| { + row.create_button(|btn| { + btn.style(ButtonStyle::Link).url(url).label("View on godbolt.org") + }) + }) + }); + } + } + } + + let asm_embed = msg + .channel_id + .send_message(&ctx.http, |e| { + *e = new_msg.clone(); + e + }) + .await?; // Success/fail react let compilation_successful = asm_embed.embeds[0].colour.unwrap().0 == COLOR_OKAY; @@ -38,7 +67,7 @@ pub async fn handle_request( mut content: String, author: User, msg: &Message, -) -> Result { +) -> Result<(CreateEmbed, CompilationDetails), CommandError> { let data_read = ctx.data.read().await; let loading_reaction = { let botinfo_lock = data_read.get::().unwrap(); @@ -73,7 +102,7 @@ pub async fn handle_request( // send out loading emote 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?\n{}", + "Unable to react to message, am I missing permissions to react or use external emoji?", )); } @@ -82,12 +111,12 @@ pub async fn handle_request( Ok(resp) => resp, Err(e) => { discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await?; - return Err(CommandError::from(format!("Azure request failed!\n\n{}", e))); + return Err(CommandError::from(format!("Godbolt request failed!\n\n{}", e))); } }; // remove our loading emote discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await?; - Ok(response.1) + Ok((response.1, response.0)) } diff --git a/src/commands/compile.rs b/src/commands/compile.rs index 8d639cc..a0a1635 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -1,6 +1,8 @@ +use std::fmt::Write as _; + use serenity::framework::standard::{macros::command, Args, CommandResult}; -use crate::cache::{MessageCache, MessageCacheEntry}; +use crate::cache::{LinkAPICache, MessageCache, MessageCacheEntry}; use crate::utls::constants::COLOR_OKAY; use crate::utls::discordhelpers::{embeds, is_success_embed}; use crate::utls::{discordhelpers, parser}; @@ -10,13 +12,12 @@ use tokio::sync::RwLockReadGuard; use serenity::builder::CreateEmbed; use serenity::client::Context; use serenity::framework::standard::CommandError; +use serenity::model::application::component::ButtonStyle; use serenity::model::channel::{Message, ReactionType}; use serenity::model::user::User; use crate::cache::{CompilerCache, ConfigCache, StatsManagerCache}; -use crate::managers::compilation::CompilationManager; - -use std::fmt::Write as _; +use crate::managers::compilation::{CompilationDetails, CompilationManager}; #[command] #[bucket = "nospam"] @@ -24,17 +25,42 @@ pub async fn compile(ctx: &Context, msg: &Message, _args: Args) -> CommandResult let data_read = ctx.data.read().await; // Handle wandbox request logic - let embed = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; + let (embed, compilation_details) = + handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; // Send our final embed - let compilation_embed = embeds::dispatch_embed(&ctx.http, msg.channel_id, embed).await?; + let mut new_msg = embeds::embed_message(embed); + let data = ctx.data.read().await; + if let Some(link_cache) = data.get::() { + if let Some(b64) = compilation_details.base64 { + let long_url = format!("https://godbolt.org/clientstate/{}", b64); + let link_cache_lock = link_cache.read().await; + if let Some(url) = link_cache_lock.get_link(long_url).await { + new_msg.components(|cmp| { + cmp.create_action_row(|row| { + row.create_button(|btn| { + btn.style(ButtonStyle::Link).url(url).label("View on godbolt.org") + }) + }) + }); + } + } + } + + let sent = msg + .channel_id + .send_message(&ctx.http, |e| { + *e = new_msg.clone(); + e + }) + .await?; // Success/fail react - let compilation_successful = compilation_embed.embeds[0].colour.unwrap().0 == COLOR_OKAY; - discordhelpers::send_completion_react(ctx, &compilation_embed, compilation_successful).await?; + let compilation_successful = sent.embeds[0].colour.unwrap().0 == COLOR_OKAY; + discordhelpers::send_completion_react(ctx, &sent, 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(sent, msg.clone())); debug!("Command executed"); Ok(()) } @@ -44,7 +70,7 @@ pub async fn handle_request( mut content: String, author: User, msg: &Message, -) -> Result { +) -> Result<(CreateEmbed, CompilationDetails), CommandError> { let data_read = ctx.data.read().await; let loading_reaction = { let botinfo_lock = data_read.get::().unwrap(); @@ -61,8 +87,7 @@ pub async fn handle_request( // Try to load in an attachment let (code, ext) = parser::get_message_attachment(&msg.attachments).await?; if !code.is_empty() { - // content.push_str(&format!("\n```{}\n{}\n```\n", ext, code)); - writeln!(content, "\n```{}\n{}\n```", ext, code).unwrap(); + writeln!(&mut content, "\n```{}\n{}\n```\n", ext, code).unwrap(); } // parse user input @@ -74,15 +99,15 @@ pub async fn handle_request( // send out loading emote 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?\n{}", + "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 result = match awd { + let compilation_result = compilation_manager_lock.compile(&parse_result, &author).await; + let result = match compilation_result { Ok(r) => r, Err(e) => { // we failed, lets remove the loading react so it doesn't seem like we're still processing @@ -124,5 +149,5 @@ pub async fn handle_request( } } - Ok(result.1) + Ok((result.1, result.0)) } diff --git a/src/commands/cpp.rs b/src/commands/cpp.rs index 6004389..3b5e720 100644 --- a/src/commands/cpp.rs +++ b/src/commands/cpp.rs @@ -5,6 +5,7 @@ use serenity::{ prelude::*, }; +use crate::managers::compilation::CompilationDetails; use crate::utls::discordhelpers::embeds::EmbedOptions; use crate::{ cache::{CompilerCache, ConfigCache, MessageCache, MessageCacheEntry}, @@ -19,7 +20,7 @@ use crate::{ #[aliases("c++")] #[bucket = "nospam"] pub async fn cpp(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { - let emb = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; + let (emb, _) = handle_request(ctx.clone(), msg.content.clone(), msg.author.clone(), msg).await?; // Dispatch our request let compilation_embed = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; @@ -38,7 +39,7 @@ pub async fn handle_request( content: String, author: User, msg: &Message, -) -> std::result::Result { +) -> std::result::Result<(CreateEmbed, CompilationDetails), CommandError> { let loading_reaction = { let data_read = ctx.data.read().await; let botinfo_lock = data_read.get::().unwrap(); @@ -63,7 +64,7 @@ pub async fn handle_request( // send out loading emote 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?\n{}", + "Unable to react to message, am I missing permissions to react or use external emoji?", )); } @@ -90,7 +91,6 @@ pub async fn handle_request( // remove our loading emote discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await?; - let options = EmbedOptions::new(false, fake_parse.target.clone(), String::default()); - - Ok(result.1.to_embed(&author, &options)) + let options = EmbedOptions::new(false, result.0.clone()); + Ok((result.1.to_embed(&author, &options), result.0)) } diff --git a/src/commands/format.rs b/src/commands/format.rs index df4fbc4..994255f 100644 --- a/src/commands/format.rs +++ b/src/commands/format.rs @@ -121,15 +121,14 @@ pub async fn format(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu msg .channel_id - .send_message(&ctx.http, |msg| msg.add_file(path.as_str()).content("Powered by MS Azure")) + .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 MS Azure*", lang_code, answer)) + .reply(&ctx.http, format!("\n```{}\n{}```\n*Powered by godbolt.org*", lang_code, answer)) .await?; } - debug!("Command executed"); Ok(()) } diff --git a/src/commands/formats.rs b/src/commands/formats.rs index d7d3f51..a7fb837 100644 --- a/src/commands/formats.rs +++ b/src/commands/formats.rs @@ -1,3 +1,5 @@ +use std::fmt::Write as _; + use crate::cache::{CompilerCache, ConfigCache}; use serenity::builder::CreateEmbed; use serenity::framework::standard::{macros::command, Args, CommandError, CommandResult}; @@ -7,8 +9,6 @@ use serenity::prelude::*; use crate::utls::constants::{COLOR_OKAY, ICON_HELP}; use crate::utls::discordhelpers::embeds; -use std::fmt::Write as _; - #[command] pub async fn formats(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let data = ctx.data.read().await; @@ -34,8 +34,7 @@ pub async fn formats(ctx: &Context, msg: &Message, _args: Args) -> CommandResult let mut output = String::new(); output.push_str("Styles:\n"); if format.styles.is_empty() { - // output.push_str(" *(None)*\n"); - writeln!(output, " *(None)*").unwrap(); + output.push_str(" *(None)*\n"); } for style in &format.styles { // output.push_str(&format!(" *- {}*\n", style)); @@ -45,7 +44,6 @@ pub async fn formats(ctx: &Context, msg: &Message, _args: Args) -> CommandResult } embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; - debug!("Command executed"); return Ok(()); } diff --git a/src/commands/invite.rs b/src/commands/invite.rs index 1fc001a..9374832 100644 --- a/src/commands/invite.rs +++ b/src/commands/invite.rs @@ -12,7 +12,6 @@ pub async fn invite(ctx: &Context, msg: &Message, _: Args) -> CommandResult { let emb = embeds::build_invite_embed(&invite); embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; - debug!("Command executed"); Ok(()) } diff --git a/src/cppeval/eval.rs b/src/cppeval/eval.rs index 20d2a99..49665d8 100644 --- a/src/cppeval/eval.rs +++ b/src/cppeval/eval.rs @@ -63,9 +63,8 @@ impl CppEval { let rest = self.input.replacen(&main, "", 1).trim().to_string(); // self.output.push_str(&format!("{}\n", rest)); - writeln!(self.output, "{}", rest).unwrap(); // self.output.push_str(&format!("{}\n", main)); - writeln!(self.output, "{}", main).unwrap(); + writeln!(self.output, "{}\n{}", rest, main).unwrap(); } else { return Err(EvalError::new("No main() specified. Invalid request")); } @@ -158,6 +157,7 @@ impl CppEval { } else { self.input.clone() }; + self.build_main(&format!("cout {};", input)); } diff --git a/src/events.rs b/src/events.rs index fc238ca..503ff19 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,5 +1,3 @@ -use std::env; - use serenity::{ async_trait, collector::CollectReaction, @@ -11,10 +9,12 @@ use serenity::{ }, prelude::*, }; +use std::env; use tokio::sync::MutexGuard; use chrono::{DateTime, Utc}; +use serenity::model::application::component::ButtonStyle; use crate::{ cache::*, @@ -188,8 +188,8 @@ impl EventHandler for Handler { .await; let _ = new_message.delete_reactions(&ctx.http).await; if collector.is_some() { - let prefix = env::var("BOT_PREFIX").expect("Expected bot prefix in .env file"); - let emb = match handle_request( + let prefix = env::var("BOT_PREFIX").expect("Bot prefix is not set!"); + let (emb, details) = match handle_request( ctx.clone(), format!("{}compile\n```{}\n{}\n```", prefix, language, code), new_message.author.clone(), @@ -197,9 +197,10 @@ impl EventHandler for Handler { ) .await { - Ok(emb) => emb, + Ok((emb, details)) => (emb, details), Err(e) => { let emb = embeds::build_fail_embed(&new_message.author, &format!("{}", e)); + let sent_fail = embeds::dispatch_embed(&ctx.http, new_message.channel_id, emb).await; if let Ok(sent) = sent_fail { @@ -209,7 +210,33 @@ impl EventHandler for Handler { return; } }; - let _ = embeds::dispatch_embed(&ctx.http, new_message.channel_id, emb).await; + + // Send our final embed + let mut new_msg = embeds::embed_message(emb); + let data = ctx.data.read().await; + if let Some(link_cache) = data.get::() { + if let Some(b64) = details.base64 { + let long_url = format!("https://godbolt.org/clientstate/{}", b64); + let link_cache_lock = link_cache.read().await; + if let Some(url) = link_cache_lock.get_link(long_url).await { + new_msg.components(|cmp| { + cmp.create_action_row(|row| { + row.create_button(|btn| { + btn.style(ButtonStyle::Link).url(url).label("View on godbolt.org") + }) + }) + }); + } + } + } + + let _ = new_message + .channel_id + .send_message(&ctx.http, |e| { + *e = new_msg.clone(); + e + }) + .await; } } } diff --git a/src/main.rs b/src/main.rs index 53e041c..5c7626c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,6 @@ use crate::commands::{ asm::*, block::*, botinfo::*, compile::*, compilers::*, cpp::*, format::*, formats::*, help::*, invite::*, languages::*, ping::*, unblock::*, }; - use crate::utls::discordhelpers::embeds::panic_embed; use crate::utls::discordhelpers::manual_dispatch; @@ -108,7 +107,6 @@ async fn main() -> Result<(), Box> { .await?; cache::fill(client.data.clone(), &prefix, bot_id.0, client.shard_manager.clone()).await?; - if let Ok(plog) = env::var("PANIC_LOG") { let default_panic = std::panic::take_hook(); let http = client.cache_and_http.http.clone(); diff --git a/src/managers/compilation.rs b/src/managers/compilation.rs index de40abe..afebf3e 100644 --- a/src/managers/compilation.rs +++ b/src/managers/compilation.rs @@ -13,10 +13,11 @@ use crate::utls::discordhelpers::embeds::{EmbedOptions, ToEmbed}; use crate::utls::parser::ParserResult; // struct containing any information resolved during the compilation step -#[derive(Default)] +#[derive(Default, Clone)] pub struct CompilationDetails { pub language: String, pub compiler: String, + pub base64: Option, } //Traits for compiler lookup @@ -79,15 +80,13 @@ impl CompilationManager { RequestHandler::CompilerExplorer => { let result = self.compiler_explorer(parser_result).await?; - let options = - EmbedOptions::new(false, result.0.language.clone(), result.0.compiler.clone()); + let options = EmbedOptions::new(false, result.0.clone()); Ok((result.0, result.1.to_embed(author, &options))) } RequestHandler::WandBox => { let result = self.wandbox(parser_result).await?; - let options = - EmbedOptions::new(false, result.0.language.clone(), result.0.compiler.clone()); + let options = EmbedOptions::new(false, result.0.clone()); Ok((result.0, result.1.to_embed(author, &options))) } RequestHandler::None => { @@ -107,7 +106,7 @@ impl CompilationManager { &self, parse_result: &ParserResult, author: &User, - ) -> Result<(String, CreateEmbed), CommandError> { + ) -> Result<(CompilationDetails, CreateEmbed), CommandError> { let gbolt = match &self.gbolt { Some(gbolt) => gbolt, None => { @@ -138,14 +137,22 @@ impl CompilationManager { let resolution_result = gbolt.resolve(target); match resolution_result { None => Err(CommandError::from(format!( - "Target '{}' either does not produce assembly or is not currently supported on MS Azure", + "Target '{}' either does not produce assembly or is not currently supported on godbolt.org", target ))), Some(compiler) => { let response = - Godbolt::send_request(&compiler, &parse_result.code, options, USER_AGENT).await?; - let options = EmbedOptions::new(true, target.to_string(), compiler.name); - Ok((compiler.lang, response.to_embed(author, &options))) + Godbolt::send_request(&compiler, &parse_result.code, options.clone(), USER_AGENT).await?; + let base64 = Godbolt::get_base64(&compiler, &parse_result.code, options)?; + + let details = CompilationDetails { + language: target.to_string(), + compiler: compiler.name, + base64: Some(base64), + }; + + let options = EmbedOptions::new(true, details.clone()); + Ok((details, response.to_embed(author, &options))) } } } @@ -186,10 +193,6 @@ impl CompilationManager { let target = if parse_result.target == "haskell" { "ghc901" } else { &parse_result.target }; let compiler = gbolt.resolve(target).unwrap(); - // report discovered information - let details = - CompilationDetails { compiler: compiler.name.clone(), language: compiler.lang.clone() }; - // add boilerplate code if needed & fix common mistakes let mut code = parse_result.code.clone(); { @@ -200,7 +203,16 @@ impl CompilationManager { code = fix_common_problems(&compiler.lang, code); } + let base64 = Godbolt::get_base64(&compiler, &code, options.clone())?; let response = Godbolt::send_request(&compiler, &code, options, USER_AGENT).await?; + + // report discovered information + let details = CompilationDetails { + compiler: compiler.name.clone(), + language: compiler.lang.clone(), + base64: Some(base64), + }; + Ok((details, response)) } diff --git a/src/slashcmds/cpp.rs b/src/slashcmds/cpp.rs index e684bd9..cfd1452 100644 --- a/src/slashcmds/cpp.rs +++ b/src/slashcmds/cpp.rs @@ -61,7 +61,7 @@ pub async fn cpp(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandR let data_read = ctx.data.read().await; let compiler_lock = data_read.get::().unwrap().read().await; let result = compiler_lock.compiler_explorer(&fake_parse).await?; - let options = EmbedOptions::new(false, fake_parse.target.clone(), String::default()); + let options = EmbedOptions::new(false, result.0); msg .edit_original_interaction_response(&ctx.http, |resp| { @@ -69,7 +69,6 @@ pub async fn cpp(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandR }) .await?; } - debug!("Command executed"); Ok(()) } diff --git a/src/slashcmds/diff.rs b/src/slashcmds/diff.rs index eb1c95f..dfcb46e 100644 --- a/src/slashcmds/diff.rs +++ b/src/slashcmds/diff.rs @@ -1,3 +1,5 @@ +use std::fmt::Write as _; + use serenity::framework::standard::CommandError; use serenity::{ framework::standard::CommandResult, @@ -13,8 +15,6 @@ use crate::{ utls::parser::ParserResult, }; -use std::fmt::Write as _; - pub async fn diff(ctx: &Context, msg: &ApplicationCommandInteraction) -> CommandResult { let message1 = msg .data @@ -103,7 +103,6 @@ pub async fn diff(ctx: &Context, msg: &ApplicationCommandInteraction) -> Command }) }) .await?; - debug!("Command executed"); Ok(()) } diff --git a/src/slashcmds/diff_msg.rs b/src/slashcmds/diff_msg.rs index 2d9a828..44c26f5 100644 --- a/src/slashcmds/diff_msg.rs +++ b/src/slashcmds/diff_msg.rs @@ -102,7 +102,6 @@ pub async fn diff_msg(ctx: &Context, msg: &ApplicationCommandInteraction) -> Com }) .await?; } - debug!("Command executed"); Ok(()) } diff --git a/src/slashcmds/format.rs b/src/slashcmds/format.rs index aa9a92b..d52e730 100644 --- a/src/slashcmds/format.rs +++ b/src/slashcmds/format.rs @@ -39,7 +39,6 @@ pub async fn format(ctx: &Context, command: &ApplicationCommandInteraction) -> C create_formats_interaction(response, &comp_mgr.gbolt.as_ref().unwrap().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)); @@ -156,7 +155,6 @@ pub async fn format(ctx: &Context, command: &ApplicationCommandInteraction) -> C }) .await?; - debug!("Command executed"); Ok(()) } diff --git a/src/tests/cpp.rs b/src/tests/cpp.rs index 4dc4ea8..ac4291d 100644 --- a/src/tests/cpp.rs +++ b/src/tests/cpp.rs @@ -66,6 +66,6 @@ async fn eval_output_balance() { let mut eval = CppEval::new("{ /* {{{ */ }"); if let Err(e) = eval.evaluate() { println!("{}", e); - panic!("Parser failed") + panic!("Parser failed."); } } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 6d13966..fa9a3ea 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,4 +1,5 @@ pub mod boilerplate; pub mod cpp; #[cfg(test)] +#[cfg_attr(feature = "cargo-clippy", allow(clippy::all))] pub mod parser; diff --git a/src/tests/parser.rs b/src/tests/parser.rs index 3f6b312..b10565c 100644 --- a/src/tests/parser.rs +++ b/src/tests/parser.rs @@ -56,9 +56,9 @@ async fn standard_parse_args() { } #[tokio::test] -async fn standard_parse_url_args() { +async fn standard_parse_url() { 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"); let reply = None; let result = get_components(input, &dummy_user, None, &reply).await; @@ -67,9 +67,8 @@ async fn standard_parse_url_args() { } let parser_result = result.unwrap(); - println!("{:?}", &parser_result); assert_eq!(parser_result.target, "c++"); - assert_eq!(parser_result.args, ["test1", "test2"]); + assert_eq!(parser_result.args.len(), 0); assert_eq!(parser_result.options.len(), 0); assert_eq!(parser_result.stdin, ""); assert_eq!(parser_result.url, "https://pastebin.com/raw/ERqDRZva"); @@ -77,9 +76,9 @@ async fn standard_parse_url_args() { } #[tokio::test] -async fn standard_parse_url() { +async fn standard_parse_url_args() { 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\ntest1 test2"); let reply = None; let result = get_components(input, &dummy_user, None, &reply).await; @@ -88,8 +87,9 @@ async fn standard_parse_url() { } let parser_result = result.unwrap(); + println!("{:?}", &parser_result); assert_eq!(parser_result.target, "c++"); - assert_eq!(parser_result.args.len(), 0); + assert_eq!(parser_result.args, ["test1", "test2"]); assert_eq!(parser_result.options.len(), 0); assert_eq!(parser_result.stdin, ""); assert_eq!(parser_result.url, "https://pastebin.com/raw/ERqDRZva"); diff --git a/src/utls/constants.rs b/src/utls/constants.rs index dca2725..66e29ea 100644 --- a/src/utls/constants.rs +++ b/src/utls/constants.rs @@ -1,9 +1,7 @@ use lazy_static::lazy_static; use regex::Regex; -//pub const COLOR_OKAY : i32 = 0x046604; pub const COLOR_OKAY: u32 = 0x5dbcd2; -//pub const COLOR_FAIL : i32 = 0x660404; pub const COLOR_FAIL: u32 = 0xff7761; pub const COLOR_WARN: u32 = 0xad7805; @@ -11,15 +9,16 @@ pub const ICON_FAIL: &str = "https://i.imgur.com/LxxYrFj.png"; 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_EXPLORER_ICON: &str = "https://i.imgur.com/GIgATFr.png"; -pub const COMPILER_ICON: &str = "http://i.michaelwflaherty.com/u/XedLoQWCVc.png"; -pub const MAX_OUTPUT_LEN: usize = 1 << 8; -pub const MAX_ERROR_LEN: usize = 1 << 8; +pub const COMPILER_ICON: &str = + "https://raw.githubusercontent.com/ThomasByr/discord-compiler-bot/master/assets/code.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 MAX_OUTPUT_LEN: usize = 250; +pub const MAX_ERROR_LEN: usize = 997; + // Boilerplate Regexes lazy_static! { pub static ref JAVA_MAIN_REGEX: Regex = diff --git a/src/utls/discordhelpers/embeds.rs b/src/utls/discordhelpers/embeds.rs index 0683454..638f2ed 100644 --- a/src/utls/discordhelpers/embeds.rs +++ b/src/utls/discordhelpers/embeds.rs @@ -2,12 +2,15 @@ use std::fmt::Write as _; use std::{env, str}; use serenity::http::Http; +use serenity::model::application::component::ButtonStyle; use serenity::{ builder::{CreateEmbed, CreateMessage}, client::Context, model::prelude::*, }; +use crate::cache::LinkAPICache; +use crate::managers::compilation::CompilationDetails; use wandbox::*; use crate::utls::constants::*; @@ -16,12 +19,12 @@ use crate::utls::discordhelpers; #[derive(Default)] pub struct EmbedOptions { pub is_assembly: bool, - pub lang: String, - pub compiler: String, + pub compilation_info: CompilationDetails, } + impl EmbedOptions { - pub fn new(is_assembly: bool, lang: String, compiler: String) -> Self { - EmbedOptions { is_assembly, lang, compiler } + pub fn new(is_assembly: bool, compilation_info: CompilationDetails) -> Self { + EmbedOptions { is_assembly, compilation_info } } } @@ -63,14 +66,14 @@ impl ToEmbed for wandbox::CompilationResult { embed.footer(|f| { let mut text = author.tag(); - if !options.lang.is_empty() { - text = format!("{} | {}", text, options.lang); + if !options.compilation_info.language.is_empty() { + text = format!("{} | {}", text, options.compilation_info.language); } - if !options.compiler.is_empty() { - text = format!("{} | {}", text, options.compiler); + if !options.compilation_info.compiler.is_empty() { + text = format!("{} | {}", text, options.compilation_info.compiler); } - text = format!("{} | CV NSight", text); + text = format!("{} | wandbox.org", text); f.text(text) }); embed @@ -120,7 +123,7 @@ impl ToEmbed for godbolt::GodboltResponse { let mut i = 1; for str in pieces { let title = format!("Assembly Output Pt. {}", i); - // let piece = discordhelpers::conform_external_str(&str, MAX_OUTPUT_LEN); + let piece = str.replace('`', "\u{200B}`"); embed.field(&title, format!("```x86asm\n{}\n```", &piece), false); output = true; @@ -133,9 +136,8 @@ impl ToEmbed for godbolt::GodboltResponse { String::from("Assembly Output") }; - // let piece = discordhelpers::conform_external_str(&append, MAX_OUTPUT_LEN); - let piece = append.replace('`', "\u{200B}`"); - embed.field(&title, format!("```x86asm\n{}\n```", &piece), false); + let str = append.replace('`', "\u{200B}`"); + embed.field(&title, format!("```x86asm\n{}\n```", &str), false); output = true; } @@ -146,7 +148,6 @@ impl ToEmbed for godbolt::GodboltResponse { } else { let mut output = String::default(); for line in &self.stdout { - // output.push_str(&format!("{}\n", line.text)); writeln!(output, "{}", line.text).unwrap(); } @@ -154,13 +155,12 @@ impl ToEmbed for godbolt::GodboltResponse { if let Some(build_result) = self.build_result { if let Some(errors) = build_result.stderr { for line in errors { - // errs.push_str(&format!("{}\n", line.text)); writeln!(errs, "{}", line.text).unwrap(); } } } + for line in &self.stderr { - // errs.push_str(&format!("{}\n", line.text)); writeln!(errs, "{}", line.text).unwrap(); } @@ -192,21 +192,47 @@ impl ToEmbed for godbolt::GodboltResponse { if let Some(time) = self.execution_time { appendstr = format!("{} | {}ms", appendstr, time); } - if !options.lang.is_empty() { - appendstr = format!("{} | {}", appendstr, options.lang); + if !options.compilation_info.language.is_empty() { + appendstr = format!("{} | {}", appendstr, options.compilation_info.language); } - if !options.compiler.is_empty() { - appendstr = format!("{} | {}", appendstr, options.compiler); + if !options.compilation_info.compiler.is_empty() { + appendstr = format!("{} | {}", appendstr, options.compilation_info.compiler); } - embed.footer(|f| f.text(format!("{} | MS Azure", appendstr))); + embed.footer(|f| f.text(format!("{} | godbolt.org", appendstr))); embed } } -pub async fn edit_message_embed(ctx: &Context, old: &mut Message, emb: CreateEmbed) { +pub async fn edit_message_embed( + ctx: &Context, + old: &mut Message, + emb: CreateEmbed, + compilation_details: Option, +) { + let mut url = None; + if let Some(details) = compilation_details { + let data = ctx.data.read().await; + if let Some(link_cache) = data.get::() { + if let Some(b64) = details.base64 { + let long_url = format!("https://godbolt.org/clientstate/{}", b64); + let link_cache_lock = link_cache.read().await; + url = link_cache_lock.get_link(long_url).await + } + } + } + let _ = old .edit(ctx, |m| { + if let Some(shorturl) = url { + m.components(|cmp| { + cmp.create_action_row(|row| { + row.create_button(|btn| { + btn.style(ButtonStyle::Link).url(shorturl).label("View on godbolt.org") + }) + }) + }); + } m.embed(|e| { e.0 = emb.0; e @@ -233,7 +259,7 @@ pub fn build_small_compilation_embed(author: &User, res: &mut CompilationResult) let str = discordhelpers::conform_external_str(&res.program_all, MAX_OUTPUT_LEN); embed.description(format!("```\n{}\n```", str)); } - embed.footer(|f| f.text(format!("Requested by: {} | Powered by NVidia", author.tag()))); + embed.footer(|f| f.text(format!("Requested by: {} | Powered by wandbox.org", author.tag()))); embed } @@ -293,9 +319,11 @@ pub fn build_welcome_embed() -> CreateEmbed { format!("{}compile python\n```py\nprint('hello world')\n```", prefix), true, ); - embed.field("Learning Time!", "If you like reading the manuals of things, read our [getting started](https://github.com/ThomasByr/discord-compiler-bot/wiki) 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 [GitHub](https://github.com/ThomasByr/discord-compiler-bot) and we'll give you a hand.", true); - embed.footer(|f| f.text("powered by Azure & NVidia // created by ThomasByr")); + embed.field("Learning Time!", "If you like reading the manuals of things, read our [wiki](https://github.com/ThomasByr/discord-compiler-bot/wiki/) 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 [github](https://github.com/ThomasByr/discord-compiler-bot) and we'll give you a hand.", true); + embed.footer(|f| { + f.text("powered by godbolt.org & wandbox.org // created by Thomas Bouyer (BlckLight#0001)") + }); embed } diff --git a/src/utls/discordhelpers/interactions.rs b/src/utls/discordhelpers/interactions.rs index b0ca2d1..316c602 100644 --- a/src/utls/discordhelpers/interactions.rs +++ b/src/utls/discordhelpers/interactions.rs @@ -98,11 +98,8 @@ pub async fn create_more_options_panel( .await .unwrap(); - println!("awaiting response..."); let msg = interaction.get_interaction_response(&ctx.http).await?; - println!("response got..."); if let Some(resp) = msg.await_modal_interaction(&ctx.shard).await { - println!("response: {:?}", resp.kind); if let ActionRowComponent::InputText(input) = &resp.data.components[0].components[0] { parse_result.options = input.value.clone().split(' ').map(|p| p.to_owned()).collect(); } @@ -320,22 +317,6 @@ 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(MessageFlags::EPHEMERAL) - // }) - // }) - // .await?; - // return Err(CommandError::from("Unable to find a codeblock to compile!")); - // } - // msg = Some(value); - // break; - // } - if let Some((_, value)) = command.data.resolved.messages.iter().next() { if !parser::find_code_block(&mut parse_result, &value.content, &command.user).await? { command diff --git a/src/utls/discordhelpers/menu.rs b/src/utls/discordhelpers/menu.rs index cc56d59..1a22986 100644 --- a/src/utls/discordhelpers/menu.rs +++ b/src/utls/discordhelpers/menu.rs @@ -19,7 +19,7 @@ impl Menu { Menu { ctx: ctx.clone(), msg: msg.clone(), - pages: items.to_vec(), + pages: Vec::from(items), page: 0, components: Menu::build_components(), } diff --git a/src/utls/discordhelpers/mod.rs b/src/utls/discordhelpers/mod.rs index e58fe24..14d8a4c 100644 --- a/src/utls/discordhelpers/mod.rs +++ b/src/utls/discordhelpers/mod.rs @@ -15,6 +15,7 @@ use serenity::client::Context; use serenity::framework::standard::CommandResult; use tokio::sync::MutexGuard; +use crate::commands::compile; use std::fmt::Write as _; pub fn build_menu_items( @@ -63,9 +64,10 @@ pub fn build_menu_items( pages } +// Pandas#3**2 on serenity disc, tyty pub fn build_reaction(emoji_id: u64, emoji_name: &str) -> ReactionType { ReactionType::Custom { - animated: false, // todo: Make this configurable + animated: false, id: EmojiId::from(emoji_id), name: Some(String::from(emoji_name)), } @@ -85,7 +87,6 @@ pub async fn handle_edit( }; // try to clear reactions - // let _ = old.delete_reactions(&ctx).await; if let Ok(updated_message) = old.channel_id.message(&ctx.http, old.id.0).await { for reaction in &updated_message.reactions { if reaction.me { @@ -101,25 +102,25 @@ pub async fn handle_edit( 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, None).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, None).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, None).await; } } 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, None).await; } } @@ -130,13 +131,13 @@ pub async fn handle_edit_cpp( mut old: Message, original_msg: Message, ) -> CommandResult { - let embed = + let (embed, details) = 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, Some(details)).await; Ok(()) } @@ -147,13 +148,13 @@ pub async fn handle_edit_compile( mut old: Message, original_msg: Message, ) -> CommandResult { - let embed = - crate::commands::compile::handle_request(ctx.clone(), content, author, &original_msg).await?; + let (embed, compilation_details) = + 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, Some(compilation_details)).await; Ok(()) } @@ -164,11 +165,11 @@ pub async fn handle_edit_asm( mut old: Message, original_msg: Message, ) -> CommandResult { - let emb = + let (emb, details) = 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, Some(details)).await; send_completion_react(ctx, &old, success).await?; Ok(()) @@ -265,12 +266,11 @@ pub async fn send_global_presence(shard_manager: &MutexGuard<'_, ShardManager>, }; // update shard guild count & presence - let final_s = if sum > 1 { "s" } else { "" }; - let presence_str = format!("{} guild{} | ;help", server_count, final_s); + 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::watching(&presence_str)), OnlineStatus::Online); + v.runner_tx.set_presence(Some(Activity::playing(&presence_str)), OnlineStatus::Online); } } diff --git a/src/utls/parser.rs b/src/utls/parser.rs index b3a8101..7949128 100644 --- a/src/utls/parser.rs +++ b/src/utls/parser.rs @@ -59,12 +59,11 @@ pub async fn get_components( end_point = parse_stop; } if let Some(index) = input.find('`') { + // if the ` character is found before \n we should use the ` as our parse stop point if end_point == 0 || index < end_point { - // if the ` character is found before \n we should use the ` as our parse stop point end_point = index; } } - let mut args: Vec<&str> = input[..end_point].split_whitespace().collect(); // ditch command str (;compile, ;asm) args.remove(0); @@ -144,22 +143,25 @@ pub async fn get_components( result.code = code; } 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 quoting is the code. + // let's assume we found the stdin and what they're replying to is the code. // Anything else probably doesn't make sense. if let Some(replied_msg) = reply { - result.stdin = result.code; - result.code = String::default(); - let attachment = get_message_attachment(&replied_msg.attachments).await?; if !attachment.0.is_empty() { if !result.target.is_empty() { result.target = attachment.1; } + // shift previos code to stdin, we have found code + result.stdin = result.code; + result.code = String::default(); result.code = attachment.0; - } else if !find_code_block(&mut result, &replied_msg.content, author).await? { - return Err(CommandError::from( - "Cannot find code to compile assuming your code block is the program's stdin.", - )); + } 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 + result.stdin = result.code; + result.code = fake_result.code; + } } } } else { @@ -173,10 +175,10 @@ pub async fn get_components( } result.code = attachment.0; } - // no reply in the attachment, lets check for a code-block.. + // no attachment in the reply, lets check for a code-block.. 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 quote a message that has one.", + "You must attach a code-block containing code to your message or reply to a message that has one.", )); } } else { @@ -309,7 +311,7 @@ pub async fn get_message_attachment( Ok((str, extension)) } Err(e) => { - Err(CommandError::from(format!("UTF8 Error occurred while parsing file: {}", e))) + Err(CommandError::from(format!("UTF8 Error occured while parsing file: {}", e))) } } } From 6a85a2d40efed47ceae8f8a6c5d64422753adedc Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 6 Mar 2023 21:47:27 +0100 Subject: [PATCH 74/81] =?UTF-8?q?`.env`=20example=20=F0=9F=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.env.example b/.env.example index 9953a7e..64e3647 100644 --- a/.env.example +++ b/.env.example @@ -48,3 +48,7 @@ DBL_WEBHOOK_PASSWORD= ## Statistics tracking API STATS_API_LINK= STATS_API_KEY= + +## quick_link for godbolt urls +# QUICK_LINK_URL= +# QUICK_LINK_POST= From 86137691ae263e51c70b387e81ca523173d5dfb1 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 6 Mar 2023 21:47:37 +0100 Subject: [PATCH 75/81] the full history, or so was I told... --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index 403093b..884d8c7 100644 --- a/changelog.md +++ b/changelog.md @@ -81,3 +81,5 @@ - added `.rustfmt.toml` to enforce a consistent style - updated dependencies (notably serenity to `0.11.5`) - finally unified embed dispatching +- went back to dispatching embeds manually for `;asm` and `;compile` commands +- upgraded godbolt From 4bc5110d40d73da03a8dc254c5bd4652b2ed821f Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 6 Mar 2023 21:48:00 +0100 Subject: [PATCH 76/81] =?UTF-8?q?latest=20version=20of=20`README`=20file?= =?UTF-8?q?=20=F0=9F=91=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c3cef28..d16b025 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,8 @@ Please read the [changelog](changelog.md) file for the full history ! - added `.rustfmt.toml` to enforce a consistent style - updated dependencies (notably serenity to `0.11.5`) - finally unified embed dispatching +- went back to dispatching embeds manually for `;asm` and `;compile` commands +- upgraded godbolt From 045d6309a4989f5b314bbd7fa2eaa2fa2889b6a3 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Mon, 6 Mar 2023 21:57:52 +0100 Subject: [PATCH 77/81] patched activity status --- src/utls/discordhelpers/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utls/discordhelpers/mod.rs b/src/utls/discordhelpers/mod.rs index 14d8a4c..5646c99 100644 --- a/src/utls/discordhelpers/mod.rs +++ b/src/utls/discordhelpers/mod.rs @@ -266,11 +266,11 @@ pub async fn send_global_presence(shard_manager: &MutexGuard<'_, ShardManager>, }; // update shard guild count & presence - let presence_str = format!("in {} servers | ;invite", server_count); + let presence_str = format!("{} guilds | ;help ({})", server_count, env!("CARGO_PKG_VERSION")); 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::watching(&presence_str)), OnlineStatus::Online); } } From b3534ff02a5986489dd6cb722a954caa1c8d1782 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Tue, 7 Mar 2023 12:29:11 +0100 Subject: [PATCH 78/81] ensure locks are closed as soon as possible --- src/cache.rs | 2 + src/commands/compile.rs | 10 ++- src/events.rs | 173 ++++++++++++++++++++------------------ src/slashcmds/diff_msg.rs | 7 +- 4 files changed, 104 insertions(+), 88 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index a74ec77..b624c69 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -64,10 +64,12 @@ impl TypeMapKey for LinkAPICache { type Value = Arc>; } +#[derive(Clone)] pub struct MessageCacheEntry { pub our_msg: Message, pub original_msg: Message, } + impl MessageCacheEntry { pub fn new(our_msg: Message, original_msg: Message) -> Self { MessageCacheEntry { our_msg, original_msg } diff --git a/src/commands/compile.rs b/src/commands/compile.rs index a0a1635..b2c5ba2 100644 --- a/src/commands/compile.rs +++ b/src/commands/compile.rs @@ -121,13 +121,19 @@ pub async fn handle_request( let _ = discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await; let is_success = is_success_embed(&result.1); + { + // stats manager is used in events.rs, lets keep our locks very short + let stats = data_read.get::().unwrap().lock().await; + if stats.should_track() { + stats.compilation(&result.0.language, !is_success).await; + } + } let stats = data_read.get::().unwrap().lock().await; if stats.should_track() { stats.compilation(&result.0.language, !is_success).await; } - let data = ctx.data.read().await; - let config = data.get::().unwrap(); + let config = data_read.get::().unwrap(); let config_lock = config.read().await; if let Some(log) = config_lock.get("COMPILE_LOG") { diff --git a/src/events.rs b/src/events.rs index 503ff19..5b1a17f 100644 --- a/src/events.rs +++ b/src/events.rs @@ -11,8 +11,6 @@ use serenity::{ }; use std::env; -use tokio::sync::MutexGuard; - use chrono::{DateTime, Utc}; use serenity::model::application::component::ButtonStyle; @@ -20,7 +18,6 @@ use crate::{ cache::*, commands::compile::handle_request, managers::compilation::RequestHandler, - managers::stats::StatsManager, utls::{ discordhelpers, discordhelpers::embeds, @@ -33,22 +30,24 @@ pub struct Handler; // event handler for serenity #[async_trait] trait ShardsReadyHandler { - async fn all_shards_ready(&self, ctx: &Context, stats: &mut MutexGuard<'_, StatsManager>); + async fn all_shards_ready(&self, ctx: &Context); } #[async_trait] impl ShardsReadyHandler for Handler { - async fn all_shards_ready(&self, ctx: &Context, stats: &mut MutexGuard<'_, StatsManager>) { + async fn all_shards_ready(&self, ctx: &Context) { let data = ctx.data.read().await; + let server_count = { + let mut stats = data.get::().unwrap().lock().await; + let guild_count = stats.get_boot_vec_sum(); + stats.post_servers(guild_count).await; + stats.server_count() + }; + // lock the shard manager to update our presences let shard_manager = data.get::().unwrap().lock().await; - let guild_count = stats.get_boot_vec_sum(); - - stats.post_servers(guild_count).await; - - discordhelpers::send_global_presence(&shard_manager, stats.server_count()).await; - - info!("Ready in {} guilds", stats.server_count()); + discordhelpers::send_global_presence(&shard_manager, server_count).await; + info!("Ready in {} guilds", server_count); // register commands globally in release if !cfg!(debug_assertions) { @@ -86,15 +85,16 @@ impl EventHandler for Handler { } // publish/queue new server to stats - let mut stats = data.get::().unwrap().lock().await; - stats.new_server().await; + let (server_count, shard_count) = { + let mut stats = data.get::().unwrap().lock().await; + stats.new_server().await; + (stats.server_count(), stats.shard_count()) + }; // ensure we're actually loaded in before we start posting our server counts - if stats.server_count() > 0 { - let new_stats = dbl::types::ShardStats::Cumulative { - server_count: stats.server_count(), - shard_count: Some(stats.shard_count()), - }; + if server_count > 0 { + let new_stats = + dbl::types::ShardStats::Cumulative { server_count, shard_count: Some(shard_count) }; if let Some(dbl_cache) = data.get::() { let dbl = dbl_cache.read().await; @@ -105,7 +105,7 @@ impl EventHandler for Handler { // update guild count in presence let shard_manager = data.get::().unwrap().lock().await; - discordhelpers::send_global_presence(&shard_manager, stats.server_count()).await; + discordhelpers::send_global_presence(&shard_manager, server_count).await; } info!("Joining {}", guild.name); @@ -131,15 +131,16 @@ impl EventHandler for Handler { } // publish/queue new server to stats - let mut stats = data.get::().unwrap().lock().await; - stats.leave_server().await; + let (server_count, shard_count) = { + let mut stats = data.get::().unwrap().lock().await; + stats.leave_server().await; + (stats.server_count(), stats.shard_count()) + }; // ensure we're actually loaded in before we start posting our server counts - if stats.server_count() > 0 { - let new_stats = dbl::types::ShardStats::Cumulative { - server_count: stats.server_count(), - shard_count: Some(stats.shard_count()), - }; + if server_count > 0 { + let new_stats = + dbl::types::ShardStats::Cumulative { server_count, shard_count: Some(shard_count) }; if let Some(dbl_cache) = data.get::() { let dbl = dbl_cache.read().await; @@ -150,7 +151,7 @@ impl EventHandler for Handler { // update guild count in presence let shard_manager = data.get::().unwrap().lock().await; - discordhelpers::send_global_presence(&shard_manager, stats.server_count()).await; + discordhelpers::send_global_presence(&shard_manager, server_count).await; } info!("Leaving {}", &incomplete.id); @@ -250,20 +251,25 @@ impl EventHandler for Handler { 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()) { - if msg.our_msg.delete(ctx.http).await.is_err() { - // ignore for now - } - message_cache.remove(id.as_u64()); + let maybe_message = { + let data = ctx.data.read().await; + let mut message_cache = data.get::().unwrap().lock().await; + message_cache.remove(id.as_u64()) + }; + + if let Some(msg) = maybe_message { + let _ = msg.our_msg.delete(ctx.http).await; } } async fn message_update(&self, ctx: Context, new_data: MessageUpdateEvent) { - let data = ctx.data.read().await; - let mut message_cache = data.get::().unwrap().lock().await; - if let Some(msg) = message_cache.get_mut(&new_data.id.0) { + let maybe_message = { + let data = ctx.data.read().await; + let mut message_cache = data.get::().unwrap().lock().await; + message_cache.get_mut(&new_data.id.0).map(|msg| msg.clone()) + }; + + if let Some(msg) = maybe_message { if let Some(new_msg) = new_data.content { if let Some(author) = new_data.author { discordhelpers::handle_edit( @@ -281,18 +287,9 @@ impl EventHandler for Handler { async fn ready(&self, ctx: Context, ready: Ready) { info!("[Shard {}] Ready", ctx.shard_id); - let data = ctx.data.read().await; - - { + let shard_count = { + let data = ctx.data.read().await; let mut stats = data.get::().unwrap().lock().await; - // occasionally we can have a ready event fire well after execution - // this check prevents us from double calling all_shards_ready - let total_shards_to_spawn = ready.shard.unwrap()[1]; - if stats.shard_count() + 1 > total_shards_to_spawn { - info!("Skipping duplicate ready event..."); - return; - } - let guild_count = ready.guilds.len() as u64; stats.add_shard(guild_count); @@ -301,18 +298,31 @@ impl EventHandler for Handler { let mut info = data.get::().unwrap().write().await; info.insert("BOT_AVATAR", ready.user.avatar_url().unwrap()); } + stats.shard_count() + }; + + // occasionally we can have a ready event fire well after execution + // this check prevents us from double calling all_shards_ready + let total_shards_to_spawn = ready.shard.unwrap()[1]; + if shard_count + 1 > total_shards_to_spawn { + info!("Skipping duplicate ready event..."); + return; + } - if stats.shard_count() == total_shards_to_spawn { - self.all_shards_ready(&ctx, &mut stats).await; - } + if shard_count == total_shards_to_spawn { + self.all_shards_ready(&ctx).await; } } async fn interaction_create(&self, ctx: Context, interaction: Interaction) { if let Interaction::ApplicationCommand(command) = interaction { - let data_read = ctx.data.read().await; - let commands = data_read.get::().unwrap().read().await; - match commands.on_command(&ctx, &command).await { + let cmd_result = { + let data_read = ctx.data.read().await; + let commands = data_read.get::().unwrap().read().await; + commands.on_command(&ctx, &command).await + }; + + match cmd_result { Ok(_) => {} Err(e) => { // in order to respond to messages with errors, we'll first try to @@ -331,42 +341,39 @@ impl EventHandler for Handler { #[hook] pub async fn before(ctx: &Context, msg: &Message, _: &str) -> bool { - let data = ctx.data.read().await; - { - let stats = data.get::().unwrap().lock().await; - if stats.should_track() { - stats.post_request().await; - } - } - // we'll go with 0 if we couldn't grab guild id let mut guild_id = 0; if let Some(id) = msg.guild_id { guild_id = id.0; } - // check user against our blocklist - { + let (author_blocked, guild_blocked) = { + let data = ctx.data.read().await; + let stats = data.get::().unwrap().lock().await; + if stats.should_track() { + stats.post_request().await; + } let blocklist = data.get::().unwrap().read().await; - let author_blocklisted = blocklist.contains(msg.author.id.0); - 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. - 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.", - ); - - let _ = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await; - if author_blocklisted { - warn!("Blocked user {} [{}]", msg.author.tag(), msg.author.id.0); - } else { - warn!("Blocked guild {}", guild_id); - } - return false; + (blocklist.contains(msg.author.id.0), blocklist.contains(guild_id)) + }; + + // check user against our blocklist + + if author_blocked || guild_blocked { + 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.", + ); + + let _ = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await; + if author_blocked { + warn!("Blocked user {} [{}]", msg.author.tag(), msg.author.id.0); + } else { + warn!("Blocked guild {}", guild_id); } + return false; } true diff --git a/src/slashcmds/diff_msg.rs b/src/slashcmds/diff_msg.rs index 44c26f5..3d49110 100644 --- a/src/slashcmds/diff_msg.rs +++ b/src/slashcmds/diff_msg.rs @@ -36,8 +36,8 @@ pub async fn diff_msg(ctx: &Context, msg: &ApplicationCommandInteraction) -> Com .await .unwrap(); { - let mut diff_cache = diff_cache_lock.lock().await; let content = get_code_block_or_content(&new_msg.content, &new_msg.author).await?; + let mut diff_cache = diff_cache_lock.lock().await; diff_cache.insert(msg.user.id.0, DiffCommandEntry::new(&content, msg)); } let resp = msg.get_interaction_response(&ctx.http).await?; @@ -48,8 +48,6 @@ pub async fn diff_msg(ctx: &Context, msg: &ApplicationCommandInteraction) -> Com .await; if let Some(interaction) = button_resp { interaction.defer(&ctx.http).await?; - let mut diff_cache = diff_cache_lock.lock().await; - diff_cache.remove(interaction.user.id.as_u64()); msg .edit_original_interaction_response(&ctx.http, |edit| { edit @@ -62,6 +60,9 @@ pub async fn diff_msg(ctx: &Context, msg: &ApplicationCommandInteraction) -> Com .components(|cmps| cmps.set_action_rows(Vec::new())) }) .await?; + + let mut diff_cache = diff_cache_lock.lock().await; + diff_cache.remove(interaction.user.id.as_u64()); } else { // Button expired msg From 40b13fe792da23989ce85ff6a79003257b5edd6f Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Wed, 8 Mar 2023 12:28:55 +0100 Subject: [PATCH 79/81] `shards` loading --- src/events.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/events.rs b/src/events.rs index 5b1a17f..33f7011 100644 --- a/src/events.rs +++ b/src/events.rs @@ -287,9 +287,17 @@ impl EventHandler for Handler { async fn ready(&self, ctx: Context, ready: Ready) { info!("[Shard {}] Ready", ctx.shard_id); + let total_shards_to_spawn = ready.shard.unwrap()[1]; let shard_count = { let data = ctx.data.read().await; let mut stats = data.get::().unwrap().lock().await; + + // occasionally we can have a ready event fire well after execution + // this check prevents us from double calling all_shards_ready + if stats.shard_count() + 1 > total_shards_to_spawn { + info!("Skipping duplicate ready event..."); + return; + } let guild_count = ready.guilds.len() as u64; stats.add_shard(guild_count); @@ -301,14 +309,6 @@ impl EventHandler for Handler { stats.shard_count() }; - // occasionally we can have a ready event fire well after execution - // this check prevents us from double calling all_shards_ready - let total_shards_to_spawn = ready.shard.unwrap()[1]; - if shard_count + 1 > total_shards_to_spawn { - info!("Skipping duplicate ready event..."); - return; - } - if shard_count == total_shards_to_spawn { self.all_shards_ready(&ctx).await; } @@ -363,8 +363,8 @@ pub async fn before(ctx: &Context, msg: &Message, _: &str) -> bool { 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.", + 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.", ); let _ = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await; From 280ae9dc8f38434317419c91d41fddbe523a7a94 Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Wed, 8 Mar 2023 12:29:16 +0100 Subject: [PATCH 80/81] the full history, or so was I told... --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 884d8c7..d20c36b 100644 --- a/changelog.md +++ b/changelog.md @@ -83,3 +83,4 @@ - finally unified embed dispatching - went back to dispatching embeds manually for `;asm` and `;compile` commands - upgraded godbolt +- restored proper order of operation when loading shards From eb95b88b187b7875a2d532df3466b2078b376f0b Mon Sep 17 00:00:00 2001 From: Thomas Byr Date: Wed, 8 Mar 2023 12:29:31 +0100 Subject: [PATCH 81/81] =?UTF-8?q?latest=20version=20of=20`README`=20file?= =?UTF-8?q?=20=F0=9F=91=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d16b025..2d9e116 100644 --- a/README.md +++ b/README.md @@ -48,8 +48,9 @@ fn main() -> Result<(), Box> { 5. [๐Ÿงช Testing](#-testing) 6. [๐Ÿง‘โ€๐Ÿซ Contributing](#-contributing) 7. [โš–๏ธ License](#๏ธ-license) -8. [๐Ÿ”„ Changelog](#-changelog) -9. [๐Ÿ› Bugs and TODO](#-bugs-and-todo) +8. [๐Ÿ–ผ๏ธ Icons](#๏ธ-icons) +9. [๐Ÿ”„ Changelog](#-changelog) +10. [๐Ÿ› Bugs and TODO](#-bugs-and-todo) ## โœ๏ธ In short @@ -132,6 +133,10 @@ This project is licensed under the AGPL-3.0 new or revised license. Please read THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +## ๐Ÿ–ผ๏ธ Icons + +Icons (except [the logo](assets/code.png)) are made by [Freepik](https://www.flaticon.com/authors/freepik) and [pixelmeetup](https://www.flaticon.com/authors/pixelmeetup) from [www.flaticon.com](https://www.flaticon.com/). + ## ๐Ÿ”„ Changelog Please read the [changelog](changelog.md) file for the full history ! @@ -172,6 +177,7 @@ Please read the [changelog](changelog.md) file for the full history ! - finally unified embed dispatching - went back to dispatching embeds manually for `;asm` and `;compile` commands - upgraded godbolt +- restored proper order of operation when loading shards @@ -189,4 +195,4 @@ Please read the [changelog](changelog.md) file for the full history ! - ~~`;botinfo` command not working~~ (v0.1.0) - ~~total number of servers joined always showing 0~~ (v0.1.1) - ~~debug commands not showing up in console (might be linked to previous bug)~~ (v0.1.1) -- some shards are randomly disconnecting +- ~~some shards are randomly disconnecting~~ (v1.4.2)