├── .github ├── dependabot.yaml └── workflows │ ├── auto-update-runner.yaml │ ├── build-release-container.yaml │ ├── check.yaml │ └── release.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── assets ├── README.md ├── container │ └── trampoline ├── demo │ └── rustbot_basic.png └── templates │ ├── help_help.md │ ├── help_ping.md │ ├── help_run.md │ ├── help_running.md │ ├── run_error_too_long.md │ └── run_no_output.md ├── bot.Dockerfile ├── build.rs ├── doc ├── container.md ├── discord.md ├── internals.md └── templates.md ├── package-readme.txt ├── runner.Dockerfile └── src ├── commands ├── help.rs ├── mod.rs ├── ping.rs └── run.rs ├── constants.rs ├── lib.rs ├── main.rs ├── model ├── container.rs └── mod.rs └── util ├── command.rs ├── configuration.rs ├── mod.rs └── template.rs /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | 2 | version: 2 3 | updates: 4 | # Enable version updates for npm 5 | - package-ecosystem: "cargo" 6 | # Look for `package.json` and `lock` files in the `root` directory 7 | directory: "/" 8 | # Check the npm registry for updates every day (weekdays) 9 | schedule: 10 | interval: "daily" 11 | 12 | # Enable version updates for Docker 13 | - package-ecosystem: "docker" 14 | # Look for a `Dockerfile` in the `root` directory 15 | directory: "/" 16 | # Check for updates once a week 17 | schedule: 18 | interval: "weekly" 19 | -------------------------------------------------------------------------------- /.github/workflows/auto-update-runner.yaml: -------------------------------------------------------------------------------- 1 | # Automatically rebuild the runner dockerfile if base image changes 2 | name: Auto update images 3 | on: 4 | #schedule: 5 | # # Random-ish time in the morning 6 | # - cron: "42 3 * * *" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 14 | - uses: actions/checkout@v2 15 | - name: Check if we have to even do all this 16 | id: check 17 | uses: twiddler/is-my-docker-parent-image-out-of-date@v1 18 | with: 19 | parent-image: rust:alpine 20 | my-image: ghcr.io/theconner/rustbot-runner:latest 21 | - name: Needs update? 22 | run: | 23 | echo "Needs to update ${{ steps.check.outputs.out-of-date }}" 24 | outputs: 25 | needsupdate: ${{ steps.check.outputs.out-of-date }} 26 | do-update: 27 | needs: [check] 28 | secrets: inherit 29 | uses: ./.github/workflows/build-release-container.yaml 30 | with: 31 | image_name: rustbot-runner 32 | image_file: runner.Dockerfile 33 | if: needs.check.outputs.needsupdate == 'true' 34 | -------------------------------------------------------------------------------- /.github/workflows/build-release-container.yaml: -------------------------------------------------------------------------------- 1 | # Reusable workflow for building and releasing self-contained images 2 | on: 3 | workflow_call: 4 | inputs: 5 | image_name: 6 | required: true 7 | type: string 8 | image_file: 9 | required: true 10 | type: string 11 | # secrets: 12 | # token: 13 | # required: true 14 | 15 | name: Build and Release Container Image 16 | 17 | jobs: 18 | build-release-docker: 19 | runs-on: ubuntu-latest 20 | permissions: 21 | packages: write 22 | contents: read 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Build image 26 | run: docker build . --file ${{ inputs.image_file }} --tag ${{ inputs.image_name }} --label "runnumber=${GITHUB_RUN_ID}" 27 | - name: Log in to registry 28 | # This is where you will update the PAT to GITHUB_TOKEN 29 | run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin 30 | - name: Push image 31 | run: | 32 | IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$RUNNER_IMAGE_NAME 33 | 34 | # Change all uppercase to lowercase 35 | IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') 36 | 37 | # Strip git ref prefix from version 38 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 39 | 40 | # Strip "v" prefix from tag name 41 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 42 | 43 | # Use Docker `latest` tag convention 44 | [ "$VERSION" == "main" ] && VERSION=latest 45 | 46 | echo IMAGE_ID=$IMAGE_ID 47 | echo VERSION=$VERSION 48 | echo "RUNNER_IMAGE_NAME=$RUNNER_IMAGE_NAME" 49 | docker tag $RUNNER_IMAGE_NAME $IMAGE_ID:$VERSION 50 | docker push $IMAGE_ID:$VERSION 51 | -------------------------------------------------------------------------------- /.github/workflows/check.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - main 6 | 7 | name: Check and Lint 8 | 9 | jobs: 10 | check: 11 | name: Check 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: check 23 | 24 | fmt: 25 | name: Rustfmt 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v2 29 | - uses: actions-rs/toolchain@v1 30 | with: 31 | profile: minimal 32 | toolchain: stable 33 | override: true 34 | - run: rustup component add rustfmt 35 | - uses: actions-rs/cargo@v1 36 | with: 37 | command: fmt 38 | args: --all -- --check 39 | 40 | clippy: 41 | name: Clippy 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v2 45 | - uses: actions-rs/toolchain@v1 46 | with: 47 | toolchain: stable 48 | components: clippy 49 | override: true 50 | - uses: actions-rs/clippy-check@v1 51 | with: 52 | token: ${{ secrets.GITHUB_TOKEN }} 53 | args: --all-features 54 | name: Clippy Output -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | name: Release Packaging 7 | 8 | env: 9 | RUNNER_IMAGE_NAME: rustbot-runner 10 | BOT_IMAGE_NAME: rustbot 11 | PROJECT_NAME_UNDERSCORE: rustbot 12 | 13 | jobs: 14 | package: 15 | name: Release Packaging 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | target: x86_64-unknown-linux-musl 24 | toolchain: stable 25 | override: true 26 | 27 | - name: Install RPM packager 28 | run: cargo install cargo-generate-rpm 29 | 30 | - name: Install MUSL Toolchain 31 | run: sudo apt-get -y install musl-tools 32 | 33 | - name: Build 34 | run: | 35 | cargo build --release --target x86_64-unknown-linux-musl 36 | strip -s target/x86_64-unknown-linux-musl/release/*rustbot 37 | cargo generate-rpm -o target/generate-rpm/rustbot-x86_64.rpm 38 | 39 | - name: 'Upload Release Artifact' 40 | uses: actions/upload-artifact@v2 41 | with: 42 | name: ${{ env.PROJECT_NAME_UNDERSCORE }} 43 | path: | 44 | target/x86_64-unknown-linux-musl/release/*rustbot* 45 | target/x86_64-unknown-linux-musl/release/assets/* 46 | LICENSE* 47 | 48 | - name: 'Upload RPM Artifact' 49 | uses: actions/upload-artifact@v2 50 | with: 51 | name: ${{ env.PROJECT_NAME_UNDERSCORE }}_rpm 52 | path: | 53 | target/generate-rpm/rustbot-x86_64.rpm 54 | 55 | package-docker: 56 | runs-on: ubuntu-latest 57 | needs: package 58 | permissions: 59 | packages: write 60 | contents: read 61 | 62 | steps: 63 | - uses: actions/checkout@v2 64 | 65 | - uses: actions/download-artifact@v2 66 | with: 67 | name: ${{ env.PROJECT_NAME_UNDERSCORE }}_rpm 68 | path: target/generate-rpm/ 69 | 70 | - name: Build runner image 71 | run: docker build . --file runner.Dockerfile --tag $RUNNER_IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" 72 | 73 | - name: Build bot image 74 | run: docker build . --file bot.Dockerfile --tag $BOT_IMAGE_NAME --label "runnumber=${GITHUB_RUN_ID}" 75 | 76 | - name: Log in to registry 77 | # This is where you will update the PAT to GITHUB_TOKEN 78 | run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin 79 | 80 | - name: Push runner image 81 | run: | 82 | IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$RUNNER_IMAGE_NAME 83 | 84 | # Change all uppercase to lowercase 85 | IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') 86 | # Strip git ref prefix from version 87 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 88 | # Strip "v" prefix from tag name 89 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 90 | # Use Docker `latest` tag convention 91 | [ "$VERSION" == "main" ] && VERSION=latest 92 | echo IMAGE_ID=$IMAGE_ID 93 | echo VERSION=$VERSION 94 | docker tag $RUNNER_IMAGE_NAME $IMAGE_ID:$VERSION 95 | docker push $IMAGE_ID:$VERSION 96 | 97 | - name: Push bot image 98 | run: | 99 | IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$BOT_IMAGE_NAME 100 | 101 | # Change all uppercase to lowercase 102 | IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') 103 | # Strip git ref prefix from version 104 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 105 | # Strip "v" prefix from tag name 106 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 107 | # Use Docker `latest` tag convention 108 | [ "$VERSION" == "main" ] && VERSION=latest 109 | echo IMAGE_ID=$IMAGE_ID 110 | echo VERSION=$VERSION 111 | docker tag $BOT_IMAGE_NAME $IMAGE_ID:$VERSION 112 | docker push $IMAGE_ID:$VERSION 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .env 3 | .env 4 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "adler" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "0.7.18" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "async-trait" 22 | version = "0.1.52" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" 25 | dependencies = [ 26 | "proc-macro2", 27 | "quote", 28 | "syn", 29 | ] 30 | 31 | [[package]] 32 | name = "async-tungstenite" 33 | version = "0.17.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" 36 | dependencies = [ 37 | "futures-io", 38 | "futures-util", 39 | "log", 40 | "pin-project-lite", 41 | "tokio", 42 | "tokio-rustls", 43 | "tungstenite", 44 | "webpki-roots", 45 | ] 46 | 47 | [[package]] 48 | name = "async_once" 49 | version = "0.2.6" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "2ce4f10ea3abcd6617873bae9f91d1c5332b4a778bd9ce34d0cd517474c1de82" 52 | 53 | [[package]] 54 | name = "autocfg" 55 | version = "1.1.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 58 | 59 | [[package]] 60 | name = "base64" 61 | version = "0.13.1" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 64 | 65 | [[package]] 66 | name = "base64" 67 | version = "0.20.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" 70 | 71 | [[package]] 72 | name = "bitflags" 73 | version = "1.3.2" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 76 | 77 | [[package]] 78 | name = "block-buffer" 79 | version = "0.10.2" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" 82 | dependencies = [ 83 | "generic-array", 84 | ] 85 | 86 | [[package]] 87 | name = "bumpalo" 88 | version = "3.8.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" 91 | 92 | [[package]] 93 | name = "byteorder" 94 | version = "1.4.3" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 97 | 98 | [[package]] 99 | name = "bytes" 100 | version = "1.1.0" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 103 | 104 | [[package]] 105 | name = "cached" 106 | version = "0.42.0" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "5e5877db5d1af7fae60d06b5db9430b68056a69b3582a0be8e3691e87654aeb6" 109 | dependencies = [ 110 | "async-trait", 111 | "async_once", 112 | "cached_proc_macro", 113 | "cached_proc_macro_types", 114 | "futures", 115 | "hashbrown 0.13.1", 116 | "instant", 117 | "lazy_static", 118 | "once_cell", 119 | "thiserror", 120 | "tokio", 121 | ] 122 | 123 | [[package]] 124 | name = "cached_proc_macro" 125 | version = "0.16.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "e10ca87c81aaa3a949dbbe2b5e6c2c45dbc94ba4897e45ea31ff9ec5087be3dc" 128 | dependencies = [ 129 | "cached_proc_macro_types", 130 | "darling", 131 | "proc-macro2", 132 | "quote", 133 | "syn", 134 | ] 135 | 136 | [[package]] 137 | name = "cached_proc_macro_types" 138 | version = "0.1.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" 141 | 142 | [[package]] 143 | name = "cc" 144 | version = "1.0.72" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" 147 | 148 | [[package]] 149 | name = "cfg-if" 150 | version = "1.0.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 153 | 154 | [[package]] 155 | name = "chrono" 156 | version = "0.4.19" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 159 | dependencies = [ 160 | "libc", 161 | "num-integer", 162 | "num-traits", 163 | "serde", 164 | "winapi", 165 | ] 166 | 167 | [[package]] 168 | name = "command_attr" 169 | version = "0.4.1" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "4d999d4e7731150ee14aee8f619c7a9aa9a4385bca0606c4fa95aa2f36a05d9a" 172 | dependencies = [ 173 | "proc-macro2", 174 | "quote", 175 | "syn", 176 | ] 177 | 178 | [[package]] 179 | name = "cpufeatures" 180 | version = "0.2.1" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 183 | dependencies = [ 184 | "libc", 185 | ] 186 | 187 | [[package]] 188 | name = "crc32fast" 189 | version = "1.3.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836" 192 | dependencies = [ 193 | "cfg-if", 194 | ] 195 | 196 | [[package]] 197 | name = "crypto-common" 198 | version = "0.1.3" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" 201 | dependencies = [ 202 | "generic-array", 203 | "typenum", 204 | ] 205 | 206 | [[package]] 207 | name = "darling" 208 | version = "0.14.2" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" 211 | dependencies = [ 212 | "darling_core", 213 | "darling_macro", 214 | ] 215 | 216 | [[package]] 217 | name = "darling_core" 218 | version = "0.14.2" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" 221 | dependencies = [ 222 | "fnv", 223 | "ident_case", 224 | "proc-macro2", 225 | "quote", 226 | "strsim", 227 | "syn", 228 | ] 229 | 230 | [[package]] 231 | name = "darling_macro" 232 | version = "0.14.2" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" 235 | dependencies = [ 236 | "darling_core", 237 | "quote", 238 | "syn", 239 | ] 240 | 241 | [[package]] 242 | name = "dashmap" 243 | version = "5.2.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "4c8858831f7781322e539ea39e72449c46b059638250c14344fec8d0aa6e539c" 246 | dependencies = [ 247 | "cfg-if", 248 | "num_cpus", 249 | "parking_lot", 250 | "serde", 251 | ] 252 | 253 | [[package]] 254 | name = "digest" 255 | version = "0.10.3" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" 258 | dependencies = [ 259 | "block-buffer", 260 | "crypto-common", 261 | ] 262 | 263 | [[package]] 264 | name = "dotenv" 265 | version = "0.15.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 268 | 269 | [[package]] 270 | name = "encoding_rs" 271 | version = "0.8.30" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" 274 | dependencies = [ 275 | "cfg-if", 276 | ] 277 | 278 | [[package]] 279 | name = "flate2" 280 | version = "1.0.22" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" 283 | dependencies = [ 284 | "cfg-if", 285 | "crc32fast", 286 | "libc", 287 | "miniz_oxide", 288 | ] 289 | 290 | [[package]] 291 | name = "fnv" 292 | version = "1.0.7" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 295 | 296 | [[package]] 297 | name = "form_urlencoded" 298 | version = "1.0.1" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 301 | dependencies = [ 302 | "matches", 303 | "percent-encoding", 304 | ] 305 | 306 | [[package]] 307 | name = "futures" 308 | version = "0.3.19" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" 311 | dependencies = [ 312 | "futures-channel", 313 | "futures-core", 314 | "futures-io", 315 | "futures-sink", 316 | "futures-task", 317 | "futures-util", 318 | ] 319 | 320 | [[package]] 321 | name = "futures-channel" 322 | version = "0.3.19" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" 325 | dependencies = [ 326 | "futures-core", 327 | "futures-sink", 328 | ] 329 | 330 | [[package]] 331 | name = "futures-core" 332 | version = "0.3.19" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" 335 | 336 | [[package]] 337 | name = "futures-io" 338 | version = "0.3.19" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" 341 | 342 | [[package]] 343 | name = "futures-sink" 344 | version = "0.3.19" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" 347 | 348 | [[package]] 349 | name = "futures-task" 350 | version = "0.3.19" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" 353 | 354 | [[package]] 355 | name = "futures-util" 356 | version = "0.3.19" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" 359 | dependencies = [ 360 | "futures-channel", 361 | "futures-core", 362 | "futures-io", 363 | "futures-sink", 364 | "futures-task", 365 | "memchr", 366 | "pin-project-lite", 367 | "pin-utils", 368 | "slab", 369 | ] 370 | 371 | [[package]] 372 | name = "generic-array" 373 | version = "0.14.4" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 376 | dependencies = [ 377 | "typenum", 378 | "version_check", 379 | ] 380 | 381 | [[package]] 382 | name = "getrandom" 383 | version = "0.2.6" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" 386 | dependencies = [ 387 | "cfg-if", 388 | "libc", 389 | "wasi", 390 | ] 391 | 392 | [[package]] 393 | name = "h2" 394 | version = "0.3.9" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd" 397 | dependencies = [ 398 | "bytes", 399 | "fnv", 400 | "futures-core", 401 | "futures-sink", 402 | "futures-util", 403 | "http", 404 | "indexmap", 405 | "slab", 406 | "tokio", 407 | "tokio-util", 408 | "tracing", 409 | ] 410 | 411 | [[package]] 412 | name = "hashbrown" 413 | version = "0.11.2" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 416 | 417 | [[package]] 418 | name = "hashbrown" 419 | version = "0.13.1" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" 422 | 423 | [[package]] 424 | name = "hermit-abi" 425 | version = "0.1.19" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 428 | dependencies = [ 429 | "libc", 430 | ] 431 | 432 | [[package]] 433 | name = "http" 434 | version = "0.2.5" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" 437 | dependencies = [ 438 | "bytes", 439 | "fnv", 440 | "itoa 0.4.8", 441 | ] 442 | 443 | [[package]] 444 | name = "http-body" 445 | version = "0.4.4" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" 448 | dependencies = [ 449 | "bytes", 450 | "http", 451 | "pin-project-lite", 452 | ] 453 | 454 | [[package]] 455 | name = "httparse" 456 | version = "1.5.1" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" 459 | 460 | [[package]] 461 | name = "httpdate" 462 | version = "1.0.2" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 465 | 466 | [[package]] 467 | name = "hyper" 468 | version = "0.14.16" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" 471 | dependencies = [ 472 | "bytes", 473 | "futures-channel", 474 | "futures-core", 475 | "futures-util", 476 | "h2", 477 | "http", 478 | "http-body", 479 | "httparse", 480 | "httpdate", 481 | "itoa 0.4.8", 482 | "pin-project-lite", 483 | "socket2", 484 | "tokio", 485 | "tower-service", 486 | "tracing", 487 | "want", 488 | ] 489 | 490 | [[package]] 491 | name = "hyper-rustls" 492 | version = "0.23.0" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" 495 | dependencies = [ 496 | "http", 497 | "hyper", 498 | "rustls", 499 | "tokio", 500 | "tokio-rustls", 501 | ] 502 | 503 | [[package]] 504 | name = "ident_case" 505 | version = "1.0.1" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 508 | 509 | [[package]] 510 | name = "idna" 511 | version = "0.2.3" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 514 | dependencies = [ 515 | "matches", 516 | "unicode-bidi", 517 | "unicode-normalization", 518 | ] 519 | 520 | [[package]] 521 | name = "indexmap" 522 | version = "1.7.0" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 525 | dependencies = [ 526 | "autocfg", 527 | "hashbrown 0.11.2", 528 | ] 529 | 530 | [[package]] 531 | name = "instant" 532 | version = "0.1.12" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 535 | dependencies = [ 536 | "cfg-if", 537 | ] 538 | 539 | [[package]] 540 | name = "ipnet" 541 | version = "2.3.1" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" 544 | 545 | [[package]] 546 | name = "itoa" 547 | version = "0.4.8" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 550 | 551 | [[package]] 552 | name = "itoa" 553 | version = "1.0.1" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 556 | 557 | [[package]] 558 | name = "js-sys" 559 | version = "0.3.55" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" 562 | dependencies = [ 563 | "wasm-bindgen", 564 | ] 565 | 566 | [[package]] 567 | name = "lazy_static" 568 | version = "1.4.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 571 | 572 | [[package]] 573 | name = "levenshtein" 574 | version = "1.0.5" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" 577 | 578 | [[package]] 579 | name = "libc" 580 | version = "0.2.120" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "ad5c14e80759d0939d013e6ca49930e59fc53dd8e5009132f76240c179380c09" 583 | 584 | [[package]] 585 | name = "lock_api" 586 | version = "0.4.7" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 589 | dependencies = [ 590 | "autocfg", 591 | "scopeguard", 592 | ] 593 | 594 | [[package]] 595 | name = "log" 596 | version = "0.4.17" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 599 | dependencies = [ 600 | "cfg-if", 601 | ] 602 | 603 | [[package]] 604 | name = "matches" 605 | version = "0.1.9" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 608 | 609 | [[package]] 610 | name = "memchr" 611 | version = "2.4.1" 612 | source = "registry+https://github.com/rust-lang/crates.io-index" 613 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 614 | 615 | [[package]] 616 | name = "mime" 617 | version = "0.3.16" 618 | source = "registry+https://github.com/rust-lang/crates.io-index" 619 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 620 | 621 | [[package]] 622 | name = "mime_guess" 623 | version = "2.0.3" 624 | source = "registry+https://github.com/rust-lang/crates.io-index" 625 | checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" 626 | dependencies = [ 627 | "mime", 628 | "unicase", 629 | ] 630 | 631 | [[package]] 632 | name = "miniz_oxide" 633 | version = "0.4.4" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 636 | dependencies = [ 637 | "adler", 638 | "autocfg", 639 | ] 640 | 641 | [[package]] 642 | name = "mio" 643 | version = "0.7.14" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" 646 | dependencies = [ 647 | "libc", 648 | "log", 649 | "miow", 650 | "ntapi", 651 | "winapi", 652 | ] 653 | 654 | [[package]] 655 | name = "miow" 656 | version = "0.3.7" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 659 | dependencies = [ 660 | "winapi", 661 | ] 662 | 663 | [[package]] 664 | name = "ntapi" 665 | version = "0.3.6" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" 668 | dependencies = [ 669 | "winapi", 670 | ] 671 | 672 | [[package]] 673 | name = "nu-ansi-term" 674 | version = "0.46.0" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 677 | dependencies = [ 678 | "overload", 679 | "winapi", 680 | ] 681 | 682 | [[package]] 683 | name = "num-integer" 684 | version = "0.1.44" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 687 | dependencies = [ 688 | "autocfg", 689 | "num-traits", 690 | ] 691 | 692 | [[package]] 693 | name = "num-traits" 694 | version = "0.2.14" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 697 | dependencies = [ 698 | "autocfg", 699 | ] 700 | 701 | [[package]] 702 | name = "num_cpus" 703 | version = "1.13.1" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 706 | dependencies = [ 707 | "hermit-abi", 708 | "libc", 709 | ] 710 | 711 | [[package]] 712 | name = "num_threads" 713 | version = "0.1.5" 714 | source = "registry+https://github.com/rust-lang/crates.io-index" 715 | checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" 716 | dependencies = [ 717 | "libc", 718 | ] 719 | 720 | [[package]] 721 | name = "once_cell" 722 | version = "1.13.0" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" 725 | 726 | [[package]] 727 | name = "ordered-float" 728 | version = "2.10.0" 729 | source = "registry+https://github.com/rust-lang/crates.io-index" 730 | checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" 731 | dependencies = [ 732 | "num-traits", 733 | ] 734 | 735 | [[package]] 736 | name = "overload" 737 | version = "0.1.1" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 740 | 741 | [[package]] 742 | name = "parking_lot" 743 | version = "0.12.0" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" 746 | dependencies = [ 747 | "lock_api", 748 | "parking_lot_core", 749 | ] 750 | 751 | [[package]] 752 | name = "parking_lot_core" 753 | version = "0.9.2" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" 756 | dependencies = [ 757 | "cfg-if", 758 | "libc", 759 | "redox_syscall", 760 | "smallvec", 761 | "windows-sys 0.34.0", 762 | ] 763 | 764 | [[package]] 765 | name = "percent-encoding" 766 | version = "2.1.0" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 769 | 770 | [[package]] 771 | name = "pin-project-lite" 772 | version = "0.2.9" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 775 | 776 | [[package]] 777 | name = "pin-utils" 778 | version = "0.1.0" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 781 | 782 | [[package]] 783 | name = "ppv-lite86" 784 | version = "0.2.15" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" 787 | 788 | [[package]] 789 | name = "proc-macro2" 790 | version = "1.0.49" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" 793 | dependencies = [ 794 | "unicode-ident", 795 | ] 796 | 797 | [[package]] 798 | name = "process_control" 799 | version = "4.0.3" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "32e056a69288d0a211f4c74c48391c6eb86e714fdcb9dc58a9f34302da9c20bf" 802 | dependencies = [ 803 | "libc", 804 | "signal-hook", 805 | "windows-sys 0.48.0", 806 | ] 807 | 808 | [[package]] 809 | name = "quote" 810 | version = "1.0.23" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 813 | dependencies = [ 814 | "proc-macro2", 815 | ] 816 | 817 | [[package]] 818 | name = "rand" 819 | version = "0.8.5" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 822 | dependencies = [ 823 | "libc", 824 | "rand_chacha", 825 | "rand_core", 826 | ] 827 | 828 | [[package]] 829 | name = "rand_chacha" 830 | version = "0.3.1" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 833 | dependencies = [ 834 | "ppv-lite86", 835 | "rand_core", 836 | ] 837 | 838 | [[package]] 839 | name = "rand_core" 840 | version = "0.6.3" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 843 | dependencies = [ 844 | "getrandom", 845 | ] 846 | 847 | [[package]] 848 | name = "redox_syscall" 849 | version = "0.2.13" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" 852 | dependencies = [ 853 | "bitflags", 854 | ] 855 | 856 | [[package]] 857 | name = "regex" 858 | version = "1.7.3" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" 861 | dependencies = [ 862 | "aho-corasick", 863 | "memchr", 864 | "regex-syntax", 865 | ] 866 | 867 | [[package]] 868 | name = "regex-syntax" 869 | version = "0.6.29" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 872 | 873 | [[package]] 874 | name = "reqwest" 875 | version = "0.11.8" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "7c4e0a76dc12a116108933f6301b95e83634e0c47b0afbed6abbaa0601e99258" 878 | dependencies = [ 879 | "base64 0.13.1", 880 | "bytes", 881 | "encoding_rs", 882 | "futures-core", 883 | "futures-util", 884 | "http", 885 | "http-body", 886 | "hyper", 887 | "hyper-rustls", 888 | "ipnet", 889 | "js-sys", 890 | "lazy_static", 891 | "log", 892 | "mime", 893 | "mime_guess", 894 | "percent-encoding", 895 | "pin-project-lite", 896 | "rustls", 897 | "rustls-pemfile", 898 | "serde", 899 | "serde_json", 900 | "serde_urlencoded", 901 | "tokio", 902 | "tokio-rustls", 903 | "tokio-util", 904 | "url", 905 | "wasm-bindgen", 906 | "wasm-bindgen-futures", 907 | "web-sys", 908 | "webpki-roots", 909 | "winreg", 910 | ] 911 | 912 | [[package]] 913 | name = "ring" 914 | version = "0.16.20" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" 917 | dependencies = [ 918 | "cc", 919 | "libc", 920 | "once_cell", 921 | "spin", 922 | "untrusted", 923 | "web-sys", 924 | "winapi", 925 | ] 926 | 927 | [[package]] 928 | name = "rustbot" 929 | version = "0.1.0" 930 | dependencies = [ 931 | "base64 0.20.0", 932 | "cached", 933 | "dotenv", 934 | "lazy_static", 935 | "process_control", 936 | "regex", 937 | "serenity", 938 | "tokio", 939 | "tracing", 940 | "tracing-subscriber", 941 | ] 942 | 943 | [[package]] 944 | name = "rustls" 945 | version = "0.20.2" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" 948 | dependencies = [ 949 | "log", 950 | "ring", 951 | "sct", 952 | "webpki", 953 | ] 954 | 955 | [[package]] 956 | name = "rustls-pemfile" 957 | version = "0.2.1" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" 960 | dependencies = [ 961 | "base64 0.13.1", 962 | ] 963 | 964 | [[package]] 965 | name = "ryu" 966 | version = "1.0.9" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 969 | 970 | [[package]] 971 | name = "scopeguard" 972 | version = "1.1.0" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 975 | 976 | [[package]] 977 | name = "sct" 978 | version = "0.7.0" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" 981 | dependencies = [ 982 | "ring", 983 | "untrusted", 984 | ] 985 | 986 | [[package]] 987 | name = "serde" 988 | version = "1.0.136" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" 991 | dependencies = [ 992 | "serde_derive", 993 | ] 994 | 995 | [[package]] 996 | name = "serde-value" 997 | version = "0.7.0" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" 1000 | dependencies = [ 1001 | "ordered-float", 1002 | "serde", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "serde_derive" 1007 | version = "1.0.136" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" 1010 | dependencies = [ 1011 | "proc-macro2", 1012 | "quote", 1013 | "syn", 1014 | ] 1015 | 1016 | [[package]] 1017 | name = "serde_json" 1018 | version = "1.0.79" 1019 | source = "registry+https://github.com/rust-lang/crates.io-index" 1020 | checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" 1021 | dependencies = [ 1022 | "itoa 1.0.1", 1023 | "ryu", 1024 | "serde", 1025 | ] 1026 | 1027 | [[package]] 1028 | name = "serde_urlencoded" 1029 | version = "0.7.0" 1030 | source = "registry+https://github.com/rust-lang/crates.io-index" 1031 | checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" 1032 | dependencies = [ 1033 | "form_urlencoded", 1034 | "itoa 0.4.8", 1035 | "ryu", 1036 | "serde", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "serenity" 1041 | version = "0.11.5" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "82fd5e7b5858ad96e99d440138f34f5b98e1b959ebcd3a1036203b30e78eb788" 1044 | dependencies = [ 1045 | "async-trait", 1046 | "async-tungstenite", 1047 | "base64 0.13.1", 1048 | "bitflags", 1049 | "bytes", 1050 | "cfg-if", 1051 | "chrono", 1052 | "command_attr", 1053 | "dashmap", 1054 | "flate2", 1055 | "futures", 1056 | "levenshtein", 1057 | "mime", 1058 | "mime_guess", 1059 | "parking_lot", 1060 | "percent-encoding", 1061 | "reqwest", 1062 | "serde", 1063 | "serde-value", 1064 | "serde_json", 1065 | "static_assertions", 1066 | "time", 1067 | "tokio", 1068 | "tracing", 1069 | "typemap_rev", 1070 | "url", 1071 | "uwl", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "sha-1" 1076 | version = "0.10.0" 1077 | source = "registry+https://github.com/rust-lang/crates.io-index" 1078 | checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" 1079 | dependencies = [ 1080 | "cfg-if", 1081 | "cpufeatures", 1082 | "digest", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "sharded-slab" 1087 | version = "0.1.4" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1090 | dependencies = [ 1091 | "lazy_static", 1092 | ] 1093 | 1094 | [[package]] 1095 | name = "signal-hook" 1096 | version = "0.3.14" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" 1099 | dependencies = [ 1100 | "libc", 1101 | "signal-hook-registry", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "signal-hook-registry" 1106 | version = "1.4.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1109 | dependencies = [ 1110 | "libc", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "slab" 1115 | version = "0.4.5" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 1118 | 1119 | [[package]] 1120 | name = "smallvec" 1121 | version = "1.10.0" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 1124 | 1125 | [[package]] 1126 | name = "socket2" 1127 | version = "0.4.2" 1128 | source = "registry+https://github.com/rust-lang/crates.io-index" 1129 | checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" 1130 | dependencies = [ 1131 | "libc", 1132 | "winapi", 1133 | ] 1134 | 1135 | [[package]] 1136 | name = "spin" 1137 | version = "0.5.2" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 1140 | 1141 | [[package]] 1142 | name = "static_assertions" 1143 | version = "1.1.0" 1144 | source = "registry+https://github.com/rust-lang/crates.io-index" 1145 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1146 | 1147 | [[package]] 1148 | name = "strsim" 1149 | version = "0.10.0" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1152 | 1153 | [[package]] 1154 | name = "syn" 1155 | version = "1.0.103" 1156 | source = "registry+https://github.com/rust-lang/crates.io-index" 1157 | checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" 1158 | dependencies = [ 1159 | "proc-macro2", 1160 | "quote", 1161 | "unicode-ident", 1162 | ] 1163 | 1164 | [[package]] 1165 | name = "thiserror" 1166 | version = "1.0.30" 1167 | source = "registry+https://github.com/rust-lang/crates.io-index" 1168 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 1169 | dependencies = [ 1170 | "thiserror-impl", 1171 | ] 1172 | 1173 | [[package]] 1174 | name = "thiserror-impl" 1175 | version = "1.0.30" 1176 | source = "registry+https://github.com/rust-lang/crates.io-index" 1177 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 1178 | dependencies = [ 1179 | "proc-macro2", 1180 | "quote", 1181 | "syn", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "thread_local" 1186 | version = "1.1.4" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 1189 | dependencies = [ 1190 | "once_cell", 1191 | ] 1192 | 1193 | [[package]] 1194 | name = "time" 1195 | version = "0.3.9" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" 1198 | dependencies = [ 1199 | "itoa 1.0.1", 1200 | "libc", 1201 | "num_threads", 1202 | "serde", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "tinyvec" 1207 | version = "1.5.1" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" 1210 | dependencies = [ 1211 | "tinyvec_macros", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "tinyvec_macros" 1216 | version = "0.1.0" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1219 | 1220 | [[package]] 1221 | name = "tokio" 1222 | version = "1.16.1" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" 1225 | dependencies = [ 1226 | "bytes", 1227 | "libc", 1228 | "memchr", 1229 | "mio", 1230 | "num_cpus", 1231 | "once_cell", 1232 | "pin-project-lite", 1233 | "signal-hook-registry", 1234 | "tokio-macros", 1235 | "winapi", 1236 | ] 1237 | 1238 | [[package]] 1239 | name = "tokio-macros" 1240 | version = "1.7.0" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" 1243 | dependencies = [ 1244 | "proc-macro2", 1245 | "quote", 1246 | "syn", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "tokio-rustls" 1251 | version = "0.23.2" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" 1254 | dependencies = [ 1255 | "rustls", 1256 | "tokio", 1257 | "webpki", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "tokio-util" 1262 | version = "0.6.9" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" 1265 | dependencies = [ 1266 | "bytes", 1267 | "futures-core", 1268 | "futures-sink", 1269 | "log", 1270 | "pin-project-lite", 1271 | "tokio", 1272 | ] 1273 | 1274 | [[package]] 1275 | name = "tower-service" 1276 | version = "0.3.1" 1277 | source = "registry+https://github.com/rust-lang/crates.io-index" 1278 | checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" 1279 | 1280 | [[package]] 1281 | name = "tracing" 1282 | version = "0.1.37" 1283 | source = "registry+https://github.com/rust-lang/crates.io-index" 1284 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1285 | dependencies = [ 1286 | "cfg-if", 1287 | "log", 1288 | "pin-project-lite", 1289 | "tracing-attributes", 1290 | "tracing-core", 1291 | ] 1292 | 1293 | [[package]] 1294 | name = "tracing-attributes" 1295 | version = "0.1.23" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 1298 | dependencies = [ 1299 | "proc-macro2", 1300 | "quote", 1301 | "syn", 1302 | ] 1303 | 1304 | [[package]] 1305 | name = "tracing-core" 1306 | version = "0.1.30" 1307 | source = "registry+https://github.com/rust-lang/crates.io-index" 1308 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 1309 | dependencies = [ 1310 | "once_cell", 1311 | "valuable", 1312 | ] 1313 | 1314 | [[package]] 1315 | name = "tracing-log" 1316 | version = "0.1.3" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 1319 | dependencies = [ 1320 | "lazy_static", 1321 | "log", 1322 | "tracing-core", 1323 | ] 1324 | 1325 | [[package]] 1326 | name = "tracing-subscriber" 1327 | version = "0.3.16" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" 1330 | dependencies = [ 1331 | "nu-ansi-term", 1332 | "sharded-slab", 1333 | "smallvec", 1334 | "thread_local", 1335 | "tracing-core", 1336 | "tracing-log", 1337 | ] 1338 | 1339 | [[package]] 1340 | name = "try-lock" 1341 | version = "0.2.3" 1342 | source = "registry+https://github.com/rust-lang/crates.io-index" 1343 | checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" 1344 | 1345 | [[package]] 1346 | name = "tungstenite" 1347 | version = "0.17.2" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" 1350 | dependencies = [ 1351 | "base64 0.13.1", 1352 | "byteorder", 1353 | "bytes", 1354 | "http", 1355 | "httparse", 1356 | "log", 1357 | "rand", 1358 | "rustls", 1359 | "sha-1", 1360 | "thiserror", 1361 | "url", 1362 | "utf-8", 1363 | "webpki", 1364 | ] 1365 | 1366 | [[package]] 1367 | name = "typemap_rev" 1368 | version = "0.1.5" 1369 | source = "registry+https://github.com/rust-lang/crates.io-index" 1370 | checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" 1371 | 1372 | [[package]] 1373 | name = "typenum" 1374 | version = "1.14.0" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 1377 | 1378 | [[package]] 1379 | name = "unicase" 1380 | version = "2.6.0" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 1383 | dependencies = [ 1384 | "version_check", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "unicode-bidi" 1389 | version = "0.3.7" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 1392 | 1393 | [[package]] 1394 | name = "unicode-ident" 1395 | version = "1.0.5" 1396 | source = "registry+https://github.com/rust-lang/crates.io-index" 1397 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 1398 | 1399 | [[package]] 1400 | name = "unicode-normalization" 1401 | version = "0.1.19" 1402 | source = "registry+https://github.com/rust-lang/crates.io-index" 1403 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 1404 | dependencies = [ 1405 | "tinyvec", 1406 | ] 1407 | 1408 | [[package]] 1409 | name = "untrusted" 1410 | version = "0.7.1" 1411 | source = "registry+https://github.com/rust-lang/crates.io-index" 1412 | checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" 1413 | 1414 | [[package]] 1415 | name = "url" 1416 | version = "2.2.2" 1417 | source = "registry+https://github.com/rust-lang/crates.io-index" 1418 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 1419 | dependencies = [ 1420 | "form_urlencoded", 1421 | "idna", 1422 | "matches", 1423 | "percent-encoding", 1424 | "serde", 1425 | ] 1426 | 1427 | [[package]] 1428 | name = "utf-8" 1429 | version = "0.7.6" 1430 | source = "registry+https://github.com/rust-lang/crates.io-index" 1431 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1432 | 1433 | [[package]] 1434 | name = "uwl" 1435 | version = "0.6.0" 1436 | source = "registry+https://github.com/rust-lang/crates.io-index" 1437 | checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" 1438 | 1439 | [[package]] 1440 | name = "valuable" 1441 | version = "0.1.0" 1442 | source = "registry+https://github.com/rust-lang/crates.io-index" 1443 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1444 | 1445 | [[package]] 1446 | name = "version_check" 1447 | version = "0.9.3" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 1450 | 1451 | [[package]] 1452 | name = "want" 1453 | version = "0.3.0" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 1456 | dependencies = [ 1457 | "log", 1458 | "try-lock", 1459 | ] 1460 | 1461 | [[package]] 1462 | name = "wasi" 1463 | version = "0.10.2+wasi-snapshot-preview1" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" 1466 | 1467 | [[package]] 1468 | name = "wasm-bindgen" 1469 | version = "0.2.78" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" 1472 | dependencies = [ 1473 | "cfg-if", 1474 | "wasm-bindgen-macro", 1475 | ] 1476 | 1477 | [[package]] 1478 | name = "wasm-bindgen-backend" 1479 | version = "0.2.78" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" 1482 | dependencies = [ 1483 | "bumpalo", 1484 | "lazy_static", 1485 | "log", 1486 | "proc-macro2", 1487 | "quote", 1488 | "syn", 1489 | "wasm-bindgen-shared", 1490 | ] 1491 | 1492 | [[package]] 1493 | name = "wasm-bindgen-futures" 1494 | version = "0.4.28" 1495 | source = "registry+https://github.com/rust-lang/crates.io-index" 1496 | checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" 1497 | dependencies = [ 1498 | "cfg-if", 1499 | "js-sys", 1500 | "wasm-bindgen", 1501 | "web-sys", 1502 | ] 1503 | 1504 | [[package]] 1505 | name = "wasm-bindgen-macro" 1506 | version = "0.2.78" 1507 | source = "registry+https://github.com/rust-lang/crates.io-index" 1508 | checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" 1509 | dependencies = [ 1510 | "quote", 1511 | "wasm-bindgen-macro-support", 1512 | ] 1513 | 1514 | [[package]] 1515 | name = "wasm-bindgen-macro-support" 1516 | version = "0.2.78" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" 1519 | dependencies = [ 1520 | "proc-macro2", 1521 | "quote", 1522 | "syn", 1523 | "wasm-bindgen-backend", 1524 | "wasm-bindgen-shared", 1525 | ] 1526 | 1527 | [[package]] 1528 | name = "wasm-bindgen-shared" 1529 | version = "0.2.78" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" 1532 | 1533 | [[package]] 1534 | name = "web-sys" 1535 | version = "0.3.55" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" 1538 | dependencies = [ 1539 | "js-sys", 1540 | "wasm-bindgen", 1541 | ] 1542 | 1543 | [[package]] 1544 | name = "webpki" 1545 | version = "0.22.0" 1546 | source = "registry+https://github.com/rust-lang/crates.io-index" 1547 | checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" 1548 | dependencies = [ 1549 | "ring", 1550 | "untrusted", 1551 | ] 1552 | 1553 | [[package]] 1554 | name = "webpki-roots" 1555 | version = "0.22.1" 1556 | source = "registry+https://github.com/rust-lang/crates.io-index" 1557 | checksum = "c475786c6f47219345717a043a37ec04cb4bc185e28853adcc4fa0a947eba630" 1558 | dependencies = [ 1559 | "webpki", 1560 | ] 1561 | 1562 | [[package]] 1563 | name = "winapi" 1564 | version = "0.3.9" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1567 | dependencies = [ 1568 | "winapi-i686-pc-windows-gnu", 1569 | "winapi-x86_64-pc-windows-gnu", 1570 | ] 1571 | 1572 | [[package]] 1573 | name = "winapi-i686-pc-windows-gnu" 1574 | version = "0.4.0" 1575 | source = "registry+https://github.com/rust-lang/crates.io-index" 1576 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1577 | 1578 | [[package]] 1579 | name = "winapi-x86_64-pc-windows-gnu" 1580 | version = "0.4.0" 1581 | source = "registry+https://github.com/rust-lang/crates.io-index" 1582 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1583 | 1584 | [[package]] 1585 | name = "windows-sys" 1586 | version = "0.34.0" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" 1589 | dependencies = [ 1590 | "windows_aarch64_msvc 0.34.0", 1591 | "windows_i686_gnu 0.34.0", 1592 | "windows_i686_msvc 0.34.0", 1593 | "windows_x86_64_gnu 0.34.0", 1594 | "windows_x86_64_msvc 0.34.0", 1595 | ] 1596 | 1597 | [[package]] 1598 | name = "windows-sys" 1599 | version = "0.48.0" 1600 | source = "registry+https://github.com/rust-lang/crates.io-index" 1601 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1602 | dependencies = [ 1603 | "windows-targets", 1604 | ] 1605 | 1606 | [[package]] 1607 | name = "windows-targets" 1608 | version = "0.48.0" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 1611 | dependencies = [ 1612 | "windows_aarch64_gnullvm", 1613 | "windows_aarch64_msvc 0.48.0", 1614 | "windows_i686_gnu 0.48.0", 1615 | "windows_i686_msvc 0.48.0", 1616 | "windows_x86_64_gnu 0.48.0", 1617 | "windows_x86_64_gnullvm", 1618 | "windows_x86_64_msvc 0.48.0", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "windows_aarch64_gnullvm" 1623 | version = "0.48.0" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 1626 | 1627 | [[package]] 1628 | name = "windows_aarch64_msvc" 1629 | version = "0.34.0" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" 1632 | 1633 | [[package]] 1634 | name = "windows_aarch64_msvc" 1635 | version = "0.48.0" 1636 | source = "registry+https://github.com/rust-lang/crates.io-index" 1637 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 1638 | 1639 | [[package]] 1640 | name = "windows_i686_gnu" 1641 | version = "0.34.0" 1642 | source = "registry+https://github.com/rust-lang/crates.io-index" 1643 | checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" 1644 | 1645 | [[package]] 1646 | name = "windows_i686_gnu" 1647 | version = "0.48.0" 1648 | source = "registry+https://github.com/rust-lang/crates.io-index" 1649 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 1650 | 1651 | [[package]] 1652 | name = "windows_i686_msvc" 1653 | version = "0.34.0" 1654 | source = "registry+https://github.com/rust-lang/crates.io-index" 1655 | checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" 1656 | 1657 | [[package]] 1658 | name = "windows_i686_msvc" 1659 | version = "0.48.0" 1660 | source = "registry+https://github.com/rust-lang/crates.io-index" 1661 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 1662 | 1663 | [[package]] 1664 | name = "windows_x86_64_gnu" 1665 | version = "0.34.0" 1666 | source = "registry+https://github.com/rust-lang/crates.io-index" 1667 | checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" 1668 | 1669 | [[package]] 1670 | name = "windows_x86_64_gnu" 1671 | version = "0.48.0" 1672 | source = "registry+https://github.com/rust-lang/crates.io-index" 1673 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 1674 | 1675 | [[package]] 1676 | name = "windows_x86_64_gnullvm" 1677 | version = "0.48.0" 1678 | source = "registry+https://github.com/rust-lang/crates.io-index" 1679 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 1680 | 1681 | [[package]] 1682 | name = "windows_x86_64_msvc" 1683 | version = "0.34.0" 1684 | source = "registry+https://github.com/rust-lang/crates.io-index" 1685 | checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" 1686 | 1687 | [[package]] 1688 | name = "windows_x86_64_msvc" 1689 | version = "0.48.0" 1690 | source = "registry+https://github.com/rust-lang/crates.io-index" 1691 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 1692 | 1693 | [[package]] 1694 | name = "winreg" 1695 | version = "0.7.0" 1696 | source = "registry+https://github.com/rust-lang/crates.io-index" 1697 | checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" 1698 | dependencies = [ 1699 | "winapi", 1700 | ] 1701 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustbot" 3 | license = "MIT/Apache-2.0" 4 | authors = [ "Conner Bradley " ] 5 | description = "A discord bot that runs whatever rust code is thrown at it" 6 | version = "0.1.0" 7 | edition = "2021" 8 | build = "build.rs" 9 | 10 | [lib] 11 | name = "rustbot" 12 | 13 | [dependencies] 14 | tracing = "0.1.37" 15 | tracing-subscriber = "0.3.16" 16 | tokio = { version = "1.16", features = ["macros", "signal", "rt-multi-thread"] } 17 | dotenv = { version = "0.15.0" } 18 | serenity = "0.11.5" 19 | regex = "1.7.3" 20 | base64 = "0.20.0" 21 | process_control = "4.0" 22 | cached = "0.42.0" 23 | lazy_static = "1.4.0" 24 | 25 | [package.metadata.generate-rpm] 26 | assets = [ 27 | { source = "target/x86_64-unknown-linux-musl/release/*rustbot", dest = "/opt/rustbot", mode = "0755" }, 28 | { source = "target/x86_64-unknown-linux-musl/release/assets/templates/*", dest = "/opt/rustbot/assets/templates", mode = "0644" }, 29 | { source = "LICENSE*", dest = "/opt/rustbot", doc = true, mode = "0644" }, 30 | { source = "package-readme.txt", dest = "/opt/rustbot/README.txt", doc = true, mode = "0644" } 31 | ] -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [2021] [Conner Bradley ] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2021] [Conner Bradley] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RustBot 2 | ![CheckAndLint](https://github.com/TheConner/RustBot/actions/workflows/check.yaml/badge.svg)   ![ReleaseBuild](https://github.com/TheConner/RustBot/actions/workflows/release.yaml/badge.svg) 3 | 4 |

