diff --git a/.env.example b/.env.example index 10e931f..64e3647 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 @@ -13,29 +13,31 @@ 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 ## If the bot is unable to fetch it's own application info, having this filled in will allow ## the bot to continue -BOT_ID= +BOT_ID=887037513682812960 ## Emojis -SUCCESS_EMOJI_NAME= -SUCCESS_EMOJI_ID= -LOADING_EMOJI_NAME= -LOADING_EMOJI_ID= -LOGO_EMOJI_NAME= -LOGO_EMOJI_ID= +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= -JOIN_LOG= -PANIC_LOG= +COMPILE_LOG=1014242294918021200 +JOIN_LOG=1015605164033445899 +PANIC_LOG=1014242354934329474 ## Top.gg bot voting announcement VOTE_CHANNEL= @@ -46,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= 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 ``` 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 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 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 diff --git a/Cargo.toml b/Cargo.toml index 4ce321a..6c7ae9c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,45 +1,45 @@ [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.4.2" +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.4" 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 = "2.0" 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"] } [dependencies.serenity] -version = "=0.11.1" +version = "=0.11.5" default-features = false features = [ "collector", diff --git a/LICENSE b/LICENSE index 810fce6..0ad25db 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 +. diff --git a/README.md b/README.md index 06183f8..2d9e116 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(()) } ``` ```` @@ -39,14 +45,17 @@ int main(int argc, char** argv) { 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. [🖼️ Icons](#️-icons) +9. [🔄 Changelog](#-changelog) +10. [🐛 Bugs and TODO](#-bugs-and-todo) ## ✏️ 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. @@ -83,9 +92,38 @@ 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 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. @@ -95,58 +133,51 @@ This project is licensed under the GPL-3.0 new or revised license. Please read t 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 +## 🖼️ Icons -Please read the [changelog](changelog.md) file for the full history ! +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/). -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. +## 🔄 Changelog -
- Beta first minor release (click here to expand) - -**v0.1.0** first release +Please read the [changelog](changelog.md) file for the full history ! -- support for 30 more languages -- asm output -- embedded messages for discord -- `;invite`, `;botinfo` commands -- icons are now found online +
+ First stable release version (click here to expand) -**v0.1.1** first patch +**v1.0** unwrapping -- locked serenity dependency version to v0.11 -- client now has privileged access to context -- fix argument endpoint detection on replies +- strongest cargo clippy analysis +- fixed some `panic!` on `.unwrap()` +- support for custom fail emoji +- the bot now uses 75B less ram on average, yay -**v0.1.2** code cleaning and slash commands +**v1.1** swaps -- 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 +- 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 -**v0.1.3** local rights and corrections +**v1.2** what is more useful when is broken ? -- 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 +- reworked C and Java boilerplates +- check for existence of builds +- added `bash` and `sh` aliases for `bash script` -**v0.1.4** hotfix +**v1.3** more languages -- compilation service unavailable +- added `c#` and `cs` aliases for `csharp` +- ... and many more -**v0.1.5** online services +**v1.4** dependencies -- 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 +- 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 +- restored proper order of operation when loading shards
@@ -164,3 +195,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~~ (v1.4.2) diff --git a/changelog.md b/changelog.md index 01fe5c6..d20c36b 100644 --- a/changelog.md +++ b/changelog.md @@ -47,3 +47,40 @@ - 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()` +- support for custom fail emoji +- 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 +- 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 + +**v1.4** dependencies + +- 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 +- restored proper order of operation when loading shards diff --git a/godbolt/Cargo.toml b/godbolt/Cargo.toml index a6176de..16070f9 100644 --- a/godbolt/Cargo.toml +++ b/godbolt/Cargo.toml @@ -1,15 +1,17 @@ [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 = "AGPL-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"] } +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 8850ba0..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; @@ -7,457 +8,516 @@ 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, +} + +#[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(), - } - } + 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); - } - - Ok(instance) - } + pub async fn new() -> Result> { + let formats = Godbolt::get_formats().await?; - /// 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) + 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); + } + } } - - /// 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) + 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 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 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); + + 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))), + }; + + let res = match serde_json::from_str::(&text) { + Ok(res) => res, + Err(e) => return Err(GodboltError::new(&format!("{}", e))), + }; + + 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())); + } } - - 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) + } + /// 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..75d6850 100644 --- a/godbolt/src/tests/tests.rs +++ b/godbolt/src/tests/tests.rs @@ -1,153 +1,77 @@ -use crate::{CompilationFilters, Godbolt}; +use crate::{CompilationFilters, CompilerOptions, ExecuteParameters, Godbolt, RequestOptions}; 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, - ) - .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 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(()) -} - -#[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(()) +async fn base64() -> Result<(), Box> { + let gbolt = Godbolt::new().await?; + let c = gbolt.resolve("clang1000").unwrap(); + + 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, + }; + let str = + Godbolt::get_base64(&c, "#include \nint main() {\nstd::cout << \"😂\";\n}", opts)?; + assert!(str.len() > 0); + 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(()) } diff --git a/run.bash b/run.bash new file mode 100755 index 0000000..6843b75 --- /dev/null +++ b/run.bash @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +# -*- coding: utf-8 -*- + +( (./target/release/discord-compiler-bot) 2>&1 | tee bot.log )& 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') 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 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/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 7943973..42a76d0 100644 --- a/src/boilerplate/c.rs +++ b/src/boilerplate/c.rs @@ -1,48 +1,48 @@ -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 { - 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 ; - - 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("#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(); - - 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(); - } - } - - if main_body.contains("printf") && !header.contains("stdio.h") { - header.push_str("#include ") - } - format!("{}\nint main(void) {{\n{}return 0;\n}}", header, main_body) + if main_body.contains("printf") && !header.contains("stdio.h") { + 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/cpp.rs b/src/boilerplate/cpp.rs index 022ca66..dfa226a 100644 --- a/src/boilerplate/cpp.rs +++ b/src/boilerplate/cpp.rs @@ -1,49 +1,49 @@ -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 { - 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 "); - } - 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 1b9a6df..43437d1 100644 --- a/src/boilerplate/csharp.rs +++ b/src/boilerplate/csharp.rs @@ -1,52 +1,52 @@ -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 { - 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;"); - } - 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 d4d432c..cbc8d68 100644 --- a/src/boilerplate/java.rs +++ b/src/boilerplate/java.rs @@ -1,48 +1,48 @@ -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 { - 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 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(); - } - } - - format!( - "{}\nclass Main{{\npublic static void main(String[] args) {{\n{}}}}}", - header, main_body - ) + 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 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 b2d4bef..b624c69 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -13,179 +13,196 @@ 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; +use serenity::model::application::interaction::application_command::ApplicationCommandInteraction; use serenity::model::channel::Message; -use serenity::model::interactions::application_command::ApplicationCommandInteraction; /** Caching **/ /// 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>; } +/// Contains the quick link api - used for godbolt button +pub struct LinkAPICache; +impl TypeMapKey for LinkAPICache { + type Value = Arc>; +} + +#[derive(Clone)] 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", - "LOADING_EMOJI_ID", - "LOADING_EMOJI_NAME", - "LOGO_EMOJI_NAME", - "LOGO_EMOJI_ID", - ]; - for id in &emoji_identifiers { - if let Ok(envvar) = env::var(id) { - if !envvar.is_empty() { - map.insert(id, envvar); - } - } - } - - 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); + 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_NAME", + "FAIL_EMOJI_ID", + "LOADING_EMOJI_ID", + "LOADING_EMOJI_NAME", + "LOGO_EMOJI_NAME", + "LOGO_EMOJI_ID", + ]; + for id in &emoji_identifiers { + if let Ok(envvar) = env::var(id) { + if !envvar.is_empty() { + map.insert(id, envvar); + } } - if let Ok(clog) = env::var("COMPILE_LOG") { - map.insert("COMPILE_LOG", clog); + } + + 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))); + } + + // 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))); } + } - 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))); + // 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))); + // Blocklist + let blocklist = Blocklist::new(); + data.insert::(Arc::new(RwLock::new(blocklist))); - // Commands - let commands = CommandManager::new(); - data.insert::(Arc::new(RwLock::new(commands))); + // 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)))); + // Diff command message tracker + data.insert::(Arc::new(Mutex::new(LruCache::new(10)))); - Ok(()) + Ok(()) } diff --git a/src/commands/asm.rs b/src/commands/asm.rs index 55ebd4e..084b1c7 100644 --- a/src/commands/asm.rs +++ b/src/commands/asm.rs @@ -1,108 +1,122 @@ +use std::fmt::Write as _; + 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}; +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 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?; - - 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 (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; + 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(()) } pub async fn handle_request( - ctx: Context, - mut content: String, - author: User, - msg: &Message, -) -> Result { - let data_read = ctx.data.read().await; - let loading_reaction = { - let botinfo_lock = data_read.get::().unwrap(); - let botinfo = botinfo_lock.read().await; - if let Some(loading_id) = botinfo.get("LOADING_EMOJI_ID") { - let loading_name = botinfo - .get("LOADING_EMOJI_NAME") - .expect("Unable to find loading emoji name") - .clone(); - 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(); + ctx: Context, + mut content: String, + author: User, + msg: &Message, +) -> Result<(CreateEmbed, CompilationDetails), CommandError> { + 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))); - } - }; - - // 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{}")); - } + // 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 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 - ))); - } + // 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))); + } }; - // 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?", + )); + } + + 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!("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/block.rs b/src/commands/block.rs index 40a15f0..f0e2272 100644 --- a/src/commands/block.rs +++ b/src/commands/block.rs @@ -7,19 +7,18 @@ 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?; + debug!("Command executed"); + 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..b2c5ba2 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,136 +12,148 @@ 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"] 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, 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 sent = msg + .channel_id + .send_message(&ctx.http, |e| { + *e = new_msg.clone(); + e + }) + .await?; + + // Success/fail react + 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(sent, msg.clone())); + debug!("Command executed"); + Ok(()) } pub async fn handle_request( - ctx: Context, - mut content: String, - author: User, - msg: &Message, -) -> Result { - let data_read = ctx.data.read().await; - let loading_reaction = { - let botinfo_lock = data_read.get::().unwrap(); - let botinfo = botinfo_lock.read().await; - if let Some(loading_id) = botinfo.get("LOADING_EMOJI_ID") { - let loading_name = botinfo - .get("LOADING_EMOJI_NAME") - .expect("Unable to find loading emoji name") - .clone(); - 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(); + ctx: Context, + mut content: String, + author: User, + msg: &Message, +) -> Result<(CreateEmbed, CompilationDetails), CommandError> { + 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{}")); + }; + + // Try to load in an attachment + let (code, ext) = parser::get_message_attachment(&msg.attachments).await?; + if !code.is_empty() { + writeln!(&mut content, "\n```{}\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?", + )); + } + + // dispatch our req + let compilation_manager_lock: RwLockReadGuard = + compilation_manager.read().await; + 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 + 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; - // 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 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; + 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; - } + } + let stats = data_read.get::().unwrap().lock().await; + if stats.should_track() { + stats.compilation(&result.0.language, !is_success).await; + } + + let config = data_read.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, result.0)) } 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..3b5e720 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::managers::compilation::CompilationDetails; 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?; - // Dispatch our request - let compilation_embed = msg - .channel_id - .send_message(&ctx.http, |_| &mut emb_msg) - .await?; + // Dispatch our request + let compilation_embed = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).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(()) + debug!("Command executed"); + Ok(()) } pub async fn handle_request( - ctx: Context, - content: String, - author: User, - msg: &Message, -) -> std::result::Result { - let loading_reaction = { - let data_read = ctx.data.read().await; - let botinfo_lock = data_read.get::().unwrap(); - let botinfo = botinfo_lock.read().await; - if let Some(loading_id) = botinfo.get("LOADING_EMOJI_ID") { - let loading_name = botinfo - .get("LOADING_EMOJI_NAME") - .expect("Unable to find loading emoji name") - .clone(); - 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`")); + ctx: Context, + content: String, + author: User, + msg: &Message, +) -> std::result::Result<(CreateEmbed, CompilationDetails), CommandError> { + 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?", + )); + } - 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![], + }; - return Err(CommandError::from(format!("{}", e))); - } - }; + 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?; - // remove our loading emote - discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await?; - let options = EmbedOptions::new(false, fake_parse.target.clone(), String::default()); + return Err(CommandError::from(format!("{}", e))); + } + }; - Ok(result.1.to_embed(&author, &options)) + // remove our loading emote + discordhelpers::delete_bot_reacts(&ctx, msg, loading_reaction).await?; + 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 6a08f03..994255f 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,139 +10,125 @@ 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"); - 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 godbolt.org") - }) - .await?; - let _ = std::fs::remove_file(&path); - } else { - msg.reply( - &ctx.http, - format!( - "\n```{}\n{}```\n*Powered by godbolt.org*", - lang_code, answer - ), - ) - .await?; - } - Ok(()) + msg + .channel_id + .send_message(&ctx.http, |msg| msg.add_file(path.as_str()).content("Powered by godbolt.org")) + .await?; + let _ = std::fs::remove_file(&path); + } else { + msg + .reply(&ctx.http, format!("\n```{}\n{}```\n*Powered by godbolt.org*", lang_code, answer)) + .await?; + } + debug!("Command executed"); + Ok(()) } diff --git a/src/commands/formats.rs b/src/commands/formats.rs index a0f908b..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,50 +9,41 @@ 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; - 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"); } + 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?; - - return Ok(()); + embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await?; + debug!("Command executed"); + return Ok(()); } diff --git a/src/commands/help.rs b/src/commands/help.rs index d799c1a..491b54b 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,121 @@ 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); + embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).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)); @@ -140,15 +136,15 @@ 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 }) }).await?; - debug!("Command executed"); - Ok(()) + debug!("Command executed"); + Ok(()) } diff --git a/src/commands/invite.rs b/src/commands/invite.rs index e4f600b..9374832 100644 --- a/src/commands/invite.rs +++ b/src/commands/invite.rs @@ -8,14 +8,10 @@ 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 mut emb_msg = embeds::embed_message(emb); - msg.channel_id - .send_message(&ctx.http, |_| &mut emb_msg) - .await?; - - Ok(()) + 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/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..8911d36 100644 --- a/src/commands/unblock.rs +++ b/src/commands/unblock.rs @@ -7,19 +7,18 @@ 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?; + debug!("Command executed"); + Ok(()) } diff --git a/src/cppeval/eval.rs b/src/cppeval/eval.rs index d61754b..49665d8 100644 --- a/src/cppeval/eval.rs +++ b/src/cppeval/eval.rs @@ -3,191 +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 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 - if let Err(e) = self.do_statements() { - return Err(e); - } - } 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); - } - } - - 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")); - } - - Ok(()) + 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(); } - 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); - - let statements = self.input[1..end].to_owned(); - self.build_main(&statements); - - Ok(()) + // 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 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; - } - stop_idx - } - - fn do_rest(&mut self, start_idx: usize) { - let rest = &self.input[start_idx..]; - self.output.push_str(rest.trim()); - } + Ok(self.output.clone()) + } - fn do_prints(&mut self) { - let input = if let Some(statement_end) = self.input.find(';') { - self.do_rest(statement_end + 1); + 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.input[..statement_end].to_owned() - } else { - self.input.clone() - }; - self.build_main(&format!("cout {};", input)); + // self.output.push_str(&format!("{}\n", rest)); + // self.output.push_str(&format!("{}\n", main)); + writeln!(self.output, "{}\n{}", rest, main).unwrap(); + } else { + return Err(EvalError::new("No main() specified. Invalid request")); } - fn add_headers(&mut self) { - self.output.push_str("#include \n"); - self.output.push_str("using namespace std;\n"); - //self.add_ostreaming(); - } + Ok(()) + } - 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 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 add_ostreaming(& mut self) { - let vec_print = include_str!("more_ostreaming.in"); - self.output.push_str(vec_print); - self.output.push_str("\n\n"); + 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; + } + 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 a86db6f..33f7011 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,425 +1,413 @@ 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::{ + application::interaction::Interaction, channel::Message, channel::ReactionType, + event::MessageUpdateEvent, gateway::Ready, guild::Guild, id::ChannelId, id::GuildId, + id::MessageId, prelude::UnavailableGuild, + }, + prelude::*, }; - -use tokio::sync::MutexGuard; +use std::env; use chrono::{DateTime, Utc}; +use serenity::model::application::component::ButtonStyle; 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, + 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>, - ready: &Ready, - ); + 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>, - ready: &Ready, - ) { - let data = ctx.data.read().await; - let mut info = data.get::().unwrap().write().await; - info.insert("BOT_AVATAR", ready.user.avatar_url().unwrap()); - - 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()); - - // 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 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; + discordhelpers::send_global_presence(&shard_manager, server_count).await; + info!("Ready in {} guilds", 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; } + } } #[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 + // publish/queue new server to stats + let (server_count, shard_count) = { let mut stats = data.get::().unwrap().lock().await; - stats.leave_server().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 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; + if let Err(e) = dbl.update_stats(id, new_stats).await { + warn!("Failed to post stats to dbl: {}", e); + } + } - // 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()), - }; + // update guild count in presence + let shard_manager = data.get::().unwrap().lock().await; + discordhelpers::send_global_presence(&shard_manager, server_count).await; + } - 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); - } - } + info!("Joining {}", guild.name); - // update guild count in presence - let shard_manager = data.get::().unwrap().lock().await; - discordhelpers::send_global_presence(&shard_manager, stats.server_count()).await; - } + if let Some(system_channel) = guild.system_channel_id { + let _ = + embeds::dispatch_embed(&ctx.http, system_channel, embeds::build_welcome_embed()).await; + } + } + } - info!("Leaving {}", &incomplete.id); + async fn guild_delete(&self, ctx: Context, incomplete: UnavailableGuild) { + let data = ctx.data.read().await; + + // 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 emb = match handle_request( - ctx.clone(), - format!(";compile\n```{}\n{}\n```", language, code), - new_message.author.clone(), - &new_message, - ) - .await - { - Ok(emb) => emb, - Err(e) => { - let emb = embeds::build_fail_embed( - &new_message.author, - &format!("{}", e), - ); - let 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 (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 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; + 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, 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("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(), + &new_message, + ) + .await + { + 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 { + let mut message_cache = data.get::().unwrap().lock().await; + message_cache.insert(new_message.id.0, MessageCacheEntry::new(sent, new_message)); + } + return; + } + }; - 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; + // 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; + } } + } } - - 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); - - if stats.shard_count() == total_shards_to_spawn { - self.all_shards_ready(&ctx, &mut stats, &ready).await; - } + } + + async fn message_delete( + &self, + ctx: Context, + _channel_id: ChannelId, + id: MessageId, + _guild_id: Option, + ) { + 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 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( + &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 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); + + // 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()); + } + stats.shard_count() + }; - 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; - } - } - } + 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 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 + // 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; + } - // 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 (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; + (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.", + ); - // 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 _ = 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 + 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 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())); } + } - // 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() - {} - } + if let DispatchError::Ratelimited(_) = error { + let emb = embeds::build_fail_embed(&msg.author, "You are sending requests too fast!"); + let _ = embeds::dispatch_embed(&ctx.http, msg.channel_id, emb).await; + } } diff --git a/src/main.rs b/src/main.rs index d705efc..5c7626c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,121 +27,112 @@ 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; 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) + if let Some(team) = info.team { + for member in &team.members { + owners.insert(member.user.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)) - } - }; - - 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| { - tokio::spawn({ - let http = http.clone(); - let plog_parse = plog.parse::().unwrap(); - let panic_str = info.to_string(); - async move { manual_dispatch(http, plog_parse, panic_embed(panic_str)).await } - }); - 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..3e6a6ed 100644 --- a/src/managers/command.rs +++ b/src/managers/command.rs @@ -2,199 +2,168 @@ 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 { - 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 == CommandType::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 Command::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(CommandType::Message) + .name(format!("Compile{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(CommandType::Message) + .name(format!("Assembly{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(CommandType::Message) + .name(format!("Format{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(CommandType::Message) + .name(format!("Diff{}", if cfg!(debug_assertions) { " [BETA]" } else { "" })); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(CommandType::ChatInput) + .name("help") + .description("Information on how to use the compiler"); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(CommandType::ChatInput) + .name("invite") + .description("Grab my invite link to invite me to your server"); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd.kind(CommandType::ChatInput).name("ping").description("Test my ping to Discord's endpoint"); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(CommandType::ChatInput) + .name("cpp") + .description("Shorthand C++ compilation using geordi-like syntax") + .create_option(|opt| { + opt + .required(false) + .name("input") + .kind(CommandOptionType::String) + .description("Geordi-like input") + }); + cmds.push(cmd); + + cmd = CreateApplicationCommand::default(); + cmd + .kind(CommandType::ChatInput) + .name("diff") + .description("Posts a diff of two message code blocks") + .create_option(|opt| { + opt + .required(true) + .name("message1") + .kind(CommandOptionType::String) + .description("Message id of first code-block") + }) + .create_option(|opt| { + opt + .required(true) + .name("message2") + .kind(CommandOptionType::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 176a64b..afebf3e 100644 --- a/src/managers/compilation.rs +++ b/src/managers/compilation.rs @@ -13,32 +13,33 @@ 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 language: String, + pub compiler: String, + pub base64: Option, } //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 +47,269 @@ 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 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))) - } + 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.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.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 assembly( + &self, + parse_result: &ParserResult, + author: &User, + ) -> Result<(CompilationDetails, 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 godbolt.org", + target + ))), + Some(compiler) => { + let response = + Godbolt::send_request(&compiler, &parse_result.code, options.clone(), USER_AGENT).await?; + let base64 = Godbolt::get_base64(&compiler, &parse_result.code, 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(), + language: target.to_string(), + compiler: compiler.name, + base64: Some(base64), }; - // 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)) + let options = EmbedOptions::new(true, details.clone()); + Ok((details, response.to_embed(author, &options))) + } } - - pub fn resolve_target(&self, target: &str) -> RequestHandler { - if target == "scala" || target == "nim" { - 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; - } - } - - RequestHandler::None + } + + 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(); + + // 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 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)) + } + + pub fn resolve_target(&self, target: &str) -> RequestHandler { + if target == "scala" || target == "nim" || target == "typescript" { + 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(); - } + 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; + } + } - 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..8306470 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::application::interaction::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..cd4fe27 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::application::interaction::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..cfd1452 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::*, + 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; 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,47 @@ 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"); - - if let ApplicationCommandInteractionDataOptionValue::String(input) = geordi_input { - let mut eval = CppEval::new(input); - let out = eval.evaluate()?; + let geordi_input = msg + .data + .options + .get(0) + .expect("Expected interaction option 0") + .resolved + .as_ref() + .expect("Expected data option value"); - 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![], - }; + if let CommandDataOptionValue::String(input) = geordi_input { + let mut eval = CppEval::new(input); + let out = eval.evaluate()?; - 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 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![], + }; - msg.edit_original_interaction_response(&ctx.http, |resp| { - resp.add_embed(result.1.to_embed(&msg.user, &options)) - }) - .await?; - } + 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, result.0); - debug!("Command executed"); - Ok(()) + msg + .edit_original_interaction_response(&ctx.http, |resp| { + resp.add_embed(result.1.to_embed(&msg.user, &options)) + }) + .await?; + } + debug!("Command executed"); + Ok(()) } diff --git a/src/slashcmds/diff.rs b/src/slashcmds/diff.rs index 096bb4d..dfcb46e 100644 --- a/src/slashcmds/diff.rs +++ b/src/slashcmds/diff.rs @@ -1,139 +1,136 @@ +use std::fmt::Write as _; + 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::application::interaction::application_command::ApplicationCommandInteraction, + model::application::interaction::application_command::CommandDataOptionValue, + model::application::interaction::MessageFlags, 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 CommandDataOptionValue::String(input) = message1 { + message1_parse = input.parse::().ok(); + } + let mut message2_parse = None; + if let CommandDataOptionValue::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(MessageFlags::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(MessageFlags::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..3d49110 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::application::interaction::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 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?; + 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?; + 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?; + + let mut diff_cache = diff_cache_lock.lock().await; + diff_cache.remove(interaction.user.id.as_u64()); + } 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 - - 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) - }; + // we can execute our diff now - 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); + 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) + }; - first_interaction - .edit_original_interaction_response(&ctx.http, interactions::edit_to_dismiss_response) - .await?; + 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); - msg.create_interaction_response(&ctx.http, |resp| { - interactions::create_diff_response(resp, &output) - }) - .await?; - } + first_interaction + .edit_original_interaction_response(&ctx.http, interactions::edit_to_dismiss_response) + .await?; - debug!("Command executed"); - Ok(()) + msg + .create_interaction_response(&ctx.http, |resp| { + interactions::create_diff_response(resp, &output) + }) + .await?; + } + debug!("Command executed"); + Ok(()) } diff --git a/src/slashcmds/format.rs b/src/slashcmds/format.rs index c8d355a..d52e730 100644 --- a/src/slashcmds/format.rs +++ b/src/slashcmds/format.rs @@ -1,232 +1,221 @@ 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::application::component::ButtonStyle, + model::application::interaction::application_command::ApplicationCommandInteraction, + model::application::interaction::InteractionResponseType, + model::application::interaction::MessageFlags, + prelude::*, }; use std::time::Duration; pub async fn format(ctx: &Context, command: &ApplicationCommandInteraction) -> CommandResult { - let mut msg = None; - let mut parse_result = ParserResult::default(); + 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.", - )); - } - - 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; + } + + // interaction expired... + if !selected { + return Ok(()); + } + + let styles = &comp_mgr + .gbolt + .as_ref() + .unwrap() + .formats + .iter() + .find(|p| p.format_type == formatter) + .unwrap() + .styles; + + // Google is our default value for clang-fmt + let mut style = String::from("Google"); + if !styles.is_empty() { command - .edit_original_interaction_response(&ctx.http, |resp| { - create_styles_interaction(resp, styles) - }) - .await?; + .edit_original_interaction_response(&ctx.http, |resp| create_styles_interaction(resp, styles)) + .await?; let resp = command.get_interaction_response(&ctx.http).await?; - cib = resp - .await_component_interactions(&ctx.shard) - .timeout(Duration::from_secs(30)); + cib = resp.await_component_interactions(&ctx.shard).timeout(Duration::from_secs(30)); cic = cib.build(); selected = false; - let mut style = String::from("WebKit"); 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.."); - } + 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(()); - } - - 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))), - }; + // they let this expire + if !selected { + return Ok(()); + } - 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 == "WebKit" { - 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(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) + }) + }) + }) + }, + ) } diff --git a/src/slashcmds/help.rs b/src/slashcmds/help.rs index dde2d71..57f6de1 100644 --- a/src/slashcmds/help.rs +++ b/src/slashcmds/help.rs @@ -1,63 +1,56 @@ 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::*}; 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..278166b 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::application::interaction::application_command::ApplicationCommandInteraction, + model::application::interaction::InteractionResponseType, 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..96f85c5 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::application::interaction::application_command::ApplicationCommandInteraction, + model::application::interaction::InteractionResponseType, 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..ac4291d 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/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 305c2a2..b10565c 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() {}"); +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() {}"); } #[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() {}"); +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() {}"); } #[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 562a5fe..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,35 +9,31 @@ 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 << 9; -pub const MAX_ERROR_LEN: usize = 1 << 9; +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", -]; + const_format::formatcp!("discord-compiler-bot/{}", env!("CARGO_PKG_VERSION")); +pub const URL_ALLOW_LIST: [&str; 4] = + ["pastebin.com", "gist.githubusercontent.com", "hastebin.com", "raw.githubusercontent.com"]; + +pub const MAX_OUTPUT_LEN: usize = 250; +pub const MAX_ERROR_LEN: usize = 997; // Boilerplate Regexes lazy_static! { - pub static ref JAVA_MAIN_REGEX: Regex = - Regex::new("\"[^\"]*?\"|(?P
void[\\s]+?main[\\s]*?\\()").unwrap(); - pub static ref C_LIKE_MAIN_REGEX: Regex = - Regex::new("\"[^\"]*?\"|(?P
main[\\s]*?\\()").unwrap(); - pub static ref CSHARP_MAIN_REGEX: Regex = - Regex::new("\"[^\"]*?\"|(?P
static[\\s]+?void[\\s]+?Main[\\s]*?\\()").unwrap(); - pub static ref 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 +43,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 895a158..638f2ed 100644 --- a/src/utls/discordhelpers/embeds.rs +++ b/src/utls/discordhelpers/embeds.rs @@ -1,12 +1,16 @@ use std::fmt::Write as _; -use std::str; +use std::{env, str}; +use serenity::http::Http; +use serenity::model::application::component::ButtonStyle; use serenity::{ - builder::{CreateEmbed, CreateMessage}, - client::Context, - model::prelude::*, + builder::{CreateEmbed, CreateMessage}, + client::Context, + model::prelude::*, }; +use crate::cache::LinkAPICache; +use crate::managers::compilation::CompilationDetails; use wandbox::*; use crate::utls::constants::*; @@ -14,357 +18,392 @@ use crate::utls::discordhelpers; #[derive(Default)] pub struct EmbedOptions { - pub is_assembly: bool, - pub lang: String, - pub compiler: String, + pub is_assembly: bool, + 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 } + } } 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); + } + } + + 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); + } - embed.footer(|f| { - let mut text = author.tag(); + embed.footer(|f| { + let mut text = author.tag(); - if !options.lang.is_empty() { - text = format!("{} | {}", text, options.lang); - } - if !options.compiler.is_empty() { - text = format!("{} | {}", text, options.compiler); - } + if !options.compilation_info.language.is_empty() { + text = format!("{} | {}", text, options.compilation_info.language); + } + if !options.compilation_info.compiler.is_empty() { + text = format!("{} | {}", text, options.compilation_info.compiler); + } - text = format!("{} | wandbox.org", text); - f.text(text) - }); - embed - } + text = format!("{} | wandbox.org", 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); - embed.field(&title, format!("```x86asm\n{}\n```", &str), false); - output = true; - i += 1; - } - if !append.is_empty() { - let title = if i > 1 { - format!("Assembly Output Pt. {}", i) - } else { - String::from("Assembly Output") - }; - - embed.field(&title, format!("```x86asm\n{}\n```", &append), 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 = 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(errors) = self.build_result.unwrap().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 str = append.replace('`', "\u{200B}`"); + embed.field(&title, format!("```x86asm\n{}\n```", &str), 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 { + 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 { + writeln!(errs, "{}", line.text).unwrap(); + } } - - embed.footer(|f| f.text(format!("{} | godbolt.org", appendstr))); - embed + } + + for line in &self.stderr { + 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); } -} -pub async fn edit_message_embed(ctx: &Context, old: &mut Message, emb: CreateEmbed) { - let _ = old - .edit(ctx, |m| { - m.embed(|e| { - e.0 = emb.0; - e - }); - m - }) - .await; -} - -#[allow(dead_code)] -pub fn build_small_compilation_embed(author: &User, res: &mut CompilationResult) -> CreateEmbed { - let mut embed = CreateEmbed::default(); - if res.status != "0" { - embed.color(COLOR_FAIL); - } else { - embed.color(COLOR_OKAY); + let mut appendstr = author.tag(); + if let Some(time) = self.execution_time { + appendstr = format!("{} | {}ms", appendstr, time); } - - 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 !options.compilation_info.language.is_empty() { + appendstr = format!("{} | {}", appendstr, options.compilation_info.language); } - if !res.program_all.is_empty() { - let str = discordhelpers::conform_external_str(&res.program_all, MAX_OUTPUT_LEN); - embed.description(format!("```\n{}\n```", str)); + if !options.compilation_info.compiler.is_empty() { + appendstr = format!("{} | {}", appendstr, options.compilation_info.compiler); } - embed.footer(|f| { - f.text(format!( - "Requested by: {} | Powered by NVidia", - author.tag() - )) - }); + embed.footer(|f| f.text(format!("{} | godbolt.org", appendstr))); embed + } } -pub fn embed_message(emb: CreateEmbed) -> CreateMessage<'static> { - let mut msg = CreateMessage::default(); - msg.embed(|e| { +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 - }); - msg + }); + m + }) + .await; } -pub fn build_dblvote_embed(tag: String) -> CreateEmbed { - let mut embed = CreateEmbed::default(); +#[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); - embed.description(format!("{} voted for us on top.gg!", tag)); - embed.thumbnail(ICON_VOTE); - embed + } + + 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 wandbox.org", 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 +} + +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); + 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(); - 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", - ";compile python\n```py\nprint('hello world')\n```", - true, - ); - embed.field("Learning Time!", "If you like reading the manuals of things, read our [getting started](https://github.com/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 [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 } 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 22c00ec..316c602 100644 --- a/src/utls/discordhelpers/interactions.rs +++ b/src/utls/discordhelpers/interactions.rs @@ -3,584 +3,529 @@ 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::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; 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(MessageFlags::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(); + + let msg = interaction.get_interaction_response(&ctx.http).await?; + if let Some(resp) = msg.await_modal_interaction(&ctx.shard).await { + 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 - ))) - } - 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); - - options.push(option); - } + 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::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()); - } - } + option.label(&compiler.name); + option.value(&compiler.name); + option.description(&compiler.version); - 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); - } + 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) + } + } + + // 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(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 + }) }) + }) + }) + }) } 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(MessageFlags::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); - } + let mut parse_result = ParserResult::default(); - // 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 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?; + let mut msg = None; + 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(MessageFlags::EPHEMERAL)) + }) + .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?; + } + 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(MessageFlags::EPHEMERAL) + .content("Select a compiler:") + .set_components(compile_components) + }, + ) + }) + .await?; + } else { 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; + .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 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; - } + } + + // 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; } - - command - .edit_original_interaction_response(&ctx.http, |resp| { - edit_to_confirmation_interaction(&result, resp) - }) - .await - .unwrap(); - - let int_resp = command.get_interaction_response(&ctx.http).await?; - if let Some(int) = int_resp.await_component_interaction(&ctx.shard).await { - int.create_interaction_response(&ctx.http, 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..1a22986 100644 --- a/src/utls/discordhelpers/menu.rs +++ b/src/utls/discordhelpers/menu.rs @@ -2,89 +2,77 @@ 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 { - 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: Vec::from(items), + page: 0, + components: Menu::build_components(), } + } - pub async fn run(&mut self) -> Result<(), CommandError> { - let mut m = self - .msg - .channel_id - .send_message(&self.ctx.http, |msg| { - msg.set_embed(self.pages[self.page].clone()) - .set_components(self.components.clone()) - }) - .await?; + 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 181abb9..5646c99 100644 --- a/src/utls/discordhelpers/mod.rs +++ b/src/utls/discordhelpers/mod.rs @@ -15,235 +15,205 @@ 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( - 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 } // Pandas#3**2 on serenity disc, tyty pub fn build_reaction(emoji_id: u64, emoji_name: &str) -> ReactionType { - ReactionType::Custom { - animated: false, - id: EmojiId::from(emoji_id), - name: Some(String::from(emoji_name)), - } + ReactionType::Custom { + animated: false, + 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 + 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, 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, 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, None).await; } + } else { + let err = embeds::build_fail_embed(&author, "Invalid command for edit functionality!"); + embeds::edit_message_embed(ctx, &mut old, err, None).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, 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?; + 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, Some(details)).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, 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?; + 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, Some(compilation_details)).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, 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; + let success = emb.0.get("color").unwrap() == COLOR_OKAY; + embeds::edit_message_embed(ctx, &mut old, emb, Some(details)).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; - if success { - { - let data = ctx.data.read().await; - let botinfo_lock = data.get::().unwrap(); - let botinfo = botinfo_lock.read().await; - if let Some(success_id) = botinfo.get("SUCCESS_EMOJI_ID") { - let success_name = botinfo - .get("SUCCESS_EMOJI_NAME") - .expect("Unable to find success emoji name") - .clone(); - reaction = discordhelpers::build_reaction( - success_id.parse::().unwrap(), - &success_name, - ); - } else { - reaction = ReactionType::Unicode(String::from("✅")); - } - } - } else { + 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("❌")); + } } - msg.react(&ctx.http, reaction).await + } + msg.react(&ctx.http, reaction).await } // Certain compiler outputs use unicode control characters that @@ -253,69 +223,66 @@ 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(|e| { + *e = emb; + 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 presence_str = format!("in {} servers | ;invite", server_count); - - let runners = shard_manager.runners.lock().await; - for (_, v) in runners.iter() { - v.runner_tx - .set_presence(Some(Activity::playing(&presence_str)), OnlineStatus::Online); + let server_count = { + if sum < 10000 { + sum.to_string() + } else { + format!("{:.1}k", sum / 1000) } + }; + + // update shard guild count & presence + 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::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 fb9165f..7949128 100644 --- a/src/utls/parser.rs +++ b/src/utls/parser.rs @@ -12,312 +12,311 @@ 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" => "c++", - "rs" => "rust", - "js" => "javascript", - "csharp" => "c#", - "cs" => "c#", - "py" => "python", - _ => 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 the ` character is found before \n we should use the ` as our parse stop point + if end_point == 0 || index < end_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)?; - - 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(); + } else { + cmdline_args = String::from(input[end_point..].trim()); + } + result.args = shell_words::split(&cmdline_args)?; - 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.", - )); - } + 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 replying to is the code. + // Anything else probably doesn't make sense. + 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; } - } 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.", + // shift previos code to stdin, we have found code + result.stdin = result.code; + result.code = String::default(); + result.code = attachment.0; + } 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 { + // 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 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 reply to 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 host = url.host(); - if host.is_none() { - return Err(CommandError::from("Unable to find host")); - } + let url = match reqwest::Url::parse(url) { + Err(e) => return Err(CommandError::from(format!("Error parsing url: {}", e))), + Ok(url) => url, + }; - 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 = url.host(); + if host.is_none() { + return Err(CommandError::from("Unable to find host")); + } - let response = match reqwest::get(url).await { - Ok(b) => b, - Err(_e) => { - return Err(CommandError::from( - "GET request failed, perhaps your link is unreachable?", - )) - } - }; + 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")); + } - 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 occured while parsing file: {}", e))) + } + } + } + Err(e) => Err(CommandError::from(format!("Failure when downloading attachment: {}", e))), + }; + } + Ok((String::new(), String::new())) } 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" 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(()) }