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 @@
-# Discord compiler bot
+# 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::