5 | 6 |

7 | 8 | *Bot is still under development and not ready for production use* 9 | 10 | RustBot is a discord bot that executes whatever rust code you throw at it. In a nutshell, it is remote code execution as a service 😛. Some practical applications of a bot that executes whatever code you throw at it is for Code Golf on discord servers (this bot only does rust), or for educational purposes where you could show code examples in a conversation. 11 | 12 | For more information on how RustBot works, see our [internals](doc/internals.md) page. 13 | 14 | Future work for this bot includes: 15 | - **Multitenancy/Admin commands**: Allow admins to configure various settings, this would involve some form of database integration and adding additional contexts to bot messages. 16 | 17 | 18 | 19 | ## Configuration 20 | Want to run your own RustBot? Great! I only have instructions to get you started developing locally on your own machine. In the future I will provide instructions for server deployments. 21 | 22 | 1. You need [Podman](https://podman.io/) installed. 23 | 2. Clone this repo, and add the `.env` file with your token. See [our instructions](doc/discord.md) on how to make a token and add a bot to your server for local development 24 | ``` 25 | DISCORD_TOKEN="YOUR_TOKEN_HERE" 26 | ``` 27 | 3. Build the container by running `podman build -f Dockerfile_runner -t rustbot-runner:latest .` 28 | 4. OPTIONAL: if you are working on the container for rustbot itself, see the section on how to [build the container](doc/container.md) 29 | 5. Build and run this project with `cargo run` 30 | 31 | ### Environment Variables 32 | These can either by specified by a `.env` file, or by exposing them the regular way. 33 | | Name | Description | Required? | Default Value | 34 | |------|-------------|-----------|---------------| 35 | | `DISCORD_TOKEN` | Discord bot token | Required | | 36 | | `BOT_PREFIX` | Prefix to use for commands | Optional | `!` | 37 | | `MAX_CONTAINER_RUNTIME` | Max amount of milliseconds before the container is killed | Optional | 5000 | 38 | | `CONTAINER_CPU` | Max amount of CPU to delegate to the container | Optional | `0.5` | 39 | | `CONTAINER_MEMORY` | Max amount of memory available to the container | Optional | `100m` | 40 | | `CONTAINER_SWAP` | Max amount of swap available to the container | Optional | `5m` | 41 | | `CONTAINER_IMAGE` | Container image to use | Optional | Uses a local `rustbot-runner:latest` for dev builds, for release it uses the [ghcr.io container image](ghcr.io/theconner/rustbot-runner:latest) | 42 | | `IS_RUNNING_IN_CONTAINER` | Tells RustBot if it's running as a container | Optional | False by default, True by default for our [container builds](Dockerfile_bot) | 43 | 44 | ## Bot Commands 45 | More commands should be coming soon, here is what we support at the moment: 46 | 47 | - `!run`: runs arbitrary code. The command expects there to be a code block. 48 | 49 | For example: 50 | > !run 51 | > 52 | > \```rs 53 | > 54 | > fn main() { println!("Hello RustBot"); } 55 | > 56 | > \``` 57 | 58 | Or, without the language identifier: 59 | > !run 60 | > 61 | > \``` 62 | > 63 | > fn main() { println!("Hello RustBot"); } 64 | > 65 | > \``` 66 | 67 | The bot will react with `🔨` to indicate your code is building / being executed, a `✅` to indicate the run is successful, and `❌` to indicate something went wrong. A reply will be posted by the bot with the standard output of your code. For the above example the response would be 68 | 69 | > ``` 70 | > Hello RustBot 71 | > ``` 72 | 73 | For edge cases such as if a response is too long, the response will be truncated to fit Discord's max message length. 74 | 75 | - `!ping`: Checks if the bot is working. The bot will react to your message and respond with PONG. 76 | 77 | 78 | 79 | ## License 80 | 81 | Licensed under either of 82 | 83 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 84 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 85 | 86 | at your option. 87 | 88 | ### Contribution 89 | 90 | Unless you explicitly state otherwise, any contribution intentionally 91 | submitted for inclusion in the work by you, as defined in the Apache-2.0 92 | license, shall be dual licensed as above, without any additional terms or 93 | conditions. 94 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # /assets 2 | This folder contains all assets used by our bot. Right now, we have a folder full of canned responses that the bot can send for a variety of scenarios in `templates/`, some `container/` assets, and some `demo/` images used in the repo. 3 | 4 | *Why not put these canned template responses in code?* Good question. I think that these response could be customized depending on the deployment. With that in mind, should the user have to re-compile the bot to change the responses? I think not. It only adds minor complexity to the bot program. 5 | 6 | For help on writing / working with templates, see [templates documentation](../doc/templates.md) -------------------------------------------------------------------------------- /assets/container/trampoline: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Trampoline utility for decoding b64 commands and running them 4 | 5 | # USAGE: trampoline [code] [args] 6 | # where 7 | # [code] = base64 encoded code to be run 8 | # [args] = base64 args to run with code 9 | 10 | # Decode command from args 11 | cmd_base64=$1 12 | args_base64=$2 13 | 14 | # Construct program and run it depending on what [action] is provided 15 | echo ${cmd_base64} | base64 -d > program.rs 16 | runner program.rs $(echo ${args_base64} | base64 -d) -------------------------------------------------------------------------------- /assets/demo/rustbot_basic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheConner/RustBot/9ef06e71df6a9f4db4c0d8220f5cb2925cc79477/assets/demo/rustbot_basic.png -------------------------------------------------------------------------------- /assets/templates/help_help.md: -------------------------------------------------------------------------------- 1 | The help command will show a general list of commands if no command is provided. If you want to learn about a specific command, enter the command name without the prefix. For example, If you want to learn about the `help` command, enter `{{BOT_PREFIX}}help help`. -------------------------------------------------------------------------------- /assets/templates/help_ping.md: -------------------------------------------------------------------------------- 1 | The ping command will respond with PONG. Run `{{BOT_PREFIX}}ping` to get a PONG response. This is more of a debugging command to see if the bot is connected ok. -------------------------------------------------------------------------------- /assets/templates/help_run.md: -------------------------------------------------------------------------------- 1 | `{{BOT_PREFIX}}run`: Runs some rust code. The code you give should be in a code block (see below) with `rs` identifying the language. For example to run a simple hello world program you would type 2 | 3 | {{BOT_PREFIX}}run 4 | \```rs 5 | fn main() { 6 | println!("Hello shellbot!"); 7 | } 8 | \``` 9 | 10 | If you want to pass arguments to the program, those can be included after the run command. For example: 11 | 12 | {{BOT_PREFIX}}run hello world 13 | \```rs 14 | use std::env; 15 | 16 | fn main() { 17 | let args: Vec = env::args().collect(); 18 | println!("{:?}", args); 19 | } 20 | \``` -------------------------------------------------------------------------------- /assets/templates/help_running.md: -------------------------------------------------------------------------------- 1 | Sorry, I could not find any code to run in your message. For help on how to run rust code, please see `{{BOT_PREFIX}}help run` -------------------------------------------------------------------------------- /assets/templates/run_error_too_long.md: -------------------------------------------------------------------------------- 1 | Not sure what you're doing here, but this took too long to run -------------------------------------------------------------------------------- /assets/templates/run_no_output.md: -------------------------------------------------------------------------------- 1 | your program executed successfully, but there was no output :face_with_spiral_eyes: -------------------------------------------------------------------------------- /bot.Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # RustBot Docker Image 3 | # 4 | # This image is reponsible for running the bot itself 5 | # 6 | # We are using centos due to podman's relationship with red hat 7 | FROM quay.io/containers/podman:latest 8 | 9 | WORKDIR /home/podman 10 | 11 | # This assumes the release build is done and the RPM package is built 12 | COPY target/generate-rpm/rustbot-x86_64.rpm /home/podman/rustbot.rpm 13 | 14 | # Install RPM and remove install file 15 | RUN rpm -ivh rustbot.rpm; \ 16 | rm rustbot.rpm 17 | 18 | # Need this environment variable to tell rustbot it's inside a container 19 | # Not all features are supported within the container 20 | ENV IS_RUNNING_IN_CONTAINER="true" 21 | 22 | # Entrypoint for rustbot 23 | CMD /opt/rustbot/rustbot -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::Path; 3 | use std::process::Command; 4 | 5 | fn main() { 6 | let base_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 7 | println!("Base dir {}", base_dir); 8 | let profile = env::var("PROFILE").unwrap(); 9 | println!("Profile {}", profile); 10 | 11 | let release_dir = Path::new(&base_dir).join("target").join(profile); 12 | println!("Release dir {}", release_dir.to_str().unwrap()); 13 | 14 | let template_dir = release_dir.join("assets"); 15 | println!("Create output dir {}", template_dir.to_str().unwrap()); 16 | // create output directory for build 17 | Command::new("mkdir") 18 | .args(&["-p", template_dir.to_str().unwrap()]) 19 | .status() 20 | .unwrap(); 21 | 22 | // copy container assets to output directory 23 | Command::new("cp") 24 | .args(&["-r", "assets/templates", template_dir.to_str().unwrap()]) 25 | .status() 26 | .unwrap(); 27 | 28 | // Copy README for package 29 | Command::new("cp") 30 | .args(&[ 31 | "package-readme.txt", 32 | release_dir.join("README.txt").to_str().unwrap(), 33 | ]) 34 | .status() 35 | .unwrap(); 36 | 37 | // Copy licenses 38 | Command::new("cp") 39 | .args(&["LICENSE-MIT", release_dir.to_str().unwrap()]) 40 | .status() 41 | .unwrap(); 42 | 43 | Command::new("cp") 44 | .args(&["LICENSE-APACHE", release_dir.to_str().unwrap()]) 45 | .status() 46 | .unwrap(); 47 | } 48 | -------------------------------------------------------------------------------- /doc/container.md: -------------------------------------------------------------------------------- 1 | # RustBot Container 2 | 3 | We ship two container images: 4 | - `rustbot`: Responsible for hosting the bot itself 5 | - `rustbot-runner`: Responsible for running code that is passed to it, invoked by `rustbot` 6 | 7 | RustBot can be ran standalone as a user service, or it can be ran using the `rustbot` container image. Note that due to limitations of podman (which we use for rootless containers), we cannot impose resource restrictions to nested containers. In other words if a `rustbot-runner` is invoked by a `rustbot` container, we can't limit the amount of CPU and memory available to the runner container. If you run `rustbot` standalone, the CPU and memory limits will be applied. 8 | 9 | Running the RustBot container is very simple: 10 | ```bash 11 | podman run --security-opt label=disable \ 12 | --user podman \ 13 | --device /dev/fuse \ 14 | -e DISCORD_TOKEN="YOUR_TOKEN_HERE" \ 15 | ghcr.io/theconner/rustbot:latest 16 | ``` 17 | 18 | The above runs a rootless container that is capable of running nested rootless podman containers (ran whenever a `!run` command is received). For more information on nested containers using podman, see this excellent article on [how to use Podman inside of a container](https://www.redhat.com/sysadmin/podman-inside-container). 19 | 20 | ## Building the RustBot Container 21 | Here is how to build the RustBot container. Note that the container only uses release builds. 22 | 23 | 1. Make release build of project `cargo build --release` 24 | 2. Strip debug symbols from release `strip -s target/release/*rustbot` 25 | 3. Generate RPM `cargo generate-rpm` 26 | 4. Build container`podman build -f bot.Dockerfile -t rustbot:latest .` 27 | 28 | Then, to run locally, substitute `ghcr.io/theconner/rustbot:latest` with `localhost/rustbot:latest` -------------------------------------------------------------------------------- /doc/discord.md: -------------------------------------------------------------------------------- 1 | # Discord Instructions 2 | These instructions are a quick summary of what you need to do to get a Discord API token for the bot. 3 | 4 | 1. Log into the [Discord Developer Portal](https://discord.com/developers/applications) 5 | 2. Create a new application, name it whatever you wish. `rustbot-dev` for example 6 | 3. Go to the bot section, and then click *Add Bot* to create a bot user 7 | 4. Once the user is created, *copy* the token. This is your secret that authenticates the bot, do not leak it anywhere 8 | 5. Under authorization flow, make sure *public bot* is disabled so that other people can't add your bot to their servers. 9 | 10 | You now have a secret that can be used to authenticate your bot with discord. Next you need to add the bot to your own server: 11 | 6. Go to the oauth section, and then click on *url generator* 12 | 7. Under *scopes* click *bot* 13 | 8. Under the bot permissions section that pops up, select the following: 14 | - Send messages 15 | - Embed links 16 | - Read messages / view channels 17 | 9. Copy the generated URL and open it 18 | 10. Add the bot to your server 19 | 20 | And you should be off to the races! -------------------------------------------------------------------------------- /doc/internals.md: -------------------------------------------------------------------------------- 1 | ## Internals 2 | 3 | All code compilation and execution is done in a container, which runs in userspace via [Podman](https://podman.io/) instead of Docker. It's worth noting that containers are not shared, the container only lives for the lifespan of the `!run` command. 4 | 5 | When your `!run` command followed by rust code is received by the bot, the bot will extract your code from the message and then base64 encode it. We *could* be very pedantic about escaping your code when it gets transferred to the container to prevent breakouts; however, base64ing it does that for us for free. The encoded string is then ran by a [trampoline](assets/container/trampoline) in the container which handles decoding and running your program. 6 | 7 | The output of this would be the output of the rust program, which is ran by the helpful [runner](https://docs.rs/crate/runner/latest) which is great for running standalone rust files without scaffolding out a full project. 8 | 9 | To prevent DOS attacks on the platform, we have some policies to prevent code like this from eating up our resources: 10 | ```rs 11 | use std::{thread, time}; 12 | fn main() { 13 | // do nothing for a minute 14 | thread::sleep(time::Duration::from_millis(60000)); 15 | println!("Hello rustbot!"); 16 | } 17 | ``` 18 | Note that if you are using a podman-in-podman setup by running RustBot as a container, then the child containers spawned by RustBot cannot be limited -------------------------------------------------------------------------------- /doc/templates.md: -------------------------------------------------------------------------------- 1 | # Writing Templates 2 | 3 | Templates are files that RustBot reads to provide various canned messages to users. Templates are designed to be user-modifiable, such that any user can edit canned responses to tailor their deployment **without** re-compiling RustBot. A key aspect of templates is they support variable injection, since parts of RustBot may be modified through environment variables (e.g., BOT_PREFIX). 4 | 5 | An example template below: 6 | 7 | ```md 8 | You can run the run command by entering {{BOT_PREFIX}}run 9 | ``` 10 | When rendered is (assuming no BOT_PREFIX is set, resulting in the default of !) 11 | ``` 12 | You can run the run command by entering !run 13 | ``` -------------------------------------------------------------------------------- /package-readme.txt: -------------------------------------------------------------------------------- 1 | 2 | ██████╗ ██╗ ██╗███████╗████████╗██████╗ ██████╗ ████████╗ 3 | ██╔══██╗██║ ██║██╔════╝╚══██╔══╝██╔══██╗██╔═══██╗╚══██╔══╝ 4 | ██████╔╝██║ ██║███████╗ ██║ ██████╔╝██║ ██║ ██║ 5 | ██╔══██╗██║ ██║╚════██║ ██║ ██╔══██╗██║ ██║ ██║ 6 | ██║ ██║╚██████╔╝███████║ ██║ ██████╔╝╚██████╔╝ ██║ 7 | ╚═╝ ╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ 8 | 9 | --- Instructions --- 10 | Thank you for using RustBot! To spin up your own RustBot instance, ensure you 11 | have the following dependencies installed: 12 | 13 | 1. Podman https://podman.io/ for containerization 14 | 2. A Discord API token set up with the following permissions 15 | - Embed links 16 | - Read Messages / View Channels 17 | - Send messages 18 | 3. When running RustBot, the secret from (2) can be exposed to the bot 19 | through the DISCORD_TOKEN environment variable 20 | 4. *Coming Soon* you can run RustBot as a user space service 21 | 22 | For full documentation of all environment variables and configuration 23 | settings, please refer to the project README on github: 24 | https://github.com/TheConner/RustBot/blob/main/README.md 25 | 26 | --- License --- 27 | Licensed under either of 28 | 29 | Apache License, Version 2.0, (LICENSE-APACHE or 30 | http://www.apache.org/licenses/LICENSE-2.0) 31 | MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) 32 | 33 | at your option. 34 | 35 | Copyright [2021] [Conner Bradley ] -------------------------------------------------------------------------------- /runner.Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # This image is responsible for running commands passed by the bot 3 | # it is invoked by the bot for one-time usage 4 | # 5 | FROM rust:alpine 6 | 7 | # Need dev utils for alpine 8 | RUN apk add --no-cache musl-dev 9 | 10 | # Need the rust runner utility 11 | RUN cargo install runner 12 | 13 | # Set up our user with restricted bash (rbash) 14 | RUN adduser --disabled-password \ 15 | --shell /bin/sh \ 16 | --home /home/rustbot/ rustbot 17 | USER rustbot 18 | WORKDIR /home/rustbot 19 | COPY assets/container/trampoline /usr/bin/trampoline -------------------------------------------------------------------------------- /src/commands/help.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use rustbot::util::configuration::get_bot_prefix; 3 | use rustbot::util::template::template_reader; 4 | /// Bot help 5 | use serenity::framework::standard::macros::command; 6 | use serenity::framework::standard::CommandResult; 7 | use serenity::model::prelude::Message; 8 | use serenity::prelude::Context; 9 | 10 | use tracing::{error, info}; 11 | 12 | /// 13 | /// Shows a generic help response 14 | async fn show_generic_help(ctx: &Context, msg: &Message, bot_prefix: String) -> CommandResult { 15 | info!("Responding with generic help embed"); 16 | // Show regular help 17 | let msg = msg.channel_id.send_message(&ctx.http, |m| { 18 | m.embed(|e| { 19 | e.title("RustBot Help"); 20 | e.description(format!("For more information on each command, you can use `{}help` command for a more specific description", bot_prefix)); 21 | e.field(format!("{}run", bot_prefix), "Runs Rust code that you provide and responds to you with the output", false); 22 | e.field(format!("{}script", bot_prefix), "Runs rust script that you provide and responds to you with the output", false); 23 | e.field(format!("{}ping", bot_prefix), "Sanity check to test if the bot is active", false); 24 | e.field(format!("{}help", bot_prefix), "Shows this help page", false); 25 | 26 | e 27 | }); 28 | m 29 | }) 30 | .await; 31 | if let Err(why) = msg { 32 | error!("Error sending message: {:?}", why); 33 | } 34 | 35 | Ok(()) 36 | } 37 | 38 | #[command] 39 | pub async fn help(ctx: &Context, msg: &Message) -> CommandResult { 40 | info!("PING command from {}", msg.author.name); 41 | 42 | let bot_prefix = get_bot_prefix(); 43 | let re = Regex::new(r"!help (.*)").unwrap(); 44 | // input: "!help run" 45 | // matches whole string 46 | // capture group 1: run 47 | let captures = re.captures(msg.content.as_str()); 48 | 49 | match captures { 50 | Some(cmd_capture) => { 51 | match cmd_capture.get(1).map(|m| String::from(m.as_str())) { 52 | Some(cmd) => { 53 | // We have a command to show help for 54 | let cmd_help = template_reader(format!("help_{}", cmd).as_str()); 55 | info!("Rendering help for {}", cmd); 56 | match cmd_help { 57 | Some(help_text) => { 58 | // Render help text 59 | let msg = msg 60 | .channel_id 61 | .send_message(&ctx.http, |m| { 62 | m.embed(|e| { 63 | e.title(format!("{}{} help", bot_prefix, cmd)); 64 | e.description(help_text); 65 | 66 | e 67 | }); 68 | m 69 | }) 70 | .await; 71 | if let Err(why) = msg { 72 | println!("Error sending message: {:?}", why); 73 | } 74 | } 75 | None => { 76 | error!("Could not find information for command {}", cmd); 77 | msg.reply( 78 | &ctx.http, 79 | "Sorry, I could not find help information for that command.", 80 | ) 81 | .await?; 82 | } 83 | } 84 | } 85 | None => { 86 | show_generic_help(ctx, msg, bot_prefix).await?; 87 | } 88 | } 89 | } 90 | None => { 91 | show_generic_help(ctx, msg, bot_prefix).await?; 92 | } 93 | } 94 | 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /src/commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod help; 2 | pub mod ping; 3 | pub mod run; 4 | -------------------------------------------------------------------------------- /src/commands/ping.rs: -------------------------------------------------------------------------------- 1 | /// 2 | /// Boring ping command for sanity testing 3 | /// 4 | use serenity::framework::standard::macros::command; 5 | use serenity::framework::standard::CommandResult; 6 | use serenity::model::prelude::Message; 7 | use serenity::prelude::Context; 8 | 9 | use rustbot::constants::CHECK_MARK_EMOJI; 10 | 11 | use tracing::info; 12 | 13 | #[command] 14 | pub async fn ping(ctx: &Context, msg: &Message) -> CommandResult { 15 | info!("PING command from {}", msg.author.name); 16 | msg.react(&ctx.http, CHECK_MARK_EMOJI).await?; 17 | msg.channel_id.say(&ctx.http, "PONG").await?; 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/run.rs: -------------------------------------------------------------------------------- 1 | use rustbot::constants::{CHECK_MARK_EMOJI, CLOCK_EMOJI, CROSS_MARK_EMOJI, HAMMER_EMOJI}; 2 | use rustbot::util::command::{build_container_command, extract_code, run_command_with_timeout}; 3 | use rustbot::util::configuration::get_container_runtime; 4 | use rustbot::util::template::template_reader; 5 | use serenity::framework::standard::macros::command; 6 | use serenity::framework::standard::CommandResult; 7 | use serenity::model::prelude::Message; 8 | use serenity::prelude::Context; 9 | use std::io::ErrorKind::TimedOut; 10 | use tracing::{debug, info}; 11 | 12 | /// Given some stdout or stderr data, format it so that it can be rendered by discord 13 | fn response_formatter(response: String) -> String { 14 | debug!("Format response \"{}\"", response); 15 | if response.len() < 1990 { 16 | // Response falls within size constraints 17 | return format!("```\n{}\n```", response); 18 | } else { 19 | // we trim to 1981 chars because [TRIMMED] is 9 chars 20 | let short_repsonse = &response[0..1981]; // TODO: maybe do this in place with a mutable string 21 | return format!("```{}[TRIMMED]```", short_repsonse); 22 | } 23 | } 24 | 25 | #[command] 26 | pub async fn run(ctx: &Context, msg: &Message) -> CommandResult { 27 | let code = extract_code(&msg.content); 28 | let code_author = &msg.author.name; 29 | info!("Running message from {}", msg.author.name); 30 | // TODO: tidy this up 31 | match code { 32 | Some(c) => { 33 | msg.react(ctx, HAMMER_EMOJI).await?; 34 | 35 | // With the code matched, we have to b64 encode it to be sent to the container 36 | // the `payload` will then be encoded and decoded inside the container in a similar fashion to the original ShellBot 37 | let encoded_program = base64::encode(c.code.unwrap_or_else(|| String::from(""))); 38 | let encoded_args = base64::encode(c.args.unwrap_or_else(|| String::from(""))); 39 | let payload = build_container_command( 40 | format!("trampoline {} {}", encoded_program, encoded_args).as_str(), 41 | ); 42 | 43 | debug!("Trampoline Payload \"{}\"", payload); 44 | 45 | let cmd_result = 46 | run_command_with_timeout(payload.as_str(), get_container_runtime()).await; 47 | 48 | match cmd_result { 49 | Ok(output) => { 50 | let mut stdout = String::new(); 51 | let mut stderr = String::new(); 52 | 53 | if !output.stdout.is_empty() { 54 | stdout = match String::from_utf8(output.stdout) { 55 | Ok(v) => v, 56 | Err(e) => panic!("Invalid UTF-8 sequence: {}", e), 57 | }; 58 | 59 | debug!("Got stdout\n\"{}\"", stdout); 60 | } else { 61 | debug!("No stdout"); 62 | } 63 | 64 | if !output.stderr.is_empty() { 65 | stderr = match String::from_utf8(output.stderr) { 66 | Ok(v) => v, 67 | Err(e) => panic!("Invalid UTF-8 sequence: {}", e), 68 | }; 69 | debug!("Got stderr \"{}\"", stderr); 70 | } else { 71 | debug!("No stderr"); 72 | } 73 | 74 | // Check to see if the response was nothing 75 | if !stdout.is_empty() && stderr.is_empty() { 76 | debug!("Response: has stdout, no stderr"); 77 | msg.react(&ctx, CHECK_MARK_EMOJI).await?; 78 | msg.reply(&ctx, response_formatter(stdout)).await?; 79 | } else if stdout.is_empty() && !stderr.is_empty() { 80 | debug!("Response: no stdout, has stderr"); 81 | // Had stderr, no stdout 82 | msg.react(&ctx, CROSS_MARK_EMOJI).await?; 83 | msg.reply(&ctx, response_formatter(stderr)).await?; 84 | } else { 85 | debug!("Response: no stdout, no stderr"); 86 | msg.react(&ctx, CHECK_MARK_EMOJI).await?; 87 | msg.reply( 88 | &ctx, 89 | template_reader("run_no_output") 90 | .expect("Could not read template run_no_output"), 91 | ) 92 | .await?; 93 | } 94 | } 95 | Err(error) => { 96 | // TODO: find out ways this can blow up 97 | info!("TIMEOUT on {}'s code", code_author); 98 | match error.kind() { 99 | TimedOut => { 100 | // Took too long to run, complain to user 101 | let response = template_reader("run_error_too_long") 102 | .expect("Could not read template run_error_too_long"); 103 | msg.react(&ctx, CROSS_MARK_EMOJI).await?; 104 | msg.react(&ctx, CLOCK_EMOJI).await?; 105 | msg.reply(&ctx, response).await?; 106 | } 107 | _ => { 108 | msg.react(ctx, CROSS_MARK_EMOJI).await?; 109 | info!("Handled error {}", error) 110 | } 111 | } 112 | } 113 | } 114 | } 115 | None => { 116 | info!("NO CODE MATCH on {}'s message", code_author); 117 | // No code matched 118 | // show the help text 119 | let response = 120 | template_reader("help_running").expect("Could not read template help_running"); 121 | msg.react(ctx, CROSS_MARK_EMOJI).await?; 122 | msg.reply(ctx, response).await?; 123 | } 124 | }; 125 | 126 | Ok(()) 127 | } 128 | -------------------------------------------------------------------------------- /src/constants.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | /// The ✅ emoji code in discord - used to indicate everything's peachy 4 | pub const CHECK_MARK_EMOJI: char = '✅'; 5 | 6 | /// The emoji code in discord - used to indicate something blew up 7 | pub const CROSS_MARK_EMOJI: char = '❌'; 8 | 9 | /// The 🔨 emoji code in discord - used to indicate compilation started 10 | pub const HAMMER_EMOJI: char = '🔨'; 11 | 12 | /// The ⏰ emoji in discord - used to indicate the command has timed out 13 | pub const CLOCK_EMOJI: char = '⏰'; 14 | 15 | /// Environment variable for our bot token 16 | pub const ENV_BOT_TOKEN: &str = "DISCORD_TOKEN"; 17 | 18 | /// Environment variable for max container runtime 19 | pub const ENV_MAX_RUNTIME: &str = "MAX_CONTAINER_RUNTIME"; 20 | pub const DEFAULT_CONTAINER_RUNTIME: u64 = 5000; 21 | 22 | /// Environment variable for bot prefix 23 | pub const ENV_BOT_PREFIX: &str = "BOT_PREFIX"; 24 | pub const DEFAULT_PREFIX: &str = "!"; 25 | 26 | /// Base path to look for templates 27 | pub const TEMPLATE_BASE_PATH: &str = "assets/templates"; 28 | 29 | // --------------------------- // 30 | // CONTAINER RESOURCE SETTINGS // 31 | // --------------------------- // 32 | pub const ENV_CONTAINER_IMAGE: &str = "CONTAINER_IMAGE"; 33 | pub const DEFAULT_CONTAINER_IMAGE: &str = "ghcr.io/theconner/rustbot-runner:latest"; // Used only on release build 34 | pub const DEFAULT_LOCAL_CONTAINER_IMAGE: &str = "rustbot-runner:latest"; 35 | 36 | pub const ENV_CONTAINER_CPU: &str = "CONTAINER_CPU"; 37 | pub const DEFAULT_CONTAINER_CPU: &str = "0.5"; // you get 1/2 of a cpu, i'm being generous 38 | 39 | // TODO: add settings for CPU scheduler 40 | // although, i'm unsure if podman supports userspace containers with different schedulers 41 | pub const ENV_CONTAINER_MEMORY: &str = "CONTAINER_MEMORY"; 42 | pub const DEFAULT_CONTAINER_MEMORY: &str = "100m"; 43 | 44 | pub const ENV_CONTAINER_SWAP: &str = "CONTAINER_SWAP"; 45 | pub const DEFAULT_CONTAINER_SWAP: &str = "5m"; 46 | 47 | // Tells RustBot if it's running in a container 48 | // this will influence flags it chooses for child containers 49 | // available values: false,true 50 | pub const ENV_IS_RUNNING_IN_CONTAINER: &str = "IS_RUNNING_IN_CONTAINER"; 51 | pub const DEFAULT_IS_RUNNING_IN_CONTAINER: bool = false; 52 | 53 | // DEFAULT SETTINGS HASH MAP 54 | lazy_static! { 55 | pub static ref DEFAULT_SETTINGS: HashMap<&'static str, &'static str> = { 56 | let mut m = HashMap::new(); 57 | m.insert(ENV_BOT_TOKEN, "NO_TOKEN_PROVIDED"); 58 | m.insert(ENV_BOT_PREFIX, DEFAULT_PREFIX); 59 | m.insert(ENV_CONTAINER_IMAGE, DEFAULT_CONTAINER_IMAGE); 60 | m.insert(ENV_CONTAINER_CPU, DEFAULT_CONTAINER_CPU); 61 | m.insert(ENV_CONTAINER_MEMORY, DEFAULT_CONTAINER_MEMORY); 62 | m.insert(ENV_CONTAINER_SWAP, DEFAULT_CONTAINER_SWAP); 63 | m 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate lazy_static; 3 | 4 | ///! Helper libraries used by RustBot 5 | pub mod constants; 6 | pub mod model; 7 | pub mod util; 8 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // RustBot main entry point 2 | use serenity::{ 3 | async_trait, 4 | client::bridge::gateway::ShardManager, 5 | framework::standard::{macros::group, StandardFramework}, 6 | http::Http, 7 | model::{event::ResumedEvent, gateway::Ready}, 8 | prelude::*, 9 | }; 10 | use std::{collections::HashSet, process, sync::Arc}; 11 | use tracing::{error, info, instrument}; 12 | 13 | use rustbot::constants::ENV_BOT_TOKEN; 14 | use rustbot::util::command::pull_latest_container_image; 15 | use rustbot::util::configuration::*; 16 | 17 | mod commands; 18 | use commands::help::*; 19 | use commands::ping::*; 20 | use commands::run::*; 21 | 22 | pub struct ShardManagerContainer; 23 | 24 | impl TypeMapKey for ShardManagerContainer { 25 | type Value = Arc>; 26 | } 27 | 28 | struct Handler; 29 | 30 | #[async_trait] 31 | impl EventHandler for Handler { 32 | async fn ready(&self, _: Context, ready: Ready) { 33 | info!("Connected as {}", ready.user.name); 34 | } 35 | 36 | async fn resume(&self, _: Context, _: ResumedEvent) { 37 | info!("Resumed"); 38 | } 39 | } 40 | 41 | #[group] 42 | #[commands(ping, run, help)] 43 | struct General; 44 | 45 | #[tokio::main] 46 | #[instrument] 47 | async fn main() { 48 | tracing_subscriber::fmt::init(); 49 | info!("RustBot: starting up"); 50 | 51 | // This will load the environment variables located at `./.env`, relative to 52 | // the CWD. See `./.env.example` for an example on how to structure this. 53 | dotenv::dotenv().ok(); 54 | 55 | // Initialize the logger to use environment variables. 56 | // 57 | // In this case, a good default is setting the environment variable 58 | // `RUST_LOG` to `debug`. 59 | //tracing_subscriber::fmt::init(); 60 | 61 | let token = get_bot_token(); 62 | 63 | if token.is_empty() { 64 | error!( 65 | "ERROR: no token provided! Please set the {} environment variable", 66 | ENV_BOT_TOKEN 67 | ); 68 | process::exit(0x0100); 69 | } 70 | 71 | // Pull the container image in advance, fail if it fails 72 | if !is_debug() { 73 | let container_pull_result = pull_latest_container_image().await; 74 | match container_pull_result { 75 | Ok(_res) => info!("Container pull OK"), 76 | Err(why) => { 77 | error!("Could not pull container image, got error code {}", why); 78 | process::exit(0x0100); 79 | } 80 | }; 81 | } 82 | 83 | let http = Http::new(&token); 84 | 85 | // We will fetch your bot's owners and id 86 | let (owners, _bot_id) = match http.get_current_application_info().await { 87 | Ok(info) => { 88 | let mut owners = HashSet::new(); 89 | if let Some(team) = info.team { 90 | owners.insert(team.owner_user_id); 91 | } else { 92 | owners.insert(info.owner.id); 93 | } 94 | match http.get_current_user().await { 95 | Ok(bot_id) => (owners, bot_id.id), 96 | Err(why) => panic!("Could not access the bot id: {:?}", why), 97 | } 98 | } 99 | Err(why) => panic!("Could not access application info: {:?}", why), 100 | }; 101 | 102 | // Create the framework 103 | let framework = StandardFramework::new() 104 | .configure(|c| c.prefix(get_bot_prefix().as_str()).owners(owners)) 105 | .group(&GENERAL_GROUP); 106 | 107 | let intents = GatewayIntents::GUILD_MESSAGES 108 | | GatewayIntents::GUILD_MESSAGE_REACTIONS 109 | | GatewayIntents::MESSAGE_CONTENT; 110 | 111 | let mut client = Client::builder(&token, intents) 112 | .framework(framework) 113 | .event_handler(Handler) 114 | .await 115 | .expect("Err creating client"); 116 | { 117 | info!("Setting up shard manager"); 118 | let mut data = client.data.write().await; 119 | data.insert::(client.shard_manager.clone()); 120 | } 121 | 122 | { 123 | let mut data = client.data.write().await; 124 | data.insert::(Arc::clone(&client.shard_manager)); 125 | } 126 | 127 | if let Err(why) = client.start().await { 128 | println!("Client error: {:?}", why); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/model/container.rs: -------------------------------------------------------------------------------- 1 | /// Settings for our container 2 | #[derive(Clone)] 3 | pub struct ContainerSettings { 4 | pub cpu: String, 5 | pub memory: String, 6 | pub swap: String, 7 | pub image: String, 8 | } 9 | 10 | pub trait RuntimeSettings { 11 | fn generate_runtime_flags(&self, is_container: bool) -> String; 12 | } 13 | 14 | impl RuntimeSettings for ContainerSettings { 15 | /// Turns a ContainerSettings instance into a string of CLI args for Podman or Docker 16 | /// is_container: describes if we are running rustbot in a container 17 | fn generate_runtime_flags(&self, is_container: bool) -> String { 18 | // BUG: when swap is included, we get a OCI runtime error as memory+swap is greater than configured memory 19 | // fix and re-add swap constraint 20 | // NOTE: podman-in-podman requires cgroups to set resources, which isn't available within nested containers 21 | // so, admins will have to limit the resources on the outer container themselves 22 | if is_container { 23 | String::from("") 24 | } else { 25 | format!("--cpus={} --memory={}", self.cpu, self.memory) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/model/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod container; 2 | -------------------------------------------------------------------------------- /src/util/command.rs: -------------------------------------------------------------------------------- 1 | use crate::model::container::RuntimeSettings; 2 | use crate::util::configuration::{get_container_settings, is_container}; 3 | use process_control::{ChildExt, Control, Output}; 4 | use regex::Regex; 5 | use std::io; 6 | use std::io::Error; 7 | use std::process::Command; 8 | use std::process::Stdio; 9 | use std::time::Duration; 10 | 11 | pub struct CodeExtraction { 12 | pub code: Option, 13 | pub args: Option, 14 | } 15 | 16 | /// Extracts source code from a command message. 17 | /// If no code is extractable, None is returned 18 | /// Otherwise, you will get Some string 19 | pub fn extract_code(text: &str) -> Option { 20 | // 21 | // The regex I want to use is (?<=```)(rs|.*)((.|\n)*)(?=```) 22 | // but no lookahead or lookbehind support in rust's regex lib 23 | // which is TRASH (kidding) 24 | // 25 | // The regex I'm stuck with is !run(.*)\n```(rs|.*)((.|\n)*)``` which 26 | // would require more massaging after processing. 27 | // it will have to do until the regex maintainers realize that 28 | // these "feature limitations" put rust's regex engine on par 29 | // with the same one used in the Safari web browser (also trash) 30 | // 31 | let re = Regex::new(r"!run(.*)\n```(rs|.*)((.|\n)*)```").unwrap(); 32 | let captures_opt = re.captures(text); 33 | 34 | captures_opt.as_ref()?; 35 | 36 | // Have matches, return some 37 | let captures = captures_opt.unwrap(); 38 | let argument_capture = captures.get(1); 39 | let code_capture = captures.get(3); 40 | Some(CodeExtraction { 41 | code: code_capture.map(|m| String::from(m.as_str())), 42 | args: argument_capture.map(|m| String::from(m.as_str())), 43 | }) 44 | } 45 | 46 | /// Builds a command to invoke our container with a command (cmd) 47 | pub fn build_container_command(cmd: &str) -> String { 48 | let container_settings = get_container_settings(); 49 | format!( 50 | "podman run --rm {} {} {}", 51 | container_settings.generate_runtime_flags(is_container()), 52 | container_settings.image, 53 | cmd 54 | ) 55 | } 56 | 57 | /// Provides a uniform way of running a command with a timeout 58 | pub async fn run_command_with_timeout(cmd: &str, timeout: u64) -> Result { 59 | // Because std::command does not give me the ability to override / modify 60 | // how arguments are escaped I have to do some stupid hack to make this 61 | // work. For example, if I wanted to run 62 | // podman run rustbot:latest ls -al 63 | // this would be impossible if I did 64 | // 65 | // std::process::Command::new("podman") 66 | // .args(["run", "rustbot:latest", "ls -al"]) 67 | // .output() 68 | // .expect("failed to invoke container"); 69 | // 70 | // As the ls -al would be quoted, and the container would try to execute 71 | // `ls -al` which would fail. The alternative is to seperate "ls", "-al" 72 | // which would also fail as the container would run `ls` then `-al` 73 | // ... what a stupid design 74 | // So instead of embracing the safety this API gives you, i'm just invoking 75 | // a shell with a payload I deem as safe 76 | let process = Command::new("sh") 77 | .args(["-c", cmd]) 78 | .stdout(Stdio::piped()) 79 | .stderr(Stdio::piped()) 80 | .spawn()?; 81 | 82 | let output = process 83 | .controlled_with_output() 84 | .time_limit(Duration::from_millis(timeout)) 85 | .terminate_for_timeout() 86 | .wait()? 87 | .ok_or_else(|| io::Error::new(io::ErrorKind::TimedOut, "Process timed out")); 88 | 89 | output 90 | } 91 | 92 | pub async fn pull_latest_container_image() -> Result<(), Error> { 93 | let container_settings = get_container_settings(); 94 | let output = Command::new("podman") 95 | .arg("pull") 96 | .arg(container_settings.image) 97 | .status() 98 | .expect("failed to execute process"); 99 | 100 | let status = output.code().expect("No output code"); 101 | 102 | if status == 0 { 103 | Ok(()) 104 | } else { 105 | Result::Err(io::Error::new( 106 | io::ErrorKind::Other, 107 | format!( 108 | "Could not pull docker image, got error code {} from podman", 109 | status 110 | ), 111 | )) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/util/configuration.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::*; 2 | use crate::model::container::ContainerSettings; 3 | use cached::proc_macro::cached; 4 | /// Configuration management for various settings 5 | /// The idea behind this is this design can easily be adapted to hook up to a database for multitenancy 6 | use std::env; 7 | 8 | /// Gets a environment configuration string value with a given key 9 | /// If no value is found in environment variables, return default value 10 | pub fn get_str_config_with_default(key: &str) -> String { 11 | let conf = env::var(key); 12 | conf.unwrap_or_else(|_| { 13 | String::from( 14 | *DEFAULT_SETTINGS 15 | .get(key) 16 | .unwrap_or(&"ERROR: NO DEFAULT PROVIDED"), 17 | ) 18 | }) 19 | } 20 | 21 | /// Gets a environment configuration int value with a given key 22 | /// If no value is found in environment variables, return default value 23 | fn get_u64_config_with_default(key: &str, default: u64) -> u64 { 24 | let conf = env::var(key); 25 | match conf { 26 | Ok(val) => val.parse::().unwrap_or(default), 27 | Err(_e) => default, 28 | } 29 | } 30 | 31 | /// Gets a environment configuration bool with a given key 32 | /// If no value is found in environment variables, return default value 33 | fn get_bool_config_with_default(key: &str, default: bool) -> bool { 34 | let conf = env::var(key); 35 | match conf { 36 | Ok(val) => val.parse::().unwrap_or(default), 37 | Err(_e) => default, 38 | } 39 | } 40 | 41 | /// maybe abstract all this as a struct, provide trait/impl methods to access various fields? 42 | /// this way I don't have to make a method for each field 43 | /// ... or maybe that's just overengineered here 44 | 45 | /// This is conditionally compiled if we are in debug mode (local development) 46 | /// For local development, return true 47 | #[cfg(debug_assertions)] 48 | pub fn is_debug() -> bool { 49 | true 50 | } 51 | 52 | /// This is conditionally compiled if we are in debug mode (local development) 53 | /// For local development, return true 54 | #[cfg(not(debug_assertions))] 55 | pub fn is_debug() -> bool { 56 | false 57 | } 58 | 59 | #[cached] 60 | pub fn get_bot_prefix() -> String { 61 | get_str_config_with_default(ENV_BOT_PREFIX) 62 | } 63 | 64 | #[cached] 65 | pub fn get_bot_token() -> String { 66 | get_str_config_with_default(ENV_BOT_TOKEN) 67 | } 68 | 69 | #[cached] 70 | pub fn get_container_runtime() -> u64 { 71 | get_u64_config_with_default(ENV_MAX_RUNTIME, DEFAULT_CONTAINER_RUNTIME) 72 | } 73 | 74 | /// Returns the container image to use depending on if we are doing local development 75 | /// or if we are running a relase build 76 | fn get_container_image() -> String { 77 | get_str_config_with_default(ENV_CONTAINER_IMAGE) 78 | } 79 | 80 | #[cached] 81 | pub fn get_container_settings() -> ContainerSettings { 82 | ContainerSettings { 83 | cpu: get_str_config_with_default(ENV_CONTAINER_CPU), 84 | memory: get_str_config_with_default(ENV_CONTAINER_MEMORY), 85 | swap: get_str_config_with_default(ENV_CONTAINER_SWAP), 86 | image: get_container_image(), 87 | } 88 | } 89 | 90 | /// Returns true if RustBot is running in a container 91 | #[cached] 92 | pub fn is_container() -> bool { 93 | get_bool_config_with_default(ENV_IS_RUNNING_IN_CONTAINER, DEFAULT_IS_RUNNING_IN_CONTAINER) 94 | } 95 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod command; 2 | pub mod configuration; 3 | pub mod template; 4 | -------------------------------------------------------------------------------- /src/util/template.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::{ENV_BOT_TOKEN, TEMPLATE_BASE_PATH}; 2 | use crate::util::configuration::get_str_config_with_default; 3 | use cached::proc_macro::cached; 4 | use regex::Regex; 5 | 6 | use std::collections::HashMap; 7 | use std::fs; 8 | use std::path::{Path, PathBuf}; 9 | 10 | use tracing::debug; 11 | 12 | /// TODO: 13 | /// - Template caching [DONE / WIP] 14 | /// - Template variable injection 15 | 16 | /// Builds a full template path from a template name 17 | fn get_template_path(template_name: &str) -> PathBuf { 18 | Path::new(TEMPLATE_BASE_PATH).join(format!("{}.md", template_name)) 19 | } 20 | 21 | /// Checks if an interpolation key is allowed, right now it will only return 22 | /// false if you try to use the bot token variable as a template 23 | /// interpolation, which is not allowed as why would you want to leak the bot 24 | /// secret to users? 25 | /// 26 | /// In the future this should have a list of allowed values 27 | fn is_interpolation_key_allowed(key: &str) -> bool { 28 | if key.eq(ENV_BOT_TOKEN) { 29 | // Cannot leak bot secret in template 30 | return false; 31 | } 32 | true 33 | } 34 | 35 | /// Gets a vector of interpolation keywords found in the template 36 | /// For example, if template is 37 | /// Hello {{NAME}} would you like a {{ITEM}} 38 | /// This function will return ['{{NAME}}','{{ITEM}}'] 39 | fn get_interpolations(template: &str) -> HashMap { 40 | let re = Regex::new(r"\{\{(.*)\}\}").unwrap(); 41 | let mut interpolations: HashMap = HashMap::new(); 42 | 43 | // Check to see if we should bother with the rest of this 44 | if !re.is_match(template) { 45 | debug!("No interpolations in template, returning"); 46 | return interpolations; 47 | } 48 | 49 | for cap in re.captures_iter(template) { 50 | // there must be a less nasty way to do this... 51 | let template_sub: &str = cap.get(1).map(|m| m.as_str()).unwrap(); 52 | if !interpolations.contains_key(template_sub) && is_interpolation_key_allowed(template_sub) 53 | { 54 | interpolations.insert( 55 | // What a stupid escape system 56 | // two {{ = one escaped { 57 | // so if I want to format `TEST` as `{{TEST}}` it looks 58 | // like this `{{{{{}}}}}` 59 | format!("{{{{{}}}}}", template_sub), 60 | get_str_config_with_default(template_sub), 61 | ); 62 | } 63 | } 64 | 65 | interpolations 66 | } 67 | 68 | fn do_interpolations(template: String, interpolations: HashMap) -> String { 69 | interpolations 70 | .iter() 71 | .fold(template, |s, (from, to)| s.replace(from, to)) 72 | } 73 | 74 | /// Reads a template file with name `template_name` 75 | /// Returns the template content as a string 76 | #[cached] 77 | fn read_template(template_name: String) -> String { 78 | debug!("Reading template {}", template_name); 79 | let path = get_template_path(template_name.as_str()); 80 | let mut template = fs::read_to_string(path).expect("Unable to read file"); 81 | let interpolations = get_interpolations(&template); 82 | if !interpolations.is_empty() { 83 | debug!("Found interpolations"); 84 | template = do_interpolations(template, interpolations); 85 | } 86 | template 87 | } 88 | 89 | /// Reads a template file with name `template_name` 90 | /// Returns the template content as a string 91 | pub fn template_reader(template_name: &str) -> Option { 92 | // note: this function exists as a proxy to the cached version of read_template 93 | // we can't cache functions with &str values, but we can cache methods with string input 94 | let template_file = get_template_path(template_name); 95 | if Path::new(&template_file).exists() { 96 | debug!("Found template file"); 97 | // Path exists, read template and return 98 | Some(read_template(template_name.to_string())) 99 | } else { 100 | debug!( 101 | "No template file {}", 102 | template_file.into_os_string().into_string().unwrap() 103 | ); 104 | None 105 | } 106 | } 107 | --------------------------------------------------------------------------------