├── .env.example ├── .github └── workflows │ ├── ci.yml │ └── docker.yml ├── .gitignore ├── .gitmodules ├── .ignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── README.md ├── TODO.md ├── build.rs ├── crates └── gen_welcome │ ├── .gitignore │ ├── Cargo.toml │ └── src │ ├── lib.rs │ └── main.rs ├── default.nix ├── doc ├── SUBMODULOS.md └── VARIABLES.md ├── flake.lock ├── flake.nix ├── rust-toolchain.toml ├── src ├── api │ ├── auth.rs │ ├── mod.rs │ └── routes │ │ ├── daily_challenge.rs │ │ ├── mod.rs │ │ ├── send_message.rs │ │ └── send_stats.rs ├── bot │ ├── commands.rs │ ├── commands │ │ ├── crate_docs.rs │ │ ├── explain.rs │ │ ├── hello.rs │ │ ├── help.rs │ │ ├── invite.rs │ │ ├── krate.rs │ │ ├── krate │ │ │ └── fetch.rs │ │ ├── ping.rs │ │ ├── stats.rs │ │ ├── suggest.rs │ │ └── suggest │ │ │ ├── cancelled.rs │ │ │ ├── implemented.rs │ │ │ └── new.rs │ ├── events.rs │ ├── events │ │ ├── anti_spam.rs │ │ ├── compiler.rs │ │ ├── godbolt │ │ │ ├── compiler.rs │ │ │ ├── mod.rs │ │ │ ├── parse_args.rs │ │ │ ├── request.rs │ │ │ ├── response.rs │ │ │ └── versions.rs │ │ ├── join.rs │ │ ├── new_members_mention.rs │ │ └── read_github_links.rs │ ├── mod.rs │ └── util.rs ├── main.rs ├── secrets.rs └── utils.rs └── static ├── Cloudkicker_-_Loops_-_22_2011_07.mp3 ├── compiler_help.txt ├── fonts ├── WorkSans-Bold.ttf └── WorkSans-Regular.ttf ├── loop.mp3 ├── ting.mp3 └── welcome_background.png /.env.example: -------------------------------------------------------------------------------- 1 | # Change this 2 | DISCORD_TOKEN = "Bot Token" 3 | GUILD_ID = "Server ID" 4 | 5 | # Config 6 | CHANNEL_DAILY = "Channel id for Daily Challenges" 7 | LAVALINK_HOST = "Hostname with Port" 8 | LAVALINK_PASSWORD = "Lavalink Password" 9 | 10 | # Keep this for development 11 | BOT_API_PORT = 8080 12 | BOT_API_ADDR = 0.0.0.0 13 | BOT_APIKEY = "PermitidoHacerCosas123" 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Check Format and Code Quality 2 | on: 3 | workflow_dispatch: 4 | workflow_call: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | - dev 10 | paths: 11 | - build.rs 12 | - "**/**.rs" 13 | 14 | jobs: 15 | check-fmt: 16 | runs-on: ubuntu-22.04 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | - uses: dtolnay/rust-toolchain@stable 21 | - name: checks 22 | run: | 23 | cargo fmt --all --check 24 | 25 | check-clippy: 26 | needs: [check-fmt] 27 | runs-on: ubuntu-22.04 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | - uses: dtolnay/rust-toolchain@stable 32 | - uses: Swatinem/rust-cache@v2 33 | - name: Checks 34 | run: | 35 | cargo clippy -- -D warnings 36 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Create and Publish a Docker Image 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | env: 10 | REGISTRY: ghcr.io 11 | 12 | jobs: 13 | generate-matrix: 14 | runs-on: ubuntu-latest 15 | outputs: 16 | matrix: ${{ steps.generate-matrix.outputs.matrix }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - name: Install Nix 21 | uses: cachix/install-nix-action@v30 22 | with: 23 | nix_path: nixpkgs=channel:nixos-unstable 24 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 25 | extra_nix_config: | 26 | experimental-features = nix-command flakes 27 | 28 | - name: Generate matrix 29 | id: generate-matrix 30 | run: | 31 | MATRIX=$(nix run .#matrix --quiet) 32 | echo "Generated Matrix:" 33 | echo "$MATRIX" 34 | echo "matrix=$MATRIX" >> $GITHUB_OUTPUT 35 | 36 | build-and-push-image: 37 | runs-on: ubuntu-latest 38 | permissions: 39 | contents: read 40 | packages: write 41 | attestations: write 42 | id-token: write 43 | needs: generate-matrix 44 | strategy: 45 | fail-fast: false 46 | matrix: 47 | include: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} 48 | 49 | steps: 50 | - name: Checkout repository 51 | uses: actions/checkout@v4 52 | with: 53 | submodules: "recursive" 54 | 55 | - name: Install Nix 56 | uses: cachix/install-nix-action@v30 57 | with: 58 | nix_path: nixpkgs=channel:nixos-unstable 59 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 60 | extra_nix_config: | 61 | experimental-features = nix-command flakes 62 | 63 | - name: Set IMAGE_NAME 64 | run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV 65 | 66 | - name: Login to the Container Registry 67 | uses: docker/login-action@v2 68 | with: 69 | registry: ${{ env.REGISTRY }} 70 | username: ${{ github.actor }} 71 | password: ${{ secrets.GITHUB_TOKEN }} 72 | 73 | - name: (${{ matrix.arch }}) Extract Version and Build/Push Images 74 | env: 75 | REGISTRY: ${{ env.REGISTRY }} 76 | IMAGE_NAME: ${{ env.IMAGE_NAME }} 77 | run: | 78 | VERSION=$(grep '^version' Cargo.toml | cut -d'"' -f2) 79 | IMG_NAME="${REGISTRY}/${IMAGE_NAME}" 80 | 81 | echo "Building ${IMG_NAME}:${VERSION}" 82 | nix build '.?submodules=1#image-${{ matrix.arch }}' 83 | docker load <./result 84 | 85 | echo "Publish '${VERSION}'" 86 | docker tag "cangrebot:${VERSION}" "${IMG_NAME}:${VERSION}" 87 | docker push "${IMG_NAME}:${VERSION}" 88 | 89 | echo "Publish 'latest'" 90 | docker tag "cangrebot:${VERSION}" "${IMG_NAME}:latest" 91 | docker push "${IMG_NAME}:latest" 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | result 3 | Secrets*.toml 4 | .env 5 | .vscode 6 | *.dca 7 | .idea 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "static/rust-examples"] 2 | path = static/rust-examples 3 | url = https://github.com/CrawKatt/rust-examples 4 | -------------------------------------------------------------------------------- /.ignore: -------------------------------------------------------------------------------- 1 | !static/ 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Registro de Cambios 2 | 3 | ## v0.1.1 (Desplegado) 4 | - Agrega algunos comandos 5 | - Agrega un mensaje de bienvenida al servidor 6 | 7 | ## v0.1.0 (No lanzada) 8 | 9 | ### Nuevo 10 | - Organización de la estructura de carpetas. 11 | - Uso de la libreria `color_eyre`. 12 | - Añadido el comando `!ping`. 13 | - Uso de la librería `serenity`. 14 | - Fase inicial del proyecto. 15 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "ab_glyph" 7 | version = "0.2.29" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" 10 | dependencies = [ 11 | "ab_glyph_rasterizer", 12 | "owned_ttf_parser", 13 | ] 14 | 15 | [[package]] 16 | name = "ab_glyph_rasterizer" 17 | version = "0.1.8" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" 20 | 21 | [[package]] 22 | name = "addr2line" 23 | version = "0.24.2" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 26 | dependencies = [ 27 | "gimli", 28 | ] 29 | 30 | [[package]] 31 | name = "adler2" 32 | version = "2.0.0" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 35 | 36 | [[package]] 37 | name = "ahash" 38 | version = "0.8.12" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" 41 | dependencies = [ 42 | "cfg-if", 43 | "getrandom 0.3.3", 44 | "once_cell", 45 | "version_check", 46 | "zerocopy", 47 | ] 48 | 49 | [[package]] 50 | name = "aho-corasick" 51 | version = "1.1.3" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 54 | dependencies = [ 55 | "memchr", 56 | ] 57 | 58 | [[package]] 59 | name = "android-tzdata" 60 | version = "0.1.1" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 63 | 64 | [[package]] 65 | name = "android_system_properties" 66 | version = "0.1.5" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 69 | dependencies = [ 70 | "libc", 71 | ] 72 | 73 | [[package]] 74 | name = "anyhow" 75 | version = "1.0.98" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" 78 | 79 | [[package]] 80 | name = "approx" 81 | version = "0.5.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" 84 | dependencies = [ 85 | "num-traits", 86 | ] 87 | 88 | [[package]] 89 | name = "arrayvec" 90 | version = "0.7.6" 91 | source = "registry+https://github.com/rust-lang/crates.io-index" 92 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" 93 | dependencies = [ 94 | "serde", 95 | ] 96 | 97 | [[package]] 98 | name = "async-trait" 99 | version = "0.1.88" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" 102 | dependencies = [ 103 | "proc-macro2", 104 | "quote", 105 | "syn 2.0.101", 106 | ] 107 | 108 | [[package]] 109 | name = "autocfg" 110 | version = "1.4.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 113 | 114 | [[package]] 115 | name = "axum" 116 | version = "0.7.9" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" 119 | dependencies = [ 120 | "async-trait", 121 | "axum-core", 122 | "bytes", 123 | "futures-util", 124 | "http 1.3.1", 125 | "http-body 1.0.1", 126 | "http-body-util", 127 | "hyper 1.6.0", 128 | "hyper-util", 129 | "itoa", 130 | "matchit", 131 | "memchr", 132 | "mime", 133 | "percent-encoding", 134 | "pin-project-lite", 135 | "rustversion", 136 | "serde", 137 | "serde_json", 138 | "serde_path_to_error", 139 | "serde_urlencoded", 140 | "sync_wrapper 1.0.2", 141 | "tokio", 142 | "tower", 143 | "tower-layer", 144 | "tower-service", 145 | "tracing", 146 | ] 147 | 148 | [[package]] 149 | name = "axum-core" 150 | version = "0.4.5" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" 153 | dependencies = [ 154 | "async-trait", 155 | "bytes", 156 | "futures-util", 157 | "http 1.3.1", 158 | "http-body 1.0.1", 159 | "http-body-util", 160 | "mime", 161 | "pin-project-lite", 162 | "rustversion", 163 | "sync_wrapper 1.0.2", 164 | "tower-layer", 165 | "tower-service", 166 | "tracing", 167 | ] 168 | 169 | [[package]] 170 | name = "backtrace" 171 | version = "0.3.75" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" 174 | dependencies = [ 175 | "addr2line", 176 | "cfg-if", 177 | "libc", 178 | "miniz_oxide", 179 | "object", 180 | "rustc-demangle", 181 | "windows-targets 0.52.6", 182 | ] 183 | 184 | [[package]] 185 | name = "base64" 186 | version = "0.21.7" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 189 | 190 | [[package]] 191 | name = "base64" 192 | version = "0.22.1" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 195 | 196 | [[package]] 197 | name = "bitflags" 198 | version = "1.3.2" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 201 | 202 | [[package]] 203 | name = "bitflags" 204 | version = "2.9.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" 207 | 208 | [[package]] 209 | name = "block-buffer" 210 | version = "0.10.4" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 213 | dependencies = [ 214 | "generic-array", 215 | ] 216 | 217 | [[package]] 218 | name = "bumpalo" 219 | version = "3.17.0" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 222 | 223 | [[package]] 224 | name = "bytecount" 225 | version = "0.6.8" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" 228 | 229 | [[package]] 230 | name = "bytemuck" 231 | version = "1.23.0" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" 234 | 235 | [[package]] 236 | name = "byteorder" 237 | version = "1.5.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 240 | 241 | [[package]] 242 | name = "byteorder-lite" 243 | version = "0.1.0" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" 246 | 247 | [[package]] 248 | name = "bytes" 249 | version = "1.10.1" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" 252 | 253 | [[package]] 254 | name = "camino" 255 | version = "1.1.9" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" 258 | dependencies = [ 259 | "serde", 260 | ] 261 | 262 | [[package]] 263 | name = "cangrebot" 264 | version = "0.1.0" 265 | dependencies = [ 266 | "anyhow", 267 | "axum", 268 | "chrono", 269 | "color-eyre", 270 | "dotenvy", 271 | "gen_welcome", 272 | "lazy_static", 273 | "once_cell", 274 | "poise", 275 | "regex", 276 | "reqwest", 277 | "scraper", 278 | "semver", 279 | "serde", 280 | "serde_json", 281 | "thiserror 2.0.12", 282 | "tokio", 283 | "tokio_schedule", 284 | "tracing", 285 | ] 286 | 287 | [[package]] 288 | name = "cargo-platform" 289 | version = "0.1.9" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" 292 | dependencies = [ 293 | "serde", 294 | ] 295 | 296 | [[package]] 297 | name = "cargo_metadata" 298 | version = "0.14.2" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" 301 | dependencies = [ 302 | "camino", 303 | "cargo-platform", 304 | "semver", 305 | "serde", 306 | "serde_json", 307 | ] 308 | 309 | [[package]] 310 | name = "cc" 311 | version = "1.2.24" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" 314 | dependencies = [ 315 | "shlex", 316 | ] 317 | 318 | [[package]] 319 | name = "cfg-if" 320 | version = "1.0.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 323 | 324 | [[package]] 325 | name = "chrono" 326 | version = "0.4.41" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" 329 | dependencies = [ 330 | "android-tzdata", 331 | "iana-time-zone", 332 | "js-sys", 333 | "num-traits", 334 | "serde", 335 | "wasm-bindgen", 336 | "windows-link", 337 | ] 338 | 339 | [[package]] 340 | name = "color-eyre" 341 | version = "0.6.4" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "e6e1761c0e16f8883bbbb8ce5990867f4f06bf11a0253da6495a04ce4b6ef0ec" 344 | dependencies = [ 345 | "backtrace", 346 | "color-spantrace", 347 | "eyre", 348 | "indenter", 349 | "once_cell", 350 | "owo-colors", 351 | "tracing-error", 352 | ] 353 | 354 | [[package]] 355 | name = "color-spantrace" 356 | version = "0.2.2" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "2ddd8d5bfda1e11a501d0a7303f3bfed9aa632ebdb859be40d0fd70478ed70d5" 359 | dependencies = [ 360 | "once_cell", 361 | "owo-colors", 362 | "tracing-core", 363 | "tracing-error", 364 | ] 365 | 366 | [[package]] 367 | name = "conv" 368 | version = "0.3.3" 369 | source = "registry+https://github.com/rust-lang/crates.io-index" 370 | checksum = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" 371 | dependencies = [ 372 | "custom_derive", 373 | ] 374 | 375 | [[package]] 376 | name = "core-foundation" 377 | version = "0.9.4" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 380 | dependencies = [ 381 | "core-foundation-sys", 382 | "libc", 383 | ] 384 | 385 | [[package]] 386 | name = "core-foundation-sys" 387 | version = "0.8.7" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 390 | 391 | [[package]] 392 | name = "cpufeatures" 393 | version = "0.2.17" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" 396 | dependencies = [ 397 | "libc", 398 | ] 399 | 400 | [[package]] 401 | name = "crc32fast" 402 | version = "1.4.2" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 405 | dependencies = [ 406 | "cfg-if", 407 | ] 408 | 409 | [[package]] 410 | name = "crossbeam-channel" 411 | version = "0.5.15" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" 414 | dependencies = [ 415 | "crossbeam-utils", 416 | ] 417 | 418 | [[package]] 419 | name = "crossbeam-utils" 420 | version = "0.8.21" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 423 | 424 | [[package]] 425 | name = "crypto-common" 426 | version = "0.1.6" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 429 | dependencies = [ 430 | "generic-array", 431 | "typenum", 432 | ] 433 | 434 | [[package]] 435 | name = "cssparser" 436 | version = "0.31.2" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" 439 | dependencies = [ 440 | "cssparser-macros", 441 | "dtoa-short", 442 | "itoa", 443 | "phf 0.11.3", 444 | "smallvec", 445 | ] 446 | 447 | [[package]] 448 | name = "cssparser-macros" 449 | version = "0.6.1" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" 452 | dependencies = [ 453 | "quote", 454 | "syn 2.0.101", 455 | ] 456 | 457 | [[package]] 458 | name = "custom_derive" 459 | version = "0.1.7" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" 462 | 463 | [[package]] 464 | name = "darling" 465 | version = "0.20.11" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" 468 | dependencies = [ 469 | "darling_core", 470 | "darling_macro", 471 | ] 472 | 473 | [[package]] 474 | name = "darling_core" 475 | version = "0.20.11" 476 | source = "registry+https://github.com/rust-lang/crates.io-index" 477 | checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" 478 | dependencies = [ 479 | "fnv", 480 | "ident_case", 481 | "proc-macro2", 482 | "quote", 483 | "strsim", 484 | "syn 2.0.101", 485 | ] 486 | 487 | [[package]] 488 | name = "darling_macro" 489 | version = "0.20.11" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" 492 | dependencies = [ 493 | "darling_core", 494 | "quote", 495 | "syn 2.0.101", 496 | ] 497 | 498 | [[package]] 499 | name = "dashmap" 500 | version = "5.5.3" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" 503 | dependencies = [ 504 | "cfg-if", 505 | "hashbrown 0.14.5", 506 | "lock_api", 507 | "once_cell", 508 | "parking_lot_core", 509 | "serde", 510 | ] 511 | 512 | [[package]] 513 | name = "data-encoding" 514 | version = "2.9.0" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" 517 | 518 | [[package]] 519 | name = "deranged" 520 | version = "0.4.0" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" 523 | dependencies = [ 524 | "powerfmt", 525 | "serde", 526 | ] 527 | 528 | [[package]] 529 | name = "derivative" 530 | version = "2.2.0" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 533 | dependencies = [ 534 | "proc-macro2", 535 | "quote", 536 | "syn 1.0.109", 537 | ] 538 | 539 | [[package]] 540 | name = "derive_more" 541 | version = "0.99.20" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" 544 | dependencies = [ 545 | "proc-macro2", 546 | "quote", 547 | "syn 2.0.101", 548 | ] 549 | 550 | [[package]] 551 | name = "digest" 552 | version = "0.10.7" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 555 | dependencies = [ 556 | "block-buffer", 557 | "crypto-common", 558 | ] 559 | 560 | [[package]] 561 | name = "displaydoc" 562 | version = "0.2.5" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 565 | dependencies = [ 566 | "proc-macro2", 567 | "quote", 568 | "syn 2.0.101", 569 | ] 570 | 571 | [[package]] 572 | name = "dotenvy" 573 | version = "0.15.7" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 576 | 577 | [[package]] 578 | name = "dtoa" 579 | version = "1.0.10" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" 582 | 583 | [[package]] 584 | name = "dtoa-short" 585 | version = "0.3.5" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" 588 | dependencies = [ 589 | "dtoa", 590 | ] 591 | 592 | [[package]] 593 | name = "ego-tree" 594 | version = "0.6.3" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "12a0bb14ac04a9fcf170d0bbbef949b44cc492f4452bd20c095636956f653642" 597 | 598 | [[package]] 599 | name = "either" 600 | version = "1.15.0" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 603 | 604 | [[package]] 605 | name = "encoding_rs" 606 | version = "0.8.35" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" 609 | dependencies = [ 610 | "cfg-if", 611 | ] 612 | 613 | [[package]] 614 | name = "equivalent" 615 | version = "1.0.2" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 618 | 619 | [[package]] 620 | name = "errno" 621 | version = "0.3.12" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" 624 | dependencies = [ 625 | "libc", 626 | "windows-sys 0.59.0", 627 | ] 628 | 629 | [[package]] 630 | name = "error-chain" 631 | version = "0.12.4" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" 634 | dependencies = [ 635 | "version_check", 636 | ] 637 | 638 | [[package]] 639 | name = "eyre" 640 | version = "0.6.12" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" 643 | dependencies = [ 644 | "indenter", 645 | "once_cell", 646 | ] 647 | 648 | [[package]] 649 | name = "fastrand" 650 | version = "2.3.0" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 653 | 654 | [[package]] 655 | name = "fdeflate" 656 | version = "0.3.7" 657 | source = "registry+https://github.com/rust-lang/crates.io-index" 658 | checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" 659 | dependencies = [ 660 | "simd-adler32", 661 | ] 662 | 663 | [[package]] 664 | name = "flate2" 665 | version = "1.1.1" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" 668 | dependencies = [ 669 | "crc32fast", 670 | "miniz_oxide", 671 | ] 672 | 673 | [[package]] 674 | name = "fnv" 675 | version = "1.0.7" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 678 | 679 | [[package]] 680 | name = "form_urlencoded" 681 | version = "1.2.1" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 684 | dependencies = [ 685 | "percent-encoding", 686 | ] 687 | 688 | [[package]] 689 | name = "futf" 690 | version = "0.1.5" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" 693 | dependencies = [ 694 | "mac", 695 | "new_debug_unreachable", 696 | ] 697 | 698 | [[package]] 699 | name = "futures" 700 | version = "0.3.31" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 703 | dependencies = [ 704 | "futures-channel", 705 | "futures-core", 706 | "futures-io", 707 | "futures-sink", 708 | "futures-task", 709 | "futures-util", 710 | ] 711 | 712 | [[package]] 713 | name = "futures-channel" 714 | version = "0.3.31" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 717 | dependencies = [ 718 | "futures-core", 719 | "futures-sink", 720 | ] 721 | 722 | [[package]] 723 | name = "futures-core" 724 | version = "0.3.31" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 727 | 728 | [[package]] 729 | name = "futures-io" 730 | version = "0.3.31" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 733 | 734 | [[package]] 735 | name = "futures-macro" 736 | version = "0.3.31" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 739 | dependencies = [ 740 | "proc-macro2", 741 | "quote", 742 | "syn 2.0.101", 743 | ] 744 | 745 | [[package]] 746 | name = "futures-sink" 747 | version = "0.3.31" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 750 | 751 | [[package]] 752 | name = "futures-task" 753 | version = "0.3.31" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 756 | 757 | [[package]] 758 | name = "futures-util" 759 | version = "0.3.31" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 762 | dependencies = [ 763 | "futures-channel", 764 | "futures-core", 765 | "futures-io", 766 | "futures-macro", 767 | "futures-sink", 768 | "futures-task", 769 | "memchr", 770 | "pin-project-lite", 771 | "pin-utils", 772 | "slab", 773 | ] 774 | 775 | [[package]] 776 | name = "fxhash" 777 | version = "0.2.1" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 780 | dependencies = [ 781 | "byteorder", 782 | ] 783 | 784 | [[package]] 785 | name = "gen_welcome" 786 | version = "0.1.0" 787 | dependencies = [ 788 | "ab_glyph", 789 | "image", 790 | "imageproc", 791 | ] 792 | 793 | [[package]] 794 | name = "generic-array" 795 | version = "0.14.7" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 798 | dependencies = [ 799 | "typenum", 800 | "version_check", 801 | ] 802 | 803 | [[package]] 804 | name = "getopts" 805 | version = "0.2.21" 806 | source = "registry+https://github.com/rust-lang/crates.io-index" 807 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 808 | dependencies = [ 809 | "unicode-width", 810 | ] 811 | 812 | [[package]] 813 | name = "getrandom" 814 | version = "0.2.16" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" 817 | dependencies = [ 818 | "cfg-if", 819 | "js-sys", 820 | "libc", 821 | "wasi 0.11.0+wasi-snapshot-preview1", 822 | "wasm-bindgen", 823 | ] 824 | 825 | [[package]] 826 | name = "getrandom" 827 | version = "0.3.3" 828 | source = "registry+https://github.com/rust-lang/crates.io-index" 829 | checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" 830 | dependencies = [ 831 | "cfg-if", 832 | "libc", 833 | "r-efi", 834 | "wasi 0.14.2+wasi-0.2.4", 835 | ] 836 | 837 | [[package]] 838 | name = "gimli" 839 | version = "0.31.1" 840 | source = "registry+https://github.com/rust-lang/crates.io-index" 841 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 842 | 843 | [[package]] 844 | name = "glob" 845 | version = "0.3.2" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 848 | 849 | [[package]] 850 | name = "h2" 851 | version = "0.3.26" 852 | source = "registry+https://github.com/rust-lang/crates.io-index" 853 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 854 | dependencies = [ 855 | "bytes", 856 | "fnv", 857 | "futures-core", 858 | "futures-sink", 859 | "futures-util", 860 | "http 0.2.12", 861 | "indexmap", 862 | "slab", 863 | "tokio", 864 | "tokio-util", 865 | "tracing", 866 | ] 867 | 868 | [[package]] 869 | name = "hashbrown" 870 | version = "0.14.5" 871 | source = "registry+https://github.com/rust-lang/crates.io-index" 872 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 873 | 874 | [[package]] 875 | name = "hashbrown" 876 | version = "0.15.3" 877 | source = "registry+https://github.com/rust-lang/crates.io-index" 878 | checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" 879 | 880 | [[package]] 881 | name = "html5ever" 882 | version = "0.27.0" 883 | source = "registry+https://github.com/rust-lang/crates.io-index" 884 | checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" 885 | dependencies = [ 886 | "log", 887 | "mac", 888 | "markup5ever", 889 | "proc-macro2", 890 | "quote", 891 | "syn 2.0.101", 892 | ] 893 | 894 | [[package]] 895 | name = "http" 896 | version = "0.2.12" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 899 | dependencies = [ 900 | "bytes", 901 | "fnv", 902 | "itoa", 903 | ] 904 | 905 | [[package]] 906 | name = "http" 907 | version = "1.3.1" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" 910 | dependencies = [ 911 | "bytes", 912 | "fnv", 913 | "itoa", 914 | ] 915 | 916 | [[package]] 917 | name = "http-body" 918 | version = "0.4.6" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 921 | dependencies = [ 922 | "bytes", 923 | "http 0.2.12", 924 | "pin-project-lite", 925 | ] 926 | 927 | [[package]] 928 | name = "http-body" 929 | version = "1.0.1" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 932 | dependencies = [ 933 | "bytes", 934 | "http 1.3.1", 935 | ] 936 | 937 | [[package]] 938 | name = "http-body-util" 939 | version = "0.1.3" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" 942 | dependencies = [ 943 | "bytes", 944 | "futures-core", 945 | "http 1.3.1", 946 | "http-body 1.0.1", 947 | "pin-project-lite", 948 | ] 949 | 950 | [[package]] 951 | name = "httparse" 952 | version = "1.10.1" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 955 | 956 | [[package]] 957 | name = "httpdate" 958 | version = "1.0.3" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 961 | 962 | [[package]] 963 | name = "hyper" 964 | version = "0.14.32" 965 | source = "registry+https://github.com/rust-lang/crates.io-index" 966 | checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" 967 | dependencies = [ 968 | "bytes", 969 | "futures-channel", 970 | "futures-core", 971 | "futures-util", 972 | "h2", 973 | "http 0.2.12", 974 | "http-body 0.4.6", 975 | "httparse", 976 | "httpdate", 977 | "itoa", 978 | "pin-project-lite", 979 | "socket2", 980 | "tokio", 981 | "tower-service", 982 | "tracing", 983 | "want", 984 | ] 985 | 986 | [[package]] 987 | name = "hyper" 988 | version = "1.6.0" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" 991 | dependencies = [ 992 | "bytes", 993 | "futures-channel", 994 | "futures-util", 995 | "http 1.3.1", 996 | "http-body 1.0.1", 997 | "httparse", 998 | "httpdate", 999 | "itoa", 1000 | "pin-project-lite", 1001 | "smallvec", 1002 | "tokio", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "hyper-rustls" 1007 | version = "0.24.2" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" 1010 | dependencies = [ 1011 | "futures-util", 1012 | "http 0.2.12", 1013 | "hyper 0.14.32", 1014 | "rustls 0.21.12", 1015 | "tokio", 1016 | "tokio-rustls 0.24.1", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "hyper-util" 1021 | version = "0.1.13" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8" 1024 | dependencies = [ 1025 | "bytes", 1026 | "futures-core", 1027 | "http 1.3.1", 1028 | "http-body 1.0.1", 1029 | "hyper 1.6.0", 1030 | "pin-project-lite", 1031 | "tokio", 1032 | "tower-service", 1033 | ] 1034 | 1035 | [[package]] 1036 | name = "iana-time-zone" 1037 | version = "0.1.63" 1038 | source = "registry+https://github.com/rust-lang/crates.io-index" 1039 | checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" 1040 | dependencies = [ 1041 | "android_system_properties", 1042 | "core-foundation-sys", 1043 | "iana-time-zone-haiku", 1044 | "js-sys", 1045 | "log", 1046 | "wasm-bindgen", 1047 | "windows-core", 1048 | ] 1049 | 1050 | [[package]] 1051 | name = "iana-time-zone-haiku" 1052 | version = "0.1.2" 1053 | source = "registry+https://github.com/rust-lang/crates.io-index" 1054 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1055 | dependencies = [ 1056 | "cc", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "icu_collections" 1061 | version = "2.0.0" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" 1064 | dependencies = [ 1065 | "displaydoc", 1066 | "potential_utf", 1067 | "yoke", 1068 | "zerofrom", 1069 | "zerovec", 1070 | ] 1071 | 1072 | [[package]] 1073 | name = "icu_locale_core" 1074 | version = "2.0.0" 1075 | source = "registry+https://github.com/rust-lang/crates.io-index" 1076 | checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" 1077 | dependencies = [ 1078 | "displaydoc", 1079 | "litemap", 1080 | "tinystr", 1081 | "writeable", 1082 | "zerovec", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "icu_normalizer" 1087 | version = "2.0.0" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" 1090 | dependencies = [ 1091 | "displaydoc", 1092 | "icu_collections", 1093 | "icu_normalizer_data", 1094 | "icu_properties", 1095 | "icu_provider", 1096 | "smallvec", 1097 | "zerovec", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "icu_normalizer_data" 1102 | version = "2.0.0" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" 1105 | 1106 | [[package]] 1107 | name = "icu_properties" 1108 | version = "2.0.1" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" 1111 | dependencies = [ 1112 | "displaydoc", 1113 | "icu_collections", 1114 | "icu_locale_core", 1115 | "icu_properties_data", 1116 | "icu_provider", 1117 | "potential_utf", 1118 | "zerotrie", 1119 | "zerovec", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "icu_properties_data" 1124 | version = "2.0.1" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" 1127 | 1128 | [[package]] 1129 | name = "icu_provider" 1130 | version = "2.0.0" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" 1133 | dependencies = [ 1134 | "displaydoc", 1135 | "icu_locale_core", 1136 | "stable_deref_trait", 1137 | "tinystr", 1138 | "writeable", 1139 | "yoke", 1140 | "zerofrom", 1141 | "zerotrie", 1142 | "zerovec", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "ident_case" 1147 | version = "1.0.1" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1150 | 1151 | [[package]] 1152 | name = "idna" 1153 | version = "1.0.3" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1156 | dependencies = [ 1157 | "idna_adapter", 1158 | "smallvec", 1159 | "utf8_iter", 1160 | ] 1161 | 1162 | [[package]] 1163 | name = "idna_adapter" 1164 | version = "1.2.1" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 1167 | dependencies = [ 1168 | "icu_normalizer", 1169 | "icu_properties", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "image" 1174 | version = "0.25.6" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" 1177 | dependencies = [ 1178 | "bytemuck", 1179 | "byteorder-lite", 1180 | "image-webp", 1181 | "num-traits", 1182 | "png", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "image-webp" 1187 | version = "0.2.1" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" 1190 | dependencies = [ 1191 | "byteorder-lite", 1192 | "quick-error", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "imageproc" 1197 | version = "0.24.0" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "a2a0d7770f428b4615960cc8602775d1f04c75d41b0ccdef862e889ebaae9bbf" 1200 | dependencies = [ 1201 | "ab_glyph", 1202 | "approx", 1203 | "conv", 1204 | "getrandom 0.2.16", 1205 | "image", 1206 | "itertools", 1207 | "nalgebra", 1208 | "num", 1209 | "rand", 1210 | "rand_distr", 1211 | ] 1212 | 1213 | [[package]] 1214 | name = "indenter" 1215 | version = "0.3.3" 1216 | source = "registry+https://github.com/rust-lang/crates.io-index" 1217 | checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" 1218 | 1219 | [[package]] 1220 | name = "indexmap" 1221 | version = "2.9.0" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 1224 | dependencies = [ 1225 | "equivalent", 1226 | "hashbrown 0.15.3", 1227 | ] 1228 | 1229 | [[package]] 1230 | name = "ipnet" 1231 | version = "2.11.0" 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" 1233 | checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" 1234 | 1235 | [[package]] 1236 | name = "itertools" 1237 | version = "0.12.1" 1238 | source = "registry+https://github.com/rust-lang/crates.io-index" 1239 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 1240 | dependencies = [ 1241 | "either", 1242 | ] 1243 | 1244 | [[package]] 1245 | name = "itoa" 1246 | version = "1.0.15" 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" 1248 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 1249 | 1250 | [[package]] 1251 | name = "js-sys" 1252 | version = "0.3.77" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 1255 | dependencies = [ 1256 | "once_cell", 1257 | "wasm-bindgen", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "lazy_static" 1262 | version = "1.5.0" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1265 | 1266 | [[package]] 1267 | name = "libc" 1268 | version = "0.2.172" 1269 | source = "registry+https://github.com/rust-lang/crates.io-index" 1270 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 1271 | 1272 | [[package]] 1273 | name = "libm" 1274 | version = "0.2.15" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" 1277 | 1278 | [[package]] 1279 | name = "linux-raw-sys" 1280 | version = "0.9.4" 1281 | source = "registry+https://github.com/rust-lang/crates.io-index" 1282 | checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" 1283 | 1284 | [[package]] 1285 | name = "litemap" 1286 | version = "0.8.0" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" 1289 | 1290 | [[package]] 1291 | name = "lock_api" 1292 | version = "0.4.13" 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" 1294 | checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" 1295 | dependencies = [ 1296 | "autocfg", 1297 | "scopeguard", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "log" 1302 | version = "0.4.27" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 1305 | 1306 | [[package]] 1307 | name = "mac" 1308 | version = "0.1.1" 1309 | source = "registry+https://github.com/rust-lang/crates.io-index" 1310 | checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" 1311 | 1312 | [[package]] 1313 | name = "markup5ever" 1314 | version = "0.12.1" 1315 | source = "registry+https://github.com/rust-lang/crates.io-index" 1316 | checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" 1317 | dependencies = [ 1318 | "log", 1319 | "phf 0.11.3", 1320 | "phf_codegen 0.11.3", 1321 | "string_cache", 1322 | "string_cache_codegen", 1323 | "tendril", 1324 | ] 1325 | 1326 | [[package]] 1327 | name = "matchit" 1328 | version = "0.7.3" 1329 | source = "registry+https://github.com/rust-lang/crates.io-index" 1330 | checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" 1331 | 1332 | [[package]] 1333 | name = "matrixmultiply" 1334 | version = "0.3.10" 1335 | source = "registry+https://github.com/rust-lang/crates.io-index" 1336 | checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" 1337 | dependencies = [ 1338 | "autocfg", 1339 | "rawpointer", 1340 | ] 1341 | 1342 | [[package]] 1343 | name = "memchr" 1344 | version = "2.7.4" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1347 | 1348 | [[package]] 1349 | name = "mime" 1350 | version = "0.3.17" 1351 | source = "registry+https://github.com/rust-lang/crates.io-index" 1352 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1353 | 1354 | [[package]] 1355 | name = "mime_guess" 1356 | version = "2.0.5" 1357 | source = "registry+https://github.com/rust-lang/crates.io-index" 1358 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 1359 | dependencies = [ 1360 | "mime", 1361 | "unicase", 1362 | ] 1363 | 1364 | [[package]] 1365 | name = "mini-moka" 1366 | version = "0.10.3" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" 1369 | dependencies = [ 1370 | "crossbeam-channel", 1371 | "crossbeam-utils", 1372 | "dashmap", 1373 | "skeptic", 1374 | "smallvec", 1375 | "tagptr", 1376 | "triomphe", 1377 | ] 1378 | 1379 | [[package]] 1380 | name = "miniz_oxide" 1381 | version = "0.8.8" 1382 | source = "registry+https://github.com/rust-lang/crates.io-index" 1383 | checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" 1384 | dependencies = [ 1385 | "adler2", 1386 | "simd-adler32", 1387 | ] 1388 | 1389 | [[package]] 1390 | name = "mio" 1391 | version = "1.0.4" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" 1394 | dependencies = [ 1395 | "libc", 1396 | "wasi 0.11.0+wasi-snapshot-preview1", 1397 | "windows-sys 0.59.0", 1398 | ] 1399 | 1400 | [[package]] 1401 | name = "nalgebra" 1402 | version = "0.32.6" 1403 | source = "registry+https://github.com/rust-lang/crates.io-index" 1404 | checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" 1405 | dependencies = [ 1406 | "approx", 1407 | "matrixmultiply", 1408 | "num-complex", 1409 | "num-rational", 1410 | "num-traits", 1411 | "simba", 1412 | "typenum", 1413 | ] 1414 | 1415 | [[package]] 1416 | name = "new_debug_unreachable" 1417 | version = "1.0.6" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" 1420 | 1421 | [[package]] 1422 | name = "num" 1423 | version = "0.4.3" 1424 | source = "registry+https://github.com/rust-lang/crates.io-index" 1425 | checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" 1426 | dependencies = [ 1427 | "num-bigint", 1428 | "num-complex", 1429 | "num-integer", 1430 | "num-iter", 1431 | "num-rational", 1432 | "num-traits", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "num-bigint" 1437 | version = "0.4.6" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 1440 | dependencies = [ 1441 | "num-integer", 1442 | "num-traits", 1443 | ] 1444 | 1445 | [[package]] 1446 | name = "num-complex" 1447 | version = "0.4.6" 1448 | source = "registry+https://github.com/rust-lang/crates.io-index" 1449 | checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" 1450 | dependencies = [ 1451 | "num-traits", 1452 | ] 1453 | 1454 | [[package]] 1455 | name = "num-conv" 1456 | version = "0.1.0" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1459 | 1460 | [[package]] 1461 | name = "num-integer" 1462 | version = "0.1.46" 1463 | source = "registry+https://github.com/rust-lang/crates.io-index" 1464 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 1465 | dependencies = [ 1466 | "num-traits", 1467 | ] 1468 | 1469 | [[package]] 1470 | name = "num-iter" 1471 | version = "0.1.45" 1472 | source = "registry+https://github.com/rust-lang/crates.io-index" 1473 | checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" 1474 | dependencies = [ 1475 | "autocfg", 1476 | "num-integer", 1477 | "num-traits", 1478 | ] 1479 | 1480 | [[package]] 1481 | name = "num-rational" 1482 | version = "0.4.2" 1483 | source = "registry+https://github.com/rust-lang/crates.io-index" 1484 | checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" 1485 | dependencies = [ 1486 | "num-bigint", 1487 | "num-integer", 1488 | "num-traits", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "num-traits" 1493 | version = "0.2.19" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1496 | dependencies = [ 1497 | "autocfg", 1498 | "libm", 1499 | ] 1500 | 1501 | [[package]] 1502 | name = "object" 1503 | version = "0.36.7" 1504 | source = "registry+https://github.com/rust-lang/crates.io-index" 1505 | checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" 1506 | dependencies = [ 1507 | "memchr", 1508 | ] 1509 | 1510 | [[package]] 1511 | name = "once_cell" 1512 | version = "1.21.3" 1513 | source = "registry+https://github.com/rust-lang/crates.io-index" 1514 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 1515 | 1516 | [[package]] 1517 | name = "owned_ttf_parser" 1518 | version = "0.25.0" 1519 | source = "registry+https://github.com/rust-lang/crates.io-index" 1520 | checksum = "22ec719bbf3b2a81c109a4e20b1f129b5566b7dce654bc3872f6a05abf82b2c4" 1521 | dependencies = [ 1522 | "ttf-parser", 1523 | ] 1524 | 1525 | [[package]] 1526 | name = "owo-colors" 1527 | version = "4.2.1" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" 1530 | 1531 | [[package]] 1532 | name = "parking_lot" 1533 | version = "0.12.4" 1534 | source = "registry+https://github.com/rust-lang/crates.io-index" 1535 | checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" 1536 | dependencies = [ 1537 | "lock_api", 1538 | "parking_lot_core", 1539 | ] 1540 | 1541 | [[package]] 1542 | name = "parking_lot_core" 1543 | version = "0.9.11" 1544 | source = "registry+https://github.com/rust-lang/crates.io-index" 1545 | checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" 1546 | dependencies = [ 1547 | "cfg-if", 1548 | "libc", 1549 | "redox_syscall", 1550 | "smallvec", 1551 | "windows-targets 0.52.6", 1552 | ] 1553 | 1554 | [[package]] 1555 | name = "paste" 1556 | version = "1.0.15" 1557 | source = "registry+https://github.com/rust-lang/crates.io-index" 1558 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1559 | 1560 | [[package]] 1561 | name = "percent-encoding" 1562 | version = "2.3.1" 1563 | source = "registry+https://github.com/rust-lang/crates.io-index" 1564 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1565 | 1566 | [[package]] 1567 | name = "phf" 1568 | version = "0.10.1" 1569 | source = "registry+https://github.com/rust-lang/crates.io-index" 1570 | checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" 1571 | dependencies = [ 1572 | "phf_shared 0.10.0", 1573 | ] 1574 | 1575 | [[package]] 1576 | name = "phf" 1577 | version = "0.11.3" 1578 | source = "registry+https://github.com/rust-lang/crates.io-index" 1579 | checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" 1580 | dependencies = [ 1581 | "phf_macros", 1582 | "phf_shared 0.11.3", 1583 | ] 1584 | 1585 | [[package]] 1586 | name = "phf_codegen" 1587 | version = "0.10.0" 1588 | source = "registry+https://github.com/rust-lang/crates.io-index" 1589 | checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" 1590 | dependencies = [ 1591 | "phf_generator 0.10.0", 1592 | "phf_shared 0.10.0", 1593 | ] 1594 | 1595 | [[package]] 1596 | name = "phf_codegen" 1597 | version = "0.11.3" 1598 | source = "registry+https://github.com/rust-lang/crates.io-index" 1599 | checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" 1600 | dependencies = [ 1601 | "phf_generator 0.11.3", 1602 | "phf_shared 0.11.3", 1603 | ] 1604 | 1605 | [[package]] 1606 | name = "phf_generator" 1607 | version = "0.10.0" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 1610 | dependencies = [ 1611 | "phf_shared 0.10.0", 1612 | "rand", 1613 | ] 1614 | 1615 | [[package]] 1616 | name = "phf_generator" 1617 | version = "0.11.3" 1618 | source = "registry+https://github.com/rust-lang/crates.io-index" 1619 | checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" 1620 | dependencies = [ 1621 | "phf_shared 0.11.3", 1622 | "rand", 1623 | ] 1624 | 1625 | [[package]] 1626 | name = "phf_macros" 1627 | version = "0.11.3" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" 1630 | dependencies = [ 1631 | "phf_generator 0.11.3", 1632 | "phf_shared 0.11.3", 1633 | "proc-macro2", 1634 | "quote", 1635 | "syn 2.0.101", 1636 | ] 1637 | 1638 | [[package]] 1639 | name = "phf_shared" 1640 | version = "0.10.0" 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" 1642 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 1643 | dependencies = [ 1644 | "siphasher 0.3.11", 1645 | ] 1646 | 1647 | [[package]] 1648 | name = "phf_shared" 1649 | version = "0.11.3" 1650 | source = "registry+https://github.com/rust-lang/crates.io-index" 1651 | checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" 1652 | dependencies = [ 1653 | "siphasher 1.0.1", 1654 | ] 1655 | 1656 | [[package]] 1657 | name = "pin-project-lite" 1658 | version = "0.2.16" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" 1661 | 1662 | [[package]] 1663 | name = "pin-utils" 1664 | version = "0.1.0" 1665 | source = "registry+https://github.com/rust-lang/crates.io-index" 1666 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1667 | 1668 | [[package]] 1669 | name = "png" 1670 | version = "0.17.16" 1671 | source = "registry+https://github.com/rust-lang/crates.io-index" 1672 | checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" 1673 | dependencies = [ 1674 | "bitflags 1.3.2", 1675 | "crc32fast", 1676 | "fdeflate", 1677 | "flate2", 1678 | "miniz_oxide", 1679 | ] 1680 | 1681 | [[package]] 1682 | name = "poise" 1683 | version = "0.6.1" 1684 | source = "registry+https://github.com/rust-lang/crates.io-index" 1685 | checksum = "1819d5a45e3590ef33754abce46432570c54a120798bdbf893112b4211fa09a6" 1686 | dependencies = [ 1687 | "async-trait", 1688 | "derivative", 1689 | "futures-util", 1690 | "parking_lot", 1691 | "poise_macros", 1692 | "regex", 1693 | "serenity", 1694 | "tokio", 1695 | "tracing", 1696 | ] 1697 | 1698 | [[package]] 1699 | name = "poise_macros" 1700 | version = "0.6.1" 1701 | source = "registry+https://github.com/rust-lang/crates.io-index" 1702 | checksum = "8fa2c123c961e78315cd3deac7663177f12be4460f5440dbf62a7ed37b1effea" 1703 | dependencies = [ 1704 | "darling", 1705 | "proc-macro2", 1706 | "quote", 1707 | "syn 2.0.101", 1708 | ] 1709 | 1710 | [[package]] 1711 | name = "potential_utf" 1712 | version = "0.1.2" 1713 | source = "registry+https://github.com/rust-lang/crates.io-index" 1714 | checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" 1715 | dependencies = [ 1716 | "zerovec", 1717 | ] 1718 | 1719 | [[package]] 1720 | name = "powerfmt" 1721 | version = "0.2.0" 1722 | source = "registry+https://github.com/rust-lang/crates.io-index" 1723 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1724 | 1725 | [[package]] 1726 | name = "ppv-lite86" 1727 | version = "0.2.21" 1728 | source = "registry+https://github.com/rust-lang/crates.io-index" 1729 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" 1730 | dependencies = [ 1731 | "zerocopy", 1732 | ] 1733 | 1734 | [[package]] 1735 | name = "precomputed-hash" 1736 | version = "0.1.1" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" 1739 | 1740 | [[package]] 1741 | name = "proc-macro2" 1742 | version = "1.0.95" 1743 | source = "registry+https://github.com/rust-lang/crates.io-index" 1744 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 1745 | dependencies = [ 1746 | "unicode-ident", 1747 | ] 1748 | 1749 | [[package]] 1750 | name = "pulldown-cmark" 1751 | version = "0.9.6" 1752 | source = "registry+https://github.com/rust-lang/crates.io-index" 1753 | checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" 1754 | dependencies = [ 1755 | "bitflags 2.9.1", 1756 | "memchr", 1757 | "unicase", 1758 | ] 1759 | 1760 | [[package]] 1761 | name = "quick-error" 1762 | version = "2.0.1" 1763 | source = "registry+https://github.com/rust-lang/crates.io-index" 1764 | checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" 1765 | 1766 | [[package]] 1767 | name = "quote" 1768 | version = "1.0.40" 1769 | source = "registry+https://github.com/rust-lang/crates.io-index" 1770 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 1771 | dependencies = [ 1772 | "proc-macro2", 1773 | ] 1774 | 1775 | [[package]] 1776 | name = "r-efi" 1777 | version = "5.2.0" 1778 | source = "registry+https://github.com/rust-lang/crates.io-index" 1779 | checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" 1780 | 1781 | [[package]] 1782 | name = "rand" 1783 | version = "0.8.5" 1784 | source = "registry+https://github.com/rust-lang/crates.io-index" 1785 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1786 | dependencies = [ 1787 | "libc", 1788 | "rand_chacha", 1789 | "rand_core", 1790 | ] 1791 | 1792 | [[package]] 1793 | name = "rand_chacha" 1794 | version = "0.3.1" 1795 | source = "registry+https://github.com/rust-lang/crates.io-index" 1796 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1797 | dependencies = [ 1798 | "ppv-lite86", 1799 | "rand_core", 1800 | ] 1801 | 1802 | [[package]] 1803 | name = "rand_core" 1804 | version = "0.6.4" 1805 | source = "registry+https://github.com/rust-lang/crates.io-index" 1806 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1807 | dependencies = [ 1808 | "getrandom 0.2.16", 1809 | ] 1810 | 1811 | [[package]] 1812 | name = "rand_distr" 1813 | version = "0.4.3" 1814 | source = "registry+https://github.com/rust-lang/crates.io-index" 1815 | checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" 1816 | dependencies = [ 1817 | "num-traits", 1818 | "rand", 1819 | ] 1820 | 1821 | [[package]] 1822 | name = "rawpointer" 1823 | version = "0.2.1" 1824 | source = "registry+https://github.com/rust-lang/crates.io-index" 1825 | checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" 1826 | 1827 | [[package]] 1828 | name = "redox_syscall" 1829 | version = "0.5.12" 1830 | source = "registry+https://github.com/rust-lang/crates.io-index" 1831 | checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" 1832 | dependencies = [ 1833 | "bitflags 2.9.1", 1834 | ] 1835 | 1836 | [[package]] 1837 | name = "regex" 1838 | version = "1.11.1" 1839 | source = "registry+https://github.com/rust-lang/crates.io-index" 1840 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1841 | dependencies = [ 1842 | "aho-corasick", 1843 | "memchr", 1844 | "regex-automata", 1845 | "regex-syntax", 1846 | ] 1847 | 1848 | [[package]] 1849 | name = "regex-automata" 1850 | version = "0.4.9" 1851 | source = "registry+https://github.com/rust-lang/crates.io-index" 1852 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1853 | dependencies = [ 1854 | "aho-corasick", 1855 | "memchr", 1856 | "regex-syntax", 1857 | ] 1858 | 1859 | [[package]] 1860 | name = "regex-syntax" 1861 | version = "0.8.5" 1862 | source = "registry+https://github.com/rust-lang/crates.io-index" 1863 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1864 | 1865 | [[package]] 1866 | name = "reqwest" 1867 | version = "0.11.27" 1868 | source = "registry+https://github.com/rust-lang/crates.io-index" 1869 | checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" 1870 | dependencies = [ 1871 | "base64 0.21.7", 1872 | "bytes", 1873 | "encoding_rs", 1874 | "futures-core", 1875 | "futures-util", 1876 | "h2", 1877 | "http 0.2.12", 1878 | "http-body 0.4.6", 1879 | "hyper 0.14.32", 1880 | "hyper-rustls", 1881 | "ipnet", 1882 | "js-sys", 1883 | "log", 1884 | "mime", 1885 | "mime_guess", 1886 | "once_cell", 1887 | "percent-encoding", 1888 | "pin-project-lite", 1889 | "rustls 0.21.12", 1890 | "rustls-pemfile", 1891 | "serde", 1892 | "serde_json", 1893 | "serde_urlencoded", 1894 | "sync_wrapper 0.1.2", 1895 | "system-configuration", 1896 | "tokio", 1897 | "tokio-rustls 0.24.1", 1898 | "tokio-util", 1899 | "tower-service", 1900 | "url", 1901 | "wasm-bindgen", 1902 | "wasm-bindgen-futures", 1903 | "wasm-streams", 1904 | "web-sys", 1905 | "webpki-roots 0.25.4", 1906 | "winreg", 1907 | ] 1908 | 1909 | [[package]] 1910 | name = "ring" 1911 | version = "0.17.14" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1914 | dependencies = [ 1915 | "cc", 1916 | "cfg-if", 1917 | "getrandom 0.2.16", 1918 | "libc", 1919 | "untrusted", 1920 | "windows-sys 0.52.0", 1921 | ] 1922 | 1923 | [[package]] 1924 | name = "rustc-demangle" 1925 | version = "0.1.24" 1926 | source = "registry+https://github.com/rust-lang/crates.io-index" 1927 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1928 | 1929 | [[package]] 1930 | name = "rustix" 1931 | version = "1.0.7" 1932 | source = "registry+https://github.com/rust-lang/crates.io-index" 1933 | checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" 1934 | dependencies = [ 1935 | "bitflags 2.9.1", 1936 | "errno", 1937 | "libc", 1938 | "linux-raw-sys", 1939 | "windows-sys 0.59.0", 1940 | ] 1941 | 1942 | [[package]] 1943 | name = "rustls" 1944 | version = "0.21.12" 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" 1946 | checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" 1947 | dependencies = [ 1948 | "log", 1949 | "ring", 1950 | "rustls-webpki 0.101.7", 1951 | "sct", 1952 | ] 1953 | 1954 | [[package]] 1955 | name = "rustls" 1956 | version = "0.22.4" 1957 | source = "registry+https://github.com/rust-lang/crates.io-index" 1958 | checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" 1959 | dependencies = [ 1960 | "log", 1961 | "ring", 1962 | "rustls-pki-types", 1963 | "rustls-webpki 0.102.8", 1964 | "subtle", 1965 | "zeroize", 1966 | ] 1967 | 1968 | [[package]] 1969 | name = "rustls-pemfile" 1970 | version = "1.0.4" 1971 | source = "registry+https://github.com/rust-lang/crates.io-index" 1972 | checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" 1973 | dependencies = [ 1974 | "base64 0.21.7", 1975 | ] 1976 | 1977 | [[package]] 1978 | name = "rustls-pki-types" 1979 | version = "1.12.0" 1980 | source = "registry+https://github.com/rust-lang/crates.io-index" 1981 | checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" 1982 | dependencies = [ 1983 | "zeroize", 1984 | ] 1985 | 1986 | [[package]] 1987 | name = "rustls-webpki" 1988 | version = "0.101.7" 1989 | source = "registry+https://github.com/rust-lang/crates.io-index" 1990 | checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" 1991 | dependencies = [ 1992 | "ring", 1993 | "untrusted", 1994 | ] 1995 | 1996 | [[package]] 1997 | name = "rustls-webpki" 1998 | version = "0.102.8" 1999 | source = "registry+https://github.com/rust-lang/crates.io-index" 2000 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 2001 | dependencies = [ 2002 | "ring", 2003 | "rustls-pki-types", 2004 | "untrusted", 2005 | ] 2006 | 2007 | [[package]] 2008 | name = "rustversion" 2009 | version = "1.0.21" 2010 | source = "registry+https://github.com/rust-lang/crates.io-index" 2011 | checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" 2012 | 2013 | [[package]] 2014 | name = "ryu" 2015 | version = "1.0.20" 2016 | source = "registry+https://github.com/rust-lang/crates.io-index" 2017 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 2018 | 2019 | [[package]] 2020 | name = "safe_arch" 2021 | version = "0.7.4" 2022 | source = "registry+https://github.com/rust-lang/crates.io-index" 2023 | checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" 2024 | dependencies = [ 2025 | "bytemuck", 2026 | ] 2027 | 2028 | [[package]] 2029 | name = "same-file" 2030 | version = "1.0.6" 2031 | source = "registry+https://github.com/rust-lang/crates.io-index" 2032 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 2033 | dependencies = [ 2034 | "winapi-util", 2035 | ] 2036 | 2037 | [[package]] 2038 | name = "scopeguard" 2039 | version = "1.2.0" 2040 | source = "registry+https://github.com/rust-lang/crates.io-index" 2041 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 2042 | 2043 | [[package]] 2044 | name = "scraper" 2045 | version = "0.19.1" 2046 | source = "registry+https://github.com/rust-lang/crates.io-index" 2047 | checksum = "761fb705fdf625482d2ed91d3f0559dcfeab2798fe2771c69560a774865d0802" 2048 | dependencies = [ 2049 | "ahash", 2050 | "cssparser", 2051 | "ego-tree", 2052 | "getopts", 2053 | "html5ever", 2054 | "indexmap", 2055 | "once_cell", 2056 | "selectors", 2057 | "tendril", 2058 | ] 2059 | 2060 | [[package]] 2061 | name = "sct" 2062 | version = "0.7.1" 2063 | source = "registry+https://github.com/rust-lang/crates.io-index" 2064 | checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" 2065 | dependencies = [ 2066 | "ring", 2067 | "untrusted", 2068 | ] 2069 | 2070 | [[package]] 2071 | name = "secrecy" 2072 | version = "0.8.0" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" 2075 | dependencies = [ 2076 | "serde", 2077 | "zeroize", 2078 | ] 2079 | 2080 | [[package]] 2081 | name = "selectors" 2082 | version = "0.25.0" 2083 | source = "registry+https://github.com/rust-lang/crates.io-index" 2084 | checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" 2085 | dependencies = [ 2086 | "bitflags 2.9.1", 2087 | "cssparser", 2088 | "derive_more", 2089 | "fxhash", 2090 | "log", 2091 | "new_debug_unreachable", 2092 | "phf 0.10.1", 2093 | "phf_codegen 0.10.0", 2094 | "precomputed-hash", 2095 | "servo_arc", 2096 | "smallvec", 2097 | ] 2098 | 2099 | [[package]] 2100 | name = "semver" 2101 | version = "1.0.26" 2102 | source = "registry+https://github.com/rust-lang/crates.io-index" 2103 | checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 2104 | dependencies = [ 2105 | "serde", 2106 | ] 2107 | 2108 | [[package]] 2109 | name = "serde" 2110 | version = "1.0.219" 2111 | source = "registry+https://github.com/rust-lang/crates.io-index" 2112 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 2113 | dependencies = [ 2114 | "serde_derive", 2115 | ] 2116 | 2117 | [[package]] 2118 | name = "serde_cow" 2119 | version = "0.1.2" 2120 | source = "registry+https://github.com/rust-lang/crates.io-index" 2121 | checksum = "1e7bbbec7196bfde255ab54b65e34087c0849629280028238e67ee25d6a4b7da" 2122 | dependencies = [ 2123 | "serde", 2124 | ] 2125 | 2126 | [[package]] 2127 | name = "serde_derive" 2128 | version = "1.0.219" 2129 | source = "registry+https://github.com/rust-lang/crates.io-index" 2130 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 2131 | dependencies = [ 2132 | "proc-macro2", 2133 | "quote", 2134 | "syn 2.0.101", 2135 | ] 2136 | 2137 | [[package]] 2138 | name = "serde_json" 2139 | version = "1.0.140" 2140 | source = "registry+https://github.com/rust-lang/crates.io-index" 2141 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 2142 | dependencies = [ 2143 | "itoa", 2144 | "memchr", 2145 | "ryu", 2146 | "serde", 2147 | ] 2148 | 2149 | [[package]] 2150 | name = "serde_path_to_error" 2151 | version = "0.1.17" 2152 | source = "registry+https://github.com/rust-lang/crates.io-index" 2153 | checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" 2154 | dependencies = [ 2155 | "itoa", 2156 | "serde", 2157 | ] 2158 | 2159 | [[package]] 2160 | name = "serde_urlencoded" 2161 | version = "0.7.1" 2162 | source = "registry+https://github.com/rust-lang/crates.io-index" 2163 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 2164 | dependencies = [ 2165 | "form_urlencoded", 2166 | "itoa", 2167 | "ryu", 2168 | "serde", 2169 | ] 2170 | 2171 | [[package]] 2172 | name = "serenity" 2173 | version = "0.12.4" 2174 | source = "registry+https://github.com/rust-lang/crates.io-index" 2175 | checksum = "3d72ec4323681bf9a3cabe40fd080abc2435859b502a1b5aa9bf693f125bfa76" 2176 | dependencies = [ 2177 | "arrayvec", 2178 | "async-trait", 2179 | "base64 0.22.1", 2180 | "bitflags 2.9.1", 2181 | "bytes", 2182 | "chrono", 2183 | "dashmap", 2184 | "flate2", 2185 | "futures", 2186 | "fxhash", 2187 | "mime_guess", 2188 | "parking_lot", 2189 | "percent-encoding", 2190 | "reqwest", 2191 | "secrecy", 2192 | "serde", 2193 | "serde_cow", 2194 | "serde_json", 2195 | "time", 2196 | "tokio", 2197 | "tokio-tungstenite", 2198 | "tracing", 2199 | "typemap_rev", 2200 | "typesize", 2201 | "url", 2202 | ] 2203 | 2204 | [[package]] 2205 | name = "servo_arc" 2206 | version = "0.3.0" 2207 | source = "registry+https://github.com/rust-lang/crates.io-index" 2208 | checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" 2209 | dependencies = [ 2210 | "stable_deref_trait", 2211 | ] 2212 | 2213 | [[package]] 2214 | name = "sha1" 2215 | version = "0.10.6" 2216 | source = "registry+https://github.com/rust-lang/crates.io-index" 2217 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 2218 | dependencies = [ 2219 | "cfg-if", 2220 | "cpufeatures", 2221 | "digest", 2222 | ] 2223 | 2224 | [[package]] 2225 | name = "sharded-slab" 2226 | version = "0.1.7" 2227 | source = "registry+https://github.com/rust-lang/crates.io-index" 2228 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 2229 | dependencies = [ 2230 | "lazy_static", 2231 | ] 2232 | 2233 | [[package]] 2234 | name = "shlex" 2235 | version = "1.3.0" 2236 | source = "registry+https://github.com/rust-lang/crates.io-index" 2237 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 2238 | 2239 | [[package]] 2240 | name = "signal-hook-registry" 2241 | version = "1.4.5" 2242 | source = "registry+https://github.com/rust-lang/crates.io-index" 2243 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 2244 | dependencies = [ 2245 | "libc", 2246 | ] 2247 | 2248 | [[package]] 2249 | name = "simba" 2250 | version = "0.8.1" 2251 | source = "registry+https://github.com/rust-lang/crates.io-index" 2252 | checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" 2253 | dependencies = [ 2254 | "approx", 2255 | "num-complex", 2256 | "num-traits", 2257 | "paste", 2258 | "wide", 2259 | ] 2260 | 2261 | [[package]] 2262 | name = "simd-adler32" 2263 | version = "0.3.7" 2264 | source = "registry+https://github.com/rust-lang/crates.io-index" 2265 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 2266 | 2267 | [[package]] 2268 | name = "siphasher" 2269 | version = "0.3.11" 2270 | source = "registry+https://github.com/rust-lang/crates.io-index" 2271 | checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" 2272 | 2273 | [[package]] 2274 | name = "siphasher" 2275 | version = "1.0.1" 2276 | source = "registry+https://github.com/rust-lang/crates.io-index" 2277 | checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" 2278 | 2279 | [[package]] 2280 | name = "skeptic" 2281 | version = "0.13.7" 2282 | source = "registry+https://github.com/rust-lang/crates.io-index" 2283 | checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" 2284 | dependencies = [ 2285 | "bytecount", 2286 | "cargo_metadata", 2287 | "error-chain", 2288 | "glob", 2289 | "pulldown-cmark", 2290 | "tempfile", 2291 | "walkdir", 2292 | ] 2293 | 2294 | [[package]] 2295 | name = "slab" 2296 | version = "0.4.9" 2297 | source = "registry+https://github.com/rust-lang/crates.io-index" 2298 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 2299 | dependencies = [ 2300 | "autocfg", 2301 | ] 2302 | 2303 | [[package]] 2304 | name = "smallvec" 2305 | version = "1.15.0" 2306 | source = "registry+https://github.com/rust-lang/crates.io-index" 2307 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 2308 | 2309 | [[package]] 2310 | name = "socket2" 2311 | version = "0.5.10" 2312 | source = "registry+https://github.com/rust-lang/crates.io-index" 2313 | checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" 2314 | dependencies = [ 2315 | "libc", 2316 | "windows-sys 0.52.0", 2317 | ] 2318 | 2319 | [[package]] 2320 | name = "stable_deref_trait" 2321 | version = "1.2.0" 2322 | source = "registry+https://github.com/rust-lang/crates.io-index" 2323 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 2324 | 2325 | [[package]] 2326 | name = "string_cache" 2327 | version = "0.8.9" 2328 | source = "registry+https://github.com/rust-lang/crates.io-index" 2329 | checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" 2330 | dependencies = [ 2331 | "new_debug_unreachable", 2332 | "parking_lot", 2333 | "phf_shared 0.11.3", 2334 | "precomputed-hash", 2335 | "serde", 2336 | ] 2337 | 2338 | [[package]] 2339 | name = "string_cache_codegen" 2340 | version = "0.5.4" 2341 | source = "registry+https://github.com/rust-lang/crates.io-index" 2342 | checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" 2343 | dependencies = [ 2344 | "phf_generator 0.11.3", 2345 | "phf_shared 0.11.3", 2346 | "proc-macro2", 2347 | "quote", 2348 | ] 2349 | 2350 | [[package]] 2351 | name = "strsim" 2352 | version = "0.11.1" 2353 | source = "registry+https://github.com/rust-lang/crates.io-index" 2354 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 2355 | 2356 | [[package]] 2357 | name = "subtle" 2358 | version = "2.6.1" 2359 | source = "registry+https://github.com/rust-lang/crates.io-index" 2360 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 2361 | 2362 | [[package]] 2363 | name = "syn" 2364 | version = "1.0.109" 2365 | source = "registry+https://github.com/rust-lang/crates.io-index" 2366 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 2367 | dependencies = [ 2368 | "proc-macro2", 2369 | "quote", 2370 | "unicode-ident", 2371 | ] 2372 | 2373 | [[package]] 2374 | name = "syn" 2375 | version = "2.0.101" 2376 | source = "registry+https://github.com/rust-lang/crates.io-index" 2377 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 2378 | dependencies = [ 2379 | "proc-macro2", 2380 | "quote", 2381 | "unicode-ident", 2382 | ] 2383 | 2384 | [[package]] 2385 | name = "sync_wrapper" 2386 | version = "0.1.2" 2387 | source = "registry+https://github.com/rust-lang/crates.io-index" 2388 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 2389 | 2390 | [[package]] 2391 | name = "sync_wrapper" 2392 | version = "1.0.2" 2393 | source = "registry+https://github.com/rust-lang/crates.io-index" 2394 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 2395 | 2396 | [[package]] 2397 | name = "synstructure" 2398 | version = "0.13.2" 2399 | source = "registry+https://github.com/rust-lang/crates.io-index" 2400 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 2401 | dependencies = [ 2402 | "proc-macro2", 2403 | "quote", 2404 | "syn 2.0.101", 2405 | ] 2406 | 2407 | [[package]] 2408 | name = "system-configuration" 2409 | version = "0.5.1" 2410 | source = "registry+https://github.com/rust-lang/crates.io-index" 2411 | checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" 2412 | dependencies = [ 2413 | "bitflags 1.3.2", 2414 | "core-foundation", 2415 | "system-configuration-sys", 2416 | ] 2417 | 2418 | [[package]] 2419 | name = "system-configuration-sys" 2420 | version = "0.5.0" 2421 | source = "registry+https://github.com/rust-lang/crates.io-index" 2422 | checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" 2423 | dependencies = [ 2424 | "core-foundation-sys", 2425 | "libc", 2426 | ] 2427 | 2428 | [[package]] 2429 | name = "tagptr" 2430 | version = "0.2.0" 2431 | source = "registry+https://github.com/rust-lang/crates.io-index" 2432 | checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" 2433 | 2434 | [[package]] 2435 | name = "tempfile" 2436 | version = "3.20.0" 2437 | source = "registry+https://github.com/rust-lang/crates.io-index" 2438 | checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" 2439 | dependencies = [ 2440 | "fastrand", 2441 | "getrandom 0.3.3", 2442 | "once_cell", 2443 | "rustix", 2444 | "windows-sys 0.59.0", 2445 | ] 2446 | 2447 | [[package]] 2448 | name = "tendril" 2449 | version = "0.4.3" 2450 | source = "registry+https://github.com/rust-lang/crates.io-index" 2451 | checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" 2452 | dependencies = [ 2453 | "futf", 2454 | "mac", 2455 | "utf-8", 2456 | ] 2457 | 2458 | [[package]] 2459 | name = "thiserror" 2460 | version = "1.0.69" 2461 | source = "registry+https://github.com/rust-lang/crates.io-index" 2462 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 2463 | dependencies = [ 2464 | "thiserror-impl 1.0.69", 2465 | ] 2466 | 2467 | [[package]] 2468 | name = "thiserror" 2469 | version = "2.0.12" 2470 | source = "registry+https://github.com/rust-lang/crates.io-index" 2471 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 2472 | dependencies = [ 2473 | "thiserror-impl 2.0.12", 2474 | ] 2475 | 2476 | [[package]] 2477 | name = "thiserror-impl" 2478 | version = "1.0.69" 2479 | source = "registry+https://github.com/rust-lang/crates.io-index" 2480 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 2481 | dependencies = [ 2482 | "proc-macro2", 2483 | "quote", 2484 | "syn 2.0.101", 2485 | ] 2486 | 2487 | [[package]] 2488 | name = "thiserror-impl" 2489 | version = "2.0.12" 2490 | source = "registry+https://github.com/rust-lang/crates.io-index" 2491 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 2492 | dependencies = [ 2493 | "proc-macro2", 2494 | "quote", 2495 | "syn 2.0.101", 2496 | ] 2497 | 2498 | [[package]] 2499 | name = "thread_local" 2500 | version = "1.1.8" 2501 | source = "registry+https://github.com/rust-lang/crates.io-index" 2502 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 2503 | dependencies = [ 2504 | "cfg-if", 2505 | "once_cell", 2506 | ] 2507 | 2508 | [[package]] 2509 | name = "time" 2510 | version = "0.3.41" 2511 | source = "registry+https://github.com/rust-lang/crates.io-index" 2512 | checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" 2513 | dependencies = [ 2514 | "deranged", 2515 | "itoa", 2516 | "num-conv", 2517 | "powerfmt", 2518 | "serde", 2519 | "time-core", 2520 | "time-macros", 2521 | ] 2522 | 2523 | [[package]] 2524 | name = "time-core" 2525 | version = "0.1.4" 2526 | source = "registry+https://github.com/rust-lang/crates.io-index" 2527 | checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" 2528 | 2529 | [[package]] 2530 | name = "time-macros" 2531 | version = "0.2.22" 2532 | source = "registry+https://github.com/rust-lang/crates.io-index" 2533 | checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" 2534 | dependencies = [ 2535 | "num-conv", 2536 | "time-core", 2537 | ] 2538 | 2539 | [[package]] 2540 | name = "tinystr" 2541 | version = "0.8.1" 2542 | source = "registry+https://github.com/rust-lang/crates.io-index" 2543 | checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" 2544 | dependencies = [ 2545 | "displaydoc", 2546 | "zerovec", 2547 | ] 2548 | 2549 | [[package]] 2550 | name = "tokio" 2551 | version = "1.45.1" 2552 | source = "registry+https://github.com/rust-lang/crates.io-index" 2553 | checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" 2554 | dependencies = [ 2555 | "backtrace", 2556 | "bytes", 2557 | "libc", 2558 | "mio", 2559 | "parking_lot", 2560 | "pin-project-lite", 2561 | "signal-hook-registry", 2562 | "socket2", 2563 | "tokio-macros", 2564 | "windows-sys 0.52.0", 2565 | ] 2566 | 2567 | [[package]] 2568 | name = "tokio-macros" 2569 | version = "2.5.0" 2570 | source = "registry+https://github.com/rust-lang/crates.io-index" 2571 | checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" 2572 | dependencies = [ 2573 | "proc-macro2", 2574 | "quote", 2575 | "syn 2.0.101", 2576 | ] 2577 | 2578 | [[package]] 2579 | name = "tokio-rustls" 2580 | version = "0.24.1" 2581 | source = "registry+https://github.com/rust-lang/crates.io-index" 2582 | checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" 2583 | dependencies = [ 2584 | "rustls 0.21.12", 2585 | "tokio", 2586 | ] 2587 | 2588 | [[package]] 2589 | name = "tokio-rustls" 2590 | version = "0.25.0" 2591 | source = "registry+https://github.com/rust-lang/crates.io-index" 2592 | checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" 2593 | dependencies = [ 2594 | "rustls 0.22.4", 2595 | "rustls-pki-types", 2596 | "tokio", 2597 | ] 2598 | 2599 | [[package]] 2600 | name = "tokio-tungstenite" 2601 | version = "0.21.0" 2602 | source = "registry+https://github.com/rust-lang/crates.io-index" 2603 | checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" 2604 | dependencies = [ 2605 | "futures-util", 2606 | "log", 2607 | "rustls 0.22.4", 2608 | "rustls-pki-types", 2609 | "tokio", 2610 | "tokio-rustls 0.25.0", 2611 | "tungstenite", 2612 | "webpki-roots 0.26.11", 2613 | ] 2614 | 2615 | [[package]] 2616 | name = "tokio-util" 2617 | version = "0.7.15" 2618 | source = "registry+https://github.com/rust-lang/crates.io-index" 2619 | checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" 2620 | dependencies = [ 2621 | "bytes", 2622 | "futures-core", 2623 | "futures-sink", 2624 | "pin-project-lite", 2625 | "tokio", 2626 | ] 2627 | 2628 | [[package]] 2629 | name = "tokio_schedule" 2630 | version = "0.3.2" 2631 | source = "registry+https://github.com/rust-lang/crates.io-index" 2632 | checksum = "61c291c554da3518d6ef69c76ea35aabc78f736185a16b6017f6d1c224dac2e0" 2633 | dependencies = [ 2634 | "chrono", 2635 | "tokio", 2636 | ] 2637 | 2638 | [[package]] 2639 | name = "tower" 2640 | version = "0.5.2" 2641 | source = "registry+https://github.com/rust-lang/crates.io-index" 2642 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 2643 | dependencies = [ 2644 | "futures-core", 2645 | "futures-util", 2646 | "pin-project-lite", 2647 | "sync_wrapper 1.0.2", 2648 | "tokio", 2649 | "tower-layer", 2650 | "tower-service", 2651 | "tracing", 2652 | ] 2653 | 2654 | [[package]] 2655 | name = "tower-layer" 2656 | version = "0.3.3" 2657 | source = "registry+https://github.com/rust-lang/crates.io-index" 2658 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 2659 | 2660 | [[package]] 2661 | name = "tower-service" 2662 | version = "0.3.3" 2663 | source = "registry+https://github.com/rust-lang/crates.io-index" 2664 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 2665 | 2666 | [[package]] 2667 | name = "tracing" 2668 | version = "0.1.41" 2669 | source = "registry+https://github.com/rust-lang/crates.io-index" 2670 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 2671 | dependencies = [ 2672 | "log", 2673 | "pin-project-lite", 2674 | "tracing-attributes", 2675 | "tracing-core", 2676 | ] 2677 | 2678 | [[package]] 2679 | name = "tracing-attributes" 2680 | version = "0.1.28" 2681 | source = "registry+https://github.com/rust-lang/crates.io-index" 2682 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 2683 | dependencies = [ 2684 | "proc-macro2", 2685 | "quote", 2686 | "syn 2.0.101", 2687 | ] 2688 | 2689 | [[package]] 2690 | name = "tracing-core" 2691 | version = "0.1.33" 2692 | source = "registry+https://github.com/rust-lang/crates.io-index" 2693 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 2694 | dependencies = [ 2695 | "once_cell", 2696 | "valuable", 2697 | ] 2698 | 2699 | [[package]] 2700 | name = "tracing-error" 2701 | version = "0.2.1" 2702 | source = "registry+https://github.com/rust-lang/crates.io-index" 2703 | checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" 2704 | dependencies = [ 2705 | "tracing", 2706 | "tracing-subscriber", 2707 | ] 2708 | 2709 | [[package]] 2710 | name = "tracing-subscriber" 2711 | version = "0.3.19" 2712 | source = "registry+https://github.com/rust-lang/crates.io-index" 2713 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 2714 | dependencies = [ 2715 | "sharded-slab", 2716 | "thread_local", 2717 | "tracing-core", 2718 | ] 2719 | 2720 | [[package]] 2721 | name = "triomphe" 2722 | version = "0.1.14" 2723 | source = "registry+https://github.com/rust-lang/crates.io-index" 2724 | checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" 2725 | 2726 | [[package]] 2727 | name = "try-lock" 2728 | version = "0.2.5" 2729 | source = "registry+https://github.com/rust-lang/crates.io-index" 2730 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 2731 | 2732 | [[package]] 2733 | name = "ttf-parser" 2734 | version = "0.25.1" 2735 | source = "registry+https://github.com/rust-lang/crates.io-index" 2736 | checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31" 2737 | 2738 | [[package]] 2739 | name = "tungstenite" 2740 | version = "0.21.0" 2741 | source = "registry+https://github.com/rust-lang/crates.io-index" 2742 | checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" 2743 | dependencies = [ 2744 | "byteorder", 2745 | "bytes", 2746 | "data-encoding", 2747 | "http 1.3.1", 2748 | "httparse", 2749 | "log", 2750 | "rand", 2751 | "rustls 0.22.4", 2752 | "rustls-pki-types", 2753 | "sha1", 2754 | "thiserror 1.0.69", 2755 | "url", 2756 | "utf-8", 2757 | ] 2758 | 2759 | [[package]] 2760 | name = "typemap_rev" 2761 | version = "0.3.0" 2762 | source = "registry+https://github.com/rust-lang/crates.io-index" 2763 | checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" 2764 | 2765 | [[package]] 2766 | name = "typenum" 2767 | version = "1.18.0" 2768 | source = "registry+https://github.com/rust-lang/crates.io-index" 2769 | checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" 2770 | 2771 | [[package]] 2772 | name = "typesize" 2773 | version = "0.1.14" 2774 | source = "registry+https://github.com/rust-lang/crates.io-index" 2775 | checksum = "7da66c62c5b7017a2787e77373c03e6a5aafde77a73bff1ff96e91cd2e128179" 2776 | dependencies = [ 2777 | "chrono", 2778 | "dashmap", 2779 | "hashbrown 0.14.5", 2780 | "mini-moka", 2781 | "parking_lot", 2782 | "secrecy", 2783 | "serde_json", 2784 | "time", 2785 | "typesize-derive", 2786 | "url", 2787 | ] 2788 | 2789 | [[package]] 2790 | name = "typesize-derive" 2791 | version = "0.1.11" 2792 | source = "registry+https://github.com/rust-lang/crates.io-index" 2793 | checksum = "536b6812192bda8551cfa0e52524e328c6a951b48e66529ee4522d6c721243d6" 2794 | dependencies = [ 2795 | "proc-macro2", 2796 | "quote", 2797 | "syn 2.0.101", 2798 | ] 2799 | 2800 | [[package]] 2801 | name = "unicase" 2802 | version = "2.8.1" 2803 | source = "registry+https://github.com/rust-lang/crates.io-index" 2804 | checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" 2805 | 2806 | [[package]] 2807 | name = "unicode-ident" 2808 | version = "1.0.18" 2809 | source = "registry+https://github.com/rust-lang/crates.io-index" 2810 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 2811 | 2812 | [[package]] 2813 | name = "unicode-width" 2814 | version = "0.1.14" 2815 | source = "registry+https://github.com/rust-lang/crates.io-index" 2816 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 2817 | 2818 | [[package]] 2819 | name = "untrusted" 2820 | version = "0.9.0" 2821 | source = "registry+https://github.com/rust-lang/crates.io-index" 2822 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2823 | 2824 | [[package]] 2825 | name = "url" 2826 | version = "2.5.4" 2827 | source = "registry+https://github.com/rust-lang/crates.io-index" 2828 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2829 | dependencies = [ 2830 | "form_urlencoded", 2831 | "idna", 2832 | "percent-encoding", 2833 | "serde", 2834 | ] 2835 | 2836 | [[package]] 2837 | name = "utf-8" 2838 | version = "0.7.6" 2839 | source = "registry+https://github.com/rust-lang/crates.io-index" 2840 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 2841 | 2842 | [[package]] 2843 | name = "utf8_iter" 2844 | version = "1.0.4" 2845 | source = "registry+https://github.com/rust-lang/crates.io-index" 2846 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2847 | 2848 | [[package]] 2849 | name = "valuable" 2850 | version = "0.1.1" 2851 | source = "registry+https://github.com/rust-lang/crates.io-index" 2852 | checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 2853 | 2854 | [[package]] 2855 | name = "version_check" 2856 | version = "0.9.5" 2857 | source = "registry+https://github.com/rust-lang/crates.io-index" 2858 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2859 | 2860 | [[package]] 2861 | name = "walkdir" 2862 | version = "2.5.0" 2863 | source = "registry+https://github.com/rust-lang/crates.io-index" 2864 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 2865 | dependencies = [ 2866 | "same-file", 2867 | "winapi-util", 2868 | ] 2869 | 2870 | [[package]] 2871 | name = "want" 2872 | version = "0.3.1" 2873 | source = "registry+https://github.com/rust-lang/crates.io-index" 2874 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2875 | dependencies = [ 2876 | "try-lock", 2877 | ] 2878 | 2879 | [[package]] 2880 | name = "wasi" 2881 | version = "0.11.0+wasi-snapshot-preview1" 2882 | source = "registry+https://github.com/rust-lang/crates.io-index" 2883 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2884 | 2885 | [[package]] 2886 | name = "wasi" 2887 | version = "0.14.2+wasi-0.2.4" 2888 | source = "registry+https://github.com/rust-lang/crates.io-index" 2889 | checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" 2890 | dependencies = [ 2891 | "wit-bindgen-rt", 2892 | ] 2893 | 2894 | [[package]] 2895 | name = "wasm-bindgen" 2896 | version = "0.2.100" 2897 | source = "registry+https://github.com/rust-lang/crates.io-index" 2898 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 2899 | dependencies = [ 2900 | "cfg-if", 2901 | "once_cell", 2902 | "rustversion", 2903 | "wasm-bindgen-macro", 2904 | ] 2905 | 2906 | [[package]] 2907 | name = "wasm-bindgen-backend" 2908 | version = "0.2.100" 2909 | source = "registry+https://github.com/rust-lang/crates.io-index" 2910 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 2911 | dependencies = [ 2912 | "bumpalo", 2913 | "log", 2914 | "proc-macro2", 2915 | "quote", 2916 | "syn 2.0.101", 2917 | "wasm-bindgen-shared", 2918 | ] 2919 | 2920 | [[package]] 2921 | name = "wasm-bindgen-futures" 2922 | version = "0.4.50" 2923 | source = "registry+https://github.com/rust-lang/crates.io-index" 2924 | checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" 2925 | dependencies = [ 2926 | "cfg-if", 2927 | "js-sys", 2928 | "once_cell", 2929 | "wasm-bindgen", 2930 | "web-sys", 2931 | ] 2932 | 2933 | [[package]] 2934 | name = "wasm-bindgen-macro" 2935 | version = "0.2.100" 2936 | source = "registry+https://github.com/rust-lang/crates.io-index" 2937 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 2938 | dependencies = [ 2939 | "quote", 2940 | "wasm-bindgen-macro-support", 2941 | ] 2942 | 2943 | [[package]] 2944 | name = "wasm-bindgen-macro-support" 2945 | version = "0.2.100" 2946 | source = "registry+https://github.com/rust-lang/crates.io-index" 2947 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 2948 | dependencies = [ 2949 | "proc-macro2", 2950 | "quote", 2951 | "syn 2.0.101", 2952 | "wasm-bindgen-backend", 2953 | "wasm-bindgen-shared", 2954 | ] 2955 | 2956 | [[package]] 2957 | name = "wasm-bindgen-shared" 2958 | version = "0.2.100" 2959 | source = "registry+https://github.com/rust-lang/crates.io-index" 2960 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 2961 | dependencies = [ 2962 | "unicode-ident", 2963 | ] 2964 | 2965 | [[package]] 2966 | name = "wasm-streams" 2967 | version = "0.4.2" 2968 | source = "registry+https://github.com/rust-lang/crates.io-index" 2969 | checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" 2970 | dependencies = [ 2971 | "futures-util", 2972 | "js-sys", 2973 | "wasm-bindgen", 2974 | "wasm-bindgen-futures", 2975 | "web-sys", 2976 | ] 2977 | 2978 | [[package]] 2979 | name = "web-sys" 2980 | version = "0.3.77" 2981 | source = "registry+https://github.com/rust-lang/crates.io-index" 2982 | checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" 2983 | dependencies = [ 2984 | "js-sys", 2985 | "wasm-bindgen", 2986 | ] 2987 | 2988 | [[package]] 2989 | name = "webpki-roots" 2990 | version = "0.25.4" 2991 | source = "registry+https://github.com/rust-lang/crates.io-index" 2992 | checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" 2993 | 2994 | [[package]] 2995 | name = "webpki-roots" 2996 | version = "0.26.11" 2997 | source = "registry+https://github.com/rust-lang/crates.io-index" 2998 | checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" 2999 | dependencies = [ 3000 | "webpki-roots 1.0.0", 3001 | ] 3002 | 3003 | [[package]] 3004 | name = "webpki-roots" 3005 | version = "1.0.0" 3006 | source = "registry+https://github.com/rust-lang/crates.io-index" 3007 | checksum = "2853738d1cc4f2da3a225c18ec6c3721abb31961096e9dbf5ab35fa88b19cfdb" 3008 | dependencies = [ 3009 | "rustls-pki-types", 3010 | ] 3011 | 3012 | [[package]] 3013 | name = "wide" 3014 | version = "0.7.32" 3015 | source = "registry+https://github.com/rust-lang/crates.io-index" 3016 | checksum = "41b5576b9a81633f3e8df296ce0063042a73507636cbe956c61133dd7034ab22" 3017 | dependencies = [ 3018 | "bytemuck", 3019 | "safe_arch", 3020 | ] 3021 | 3022 | [[package]] 3023 | name = "winapi-util" 3024 | version = "0.1.9" 3025 | source = "registry+https://github.com/rust-lang/crates.io-index" 3026 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 3027 | dependencies = [ 3028 | "windows-sys 0.59.0", 3029 | ] 3030 | 3031 | [[package]] 3032 | name = "windows-core" 3033 | version = "0.61.2" 3034 | source = "registry+https://github.com/rust-lang/crates.io-index" 3035 | checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" 3036 | dependencies = [ 3037 | "windows-implement", 3038 | "windows-interface", 3039 | "windows-link", 3040 | "windows-result", 3041 | "windows-strings", 3042 | ] 3043 | 3044 | [[package]] 3045 | name = "windows-implement" 3046 | version = "0.60.0" 3047 | source = "registry+https://github.com/rust-lang/crates.io-index" 3048 | checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" 3049 | dependencies = [ 3050 | "proc-macro2", 3051 | "quote", 3052 | "syn 2.0.101", 3053 | ] 3054 | 3055 | [[package]] 3056 | name = "windows-interface" 3057 | version = "0.59.1" 3058 | source = "registry+https://github.com/rust-lang/crates.io-index" 3059 | checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" 3060 | dependencies = [ 3061 | "proc-macro2", 3062 | "quote", 3063 | "syn 2.0.101", 3064 | ] 3065 | 3066 | [[package]] 3067 | name = "windows-link" 3068 | version = "0.1.1" 3069 | source = "registry+https://github.com/rust-lang/crates.io-index" 3070 | checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" 3071 | 3072 | [[package]] 3073 | name = "windows-result" 3074 | version = "0.3.4" 3075 | source = "registry+https://github.com/rust-lang/crates.io-index" 3076 | checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" 3077 | dependencies = [ 3078 | "windows-link", 3079 | ] 3080 | 3081 | [[package]] 3082 | name = "windows-strings" 3083 | version = "0.4.2" 3084 | source = "registry+https://github.com/rust-lang/crates.io-index" 3085 | checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" 3086 | dependencies = [ 3087 | "windows-link", 3088 | ] 3089 | 3090 | [[package]] 3091 | name = "windows-sys" 3092 | version = "0.48.0" 3093 | source = "registry+https://github.com/rust-lang/crates.io-index" 3094 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 3095 | dependencies = [ 3096 | "windows-targets 0.48.5", 3097 | ] 3098 | 3099 | [[package]] 3100 | name = "windows-sys" 3101 | version = "0.52.0" 3102 | source = "registry+https://github.com/rust-lang/crates.io-index" 3103 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 3104 | dependencies = [ 3105 | "windows-targets 0.52.6", 3106 | ] 3107 | 3108 | [[package]] 3109 | name = "windows-sys" 3110 | version = "0.59.0" 3111 | source = "registry+https://github.com/rust-lang/crates.io-index" 3112 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 3113 | dependencies = [ 3114 | "windows-targets 0.52.6", 3115 | ] 3116 | 3117 | [[package]] 3118 | name = "windows-targets" 3119 | version = "0.48.5" 3120 | source = "registry+https://github.com/rust-lang/crates.io-index" 3121 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 3122 | dependencies = [ 3123 | "windows_aarch64_gnullvm 0.48.5", 3124 | "windows_aarch64_msvc 0.48.5", 3125 | "windows_i686_gnu 0.48.5", 3126 | "windows_i686_msvc 0.48.5", 3127 | "windows_x86_64_gnu 0.48.5", 3128 | "windows_x86_64_gnullvm 0.48.5", 3129 | "windows_x86_64_msvc 0.48.5", 3130 | ] 3131 | 3132 | [[package]] 3133 | name = "windows-targets" 3134 | version = "0.52.6" 3135 | source = "registry+https://github.com/rust-lang/crates.io-index" 3136 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 3137 | dependencies = [ 3138 | "windows_aarch64_gnullvm 0.52.6", 3139 | "windows_aarch64_msvc 0.52.6", 3140 | "windows_i686_gnu 0.52.6", 3141 | "windows_i686_gnullvm", 3142 | "windows_i686_msvc 0.52.6", 3143 | "windows_x86_64_gnu 0.52.6", 3144 | "windows_x86_64_gnullvm 0.52.6", 3145 | "windows_x86_64_msvc 0.52.6", 3146 | ] 3147 | 3148 | [[package]] 3149 | name = "windows_aarch64_gnullvm" 3150 | version = "0.48.5" 3151 | source = "registry+https://github.com/rust-lang/crates.io-index" 3152 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 3153 | 3154 | [[package]] 3155 | name = "windows_aarch64_gnullvm" 3156 | version = "0.52.6" 3157 | source = "registry+https://github.com/rust-lang/crates.io-index" 3158 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 3159 | 3160 | [[package]] 3161 | name = "windows_aarch64_msvc" 3162 | version = "0.48.5" 3163 | source = "registry+https://github.com/rust-lang/crates.io-index" 3164 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 3165 | 3166 | [[package]] 3167 | name = "windows_aarch64_msvc" 3168 | version = "0.52.6" 3169 | source = "registry+https://github.com/rust-lang/crates.io-index" 3170 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 3171 | 3172 | [[package]] 3173 | name = "windows_i686_gnu" 3174 | version = "0.48.5" 3175 | source = "registry+https://github.com/rust-lang/crates.io-index" 3176 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 3177 | 3178 | [[package]] 3179 | name = "windows_i686_gnu" 3180 | version = "0.52.6" 3181 | source = "registry+https://github.com/rust-lang/crates.io-index" 3182 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 3183 | 3184 | [[package]] 3185 | name = "windows_i686_gnullvm" 3186 | version = "0.52.6" 3187 | source = "registry+https://github.com/rust-lang/crates.io-index" 3188 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 3189 | 3190 | [[package]] 3191 | name = "windows_i686_msvc" 3192 | version = "0.48.5" 3193 | source = "registry+https://github.com/rust-lang/crates.io-index" 3194 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 3195 | 3196 | [[package]] 3197 | name = "windows_i686_msvc" 3198 | version = "0.52.6" 3199 | source = "registry+https://github.com/rust-lang/crates.io-index" 3200 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 3201 | 3202 | [[package]] 3203 | name = "windows_x86_64_gnu" 3204 | version = "0.48.5" 3205 | source = "registry+https://github.com/rust-lang/crates.io-index" 3206 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 3207 | 3208 | [[package]] 3209 | name = "windows_x86_64_gnu" 3210 | version = "0.52.6" 3211 | source = "registry+https://github.com/rust-lang/crates.io-index" 3212 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 3213 | 3214 | [[package]] 3215 | name = "windows_x86_64_gnullvm" 3216 | version = "0.48.5" 3217 | source = "registry+https://github.com/rust-lang/crates.io-index" 3218 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 3219 | 3220 | [[package]] 3221 | name = "windows_x86_64_gnullvm" 3222 | version = "0.52.6" 3223 | source = "registry+https://github.com/rust-lang/crates.io-index" 3224 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 3225 | 3226 | [[package]] 3227 | name = "windows_x86_64_msvc" 3228 | version = "0.48.5" 3229 | source = "registry+https://github.com/rust-lang/crates.io-index" 3230 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 3231 | 3232 | [[package]] 3233 | name = "windows_x86_64_msvc" 3234 | version = "0.52.6" 3235 | source = "registry+https://github.com/rust-lang/crates.io-index" 3236 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 3237 | 3238 | [[package]] 3239 | name = "winreg" 3240 | version = "0.50.0" 3241 | source = "registry+https://github.com/rust-lang/crates.io-index" 3242 | checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" 3243 | dependencies = [ 3244 | "cfg-if", 3245 | "windows-sys 0.48.0", 3246 | ] 3247 | 3248 | [[package]] 3249 | name = "wit-bindgen-rt" 3250 | version = "0.39.0" 3251 | source = "registry+https://github.com/rust-lang/crates.io-index" 3252 | checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" 3253 | dependencies = [ 3254 | "bitflags 2.9.1", 3255 | ] 3256 | 3257 | [[package]] 3258 | name = "writeable" 3259 | version = "0.6.1" 3260 | source = "registry+https://github.com/rust-lang/crates.io-index" 3261 | checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" 3262 | 3263 | [[package]] 3264 | name = "yoke" 3265 | version = "0.8.0" 3266 | source = "registry+https://github.com/rust-lang/crates.io-index" 3267 | checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" 3268 | dependencies = [ 3269 | "serde", 3270 | "stable_deref_trait", 3271 | "yoke-derive", 3272 | "zerofrom", 3273 | ] 3274 | 3275 | [[package]] 3276 | name = "yoke-derive" 3277 | version = "0.8.0" 3278 | source = "registry+https://github.com/rust-lang/crates.io-index" 3279 | checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" 3280 | dependencies = [ 3281 | "proc-macro2", 3282 | "quote", 3283 | "syn 2.0.101", 3284 | "synstructure", 3285 | ] 3286 | 3287 | [[package]] 3288 | name = "zerocopy" 3289 | version = "0.8.25" 3290 | source = "registry+https://github.com/rust-lang/crates.io-index" 3291 | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" 3292 | dependencies = [ 3293 | "zerocopy-derive", 3294 | ] 3295 | 3296 | [[package]] 3297 | name = "zerocopy-derive" 3298 | version = "0.8.25" 3299 | source = "registry+https://github.com/rust-lang/crates.io-index" 3300 | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" 3301 | dependencies = [ 3302 | "proc-macro2", 3303 | "quote", 3304 | "syn 2.0.101", 3305 | ] 3306 | 3307 | [[package]] 3308 | name = "zerofrom" 3309 | version = "0.1.6" 3310 | source = "registry+https://github.com/rust-lang/crates.io-index" 3311 | checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" 3312 | dependencies = [ 3313 | "zerofrom-derive", 3314 | ] 3315 | 3316 | [[package]] 3317 | name = "zerofrom-derive" 3318 | version = "0.1.6" 3319 | source = "registry+https://github.com/rust-lang/crates.io-index" 3320 | checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" 3321 | dependencies = [ 3322 | "proc-macro2", 3323 | "quote", 3324 | "syn 2.0.101", 3325 | "synstructure", 3326 | ] 3327 | 3328 | [[package]] 3329 | name = "zeroize" 3330 | version = "1.8.1" 3331 | source = "registry+https://github.com/rust-lang/crates.io-index" 3332 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 3333 | 3334 | [[package]] 3335 | name = "zerotrie" 3336 | version = "0.2.2" 3337 | source = "registry+https://github.com/rust-lang/crates.io-index" 3338 | checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" 3339 | dependencies = [ 3340 | "displaydoc", 3341 | "yoke", 3342 | "zerofrom", 3343 | ] 3344 | 3345 | [[package]] 3346 | name = "zerovec" 3347 | version = "0.11.2" 3348 | source = "registry+https://github.com/rust-lang/crates.io-index" 3349 | checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" 3350 | dependencies = [ 3351 | "yoke", 3352 | "zerofrom", 3353 | "zerovec-derive", 3354 | ] 3355 | 3356 | [[package]] 3357 | name = "zerovec-derive" 3358 | version = "0.11.1" 3359 | source = "registry+https://github.com/rust-lang/crates.io-index" 3360 | checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" 3361 | dependencies = [ 3362 | "proc-macro2", 3363 | "quote", 3364 | "syn 2.0.101", 3365 | ] 3366 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | workspace = { members = ["crates/gen_welcome"] } 2 | 3 | [package] 4 | name = "cangrebot" 5 | version = "0.1.0" 6 | edition = "2021" 7 | authors = [ 8 | "Sergio Meneses ", 9 | "Daniel Solarte ", 10 | ] 11 | description = "A Discord Bot made with Rust from RustLangEs Community" 12 | readme = "README.md" 13 | 14 | [dependencies] 15 | gen_welcome = { version = "0.1.0", path = "crates/gen_welcome" } 16 | 17 | anyhow = "1.0.66" 18 | reqwest = { version = "0.11.27", default-features = false, features = [ 19 | "rustls-tls", 20 | "json", 21 | ] } 22 | axum = "0.7.5" 23 | chrono = "0.4.38" 24 | color-eyre = "0.6.2" 25 | poise = "0.6.1" 26 | regex = "1.10.2" 27 | scraper = { version = "0.19.0", features = [ 28 | "indexmap", 29 | "deterministic", 30 | "atomic", 31 | ] } 32 | serde = "1.0.188" 33 | serde_json = "1.0.105" 34 | tokio_schedule = "0.3.2" 35 | tokio = { version = "1.26.0", features = ["full"] } 36 | tracing = "0.1.37" 37 | once_cell = "1.18.0" 38 | lazy_static = "1.5.0" 39 | dotenvy = "0.15.7" 40 | semver = "1.0.26" 41 | thiserror = "2.0.12" 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CangreBot 2 | 3 | Bot de la comunidad de Discord de RustLang en Español. 4 | 5 | ## Desarrollo 6 | 7 | ### 1. Configurar variables de entorno 8 | 9 | Las variables de entorno necesarias están documentadas en [`doc/VARIABLES.md`](doc/VARIABLES.md). Asegúrate de configurarlas antes de ejecutar el bot. 10 | 11 | ### 2. Gestión de Submódulos 12 | 13 | Para gestionar los submódulos del proyecto, consulta la documentación en [`doc/SUBMODULOS.md`](doc/SUBMODULOS.md). 14 | 15 | ### 3. Ejecutar el bot 16 | 17 | Luego ejecuta el siguiente comando para ejecutar de modo local el bot: 18 | ```bash 19 | cargo run 20 | ``` 21 | 22 | ## Autores 23 | 24 | - [@sergiomeneses](https://github.com/sergiomeneses) - Contribuidor 25 | - [@shiftrtech](https://github.com/shiftrtech) - Contribuidor 26 | - [@danielsolartech](https://github.com/danielsolartech) - Contribuidor 27 | - [@Phosphorus-M](https://github.com/Phosphorus-M) - Contribuidor 28 | - [@SergioRibera](https://github.com/SergioRibera) - Contribuidor 29 | - [@memw](https://github.com/stifskere) - Contribuidor 30 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [x] Eliminar comandos innecesarios 2 | - [ ] Reestruturar el proyecto 3 | - [x] Enviar ejercicios diarios a un canal especifico consumiendo la [api](https://www.codewars.com/trainer/peek/rust/random?dequeue=false) de [codewars](https://codewars.com) 4 | - [x] Envia el mensaje y crea un hilo a partir de este para que los miembros del discor dpuedan comentar sobre el ejercicio 5 | - [x] Menciona al rol de `@codewars` para notificarles sobre el nuevo ejercicio 6 | - [ ] Permitir que las sugerencias sean mensages normales y no un sistema de inputs 7 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | // dep-installer-hack/build.rs 2 | 3 | fn main() { 4 | if !std::path::Path::new("static/rust-examples/docs").exists() { 5 | if !std::process::Command::new("git") 6 | .args(["submodule", "update", "--init", "--recursive"]) 7 | .status() 8 | .expect("Failed to execute git submodule update") 9 | .success() 10 | { 11 | panic!("Submodule update failed. Run: git submodule update --init --recursive"); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /crates/gen_welcome/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/gen_welcome/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gen_welcome" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ab_glyph = "0.2.24" 8 | image = { version = "0.25.1", default-features = false, features = [ 9 | "png", 10 | "webp", 11 | ] } 12 | imageproc = { version = "0.24.0", default-features = false } 13 | -------------------------------------------------------------------------------- /crates/gen_welcome/src/lib.rs: -------------------------------------------------------------------------------- 1 | use ab_glyph::{FontRef, PxScale}; 2 | use image::imageops::overlay; 3 | use image::{GenericImage, GenericImageView, ImageBuffer, ImageError, Pixel, Rgba}; 4 | 5 | const FONT_SIZE: PxScale = PxScale { x: 36., y: 36. }; 6 | 7 | pub fn generate( 8 | bg: &str, 9 | avatar: &[u8], 10 | member_name: &str, 11 | members: Option, 12 | bold_font: &[u8], 13 | regular_font: &[u8], 14 | out: &str, 15 | ) -> Result<(), ImageError> { 16 | // Fonts 17 | let bold = FontRef::try_from_slice(bold_font).expect("Cannot load bold font"); 18 | let regular = FontRef::try_from_slice(regular_font).expect("Cannot load regular font"); 19 | 20 | let avatar = image::load_from_memory(avatar)?; 21 | let avatar = avatar.resize(256, 256, image::imageops::Lanczos3); 22 | let avatar = round(&avatar); 23 | let mut background = image::open(bg)?; 24 | let (w, _h) = background.dimensions(); 25 | 26 | overlay(&mut background, &avatar, 412, 87); 27 | let w_msg = format!("{member_name} acaba de caer en el servidor"); 28 | 29 | // Welcome message 30 | let (t1_x, _t1_y) = imageproc::drawing::text_size(FONT_SIZE, &bold, &w_msg); 31 | imageproc::drawing::draw_text_mut( 32 | &mut background, 33 | Rgba([255, 255, 255, 255]), 34 | ((w / 2) - (t1_x / 2)) as i32, 35 | 429, 36 | FONT_SIZE, 37 | &bold, 38 | &w_msg, 39 | ); 40 | 41 | if let Some(members) = members.as_ref() { 42 | let n_msg = format!("Eres el Rustaceo #{members}"); 43 | // Member number 44 | let (t2_x, _t2_y) = imageproc::drawing::text_size(FONT_SIZE, ®ular, &n_msg); 45 | imageproc::drawing::draw_text_mut( 46 | &mut background, 47 | Rgba([255, 255, 255, 255]), 48 | ((w / 2) - (t2_x / 2)) as i32, 49 | 488, 50 | FONT_SIZE, 51 | ®ular, 52 | &n_msg, 53 | ); 54 | } 55 | 56 | background.save(out) 57 | } 58 | 59 | fn round>>(avatar: &I) -> impl GenericImage> { 60 | let (width, height) = avatar.dimensions(); 61 | let radius = width as f32 / 2.0; 62 | let mut mask = ImageBuffer::new(width, height); 63 | let center = (width as f32 / 2.0, height as f32 / 2.0); 64 | 65 | for (x, y, pixel) in mask.enumerate_pixels_mut() { 66 | let dx = x as f32 - center.0 + 0.5; // +0.5 para centrar el pixel 67 | let dy = y as f32 - center.1 + 0.5; 68 | if dx.powi(2) + dy.powi(2) <= radius.powi(2) { 69 | *pixel = Rgba([255, 255, 255, 255]); 70 | } else { 71 | *pixel = Rgba([0, 0, 0, 0]); 72 | } 73 | } 74 | 75 | // Aplica la máscara al avatar redimensionado 76 | ImageBuffer::from_fn(width, height, |x, y| { 77 | let mask_pixel = mask.get_pixel(x, y).0[3]; 78 | let avatar_pixel = avatar.get_pixel(x, y); 79 | if mask_pixel > 0 { 80 | avatar_pixel 81 | } else { 82 | avatar_pixel.map_with_alpha(|f| f, |_| 0) 83 | } 84 | }) 85 | } 86 | -------------------------------------------------------------------------------- /crates/gen_welcome/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | 4 | use gen_welcome::generate; 5 | 6 | fn main() { 7 | let mut args = std::env::args().skip(1); 8 | 9 | let Some(background) = args.next() else { 10 | panic!("El background es necesario") 11 | }; 12 | let Some(avatar) = args.next() else { 13 | panic!("El avatar es necesario") 14 | }; 15 | let Some(name) = args.next() else { 16 | panic!("El nombre es necesario") 17 | }; 18 | let Some(members) = args.next().map(|m| { 19 | m.parse::() 20 | .expect("No se pudo parsear la cantidad de miembros") 21 | }) else { 22 | panic!("La cantidad de miembros es necesario") 23 | }; 24 | let Some(out) = args.next() else { 25 | panic!("El out file es necesario") 26 | }; 27 | 28 | let avatar = { 29 | let mut f = File::open(avatar).unwrap(); 30 | let mut buffer = Vec::new(); 31 | f.read_to_end(&mut buffer).expect("buffer overflow"); 32 | 33 | buffer 34 | }; 35 | 36 | generate( 37 | &background, 38 | &avatar, 39 | &name, 40 | Some(members), 41 | include_bytes!("../../../static/fonts/WorkSans-Bold.ttf"), 42 | include_bytes!("../../../static/fonts/WorkSans-Regular.ttf"), 43 | &out, 44 | ) 45 | .unwrap(); 46 | } 47 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { 2 | pkgs ? import { }, 3 | system ? builtins.currentSystem, 4 | ... 5 | }: let 6 | cargoManifest = builtins.fromTOML (builtins.readFile ./Cargo.toml); 7 | systemToTarget = system: let 8 | arch = builtins.elemAt (pkgs.lib.splitString "-" system) 0; 9 | os = builtins.elemAt (pkgs.lib.splitString "-" system) 1; 10 | in 11 | if os == "darwin" 12 | then "${arch}-apple-darwin" 13 | else if os == "linux" 14 | then "${arch}-unknown-linux-gnu" 15 | else throw "Unsupported system: ${system}"; 16 | 17 | hostTarget = systemToTarget system; 18 | toolchain = target: 19 | (pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml).override { 20 | targets = [hostTarget target]; 21 | }; 22 | 23 | architectures = [ 24 | { arch = "x86_64"; target = "x86_64-unknown-linux-gnu"; } 25 | # { arch = "aarch64"; target = "aarch64-unknown-linux-gnu"; } 26 | ]; 27 | 28 | buildInputs = with pkgs; [ 29 | pkg-config 30 | libopus 31 | ]; 32 | 33 | appPkg = { arch, target, ... }: let 34 | target_name = pkgs.lib.toUpper (builtins.replaceStrings ["-"] ["_"] target); 35 | in pkgs.rustPlatform.buildRustPackage rec { 36 | pname = cargoManifest.package.name; 37 | name = "${pname}-${arch}"; 38 | version = cargoManifest.package.version; 39 | doCheck = false; 40 | src = pkgs.lib.cleanSourceWith { 41 | src = ./.; 42 | filter = path: type: 43 | (baseNameOf path) == "Cargo.lock" 44 | || (baseNameOf path) == ".gitmodules" 45 | || (pkgs.lib.hasSuffix ".rs" path) 46 | || (pkgs.lib.hasSuffix ".toml" path) 47 | || (pkgs.lib.hasInfix "crates" path) 48 | || (pkgs.lib.hasInfix "src" path) 49 | || (pkgs.lib.hasInfix "static/rust-examples" path) 50 | || (pkgs.lib.hasInfix "static" path); 51 | }; 52 | cargoLock.lockFile = ./Cargo.lock; 53 | 54 | inherit buildInputs; 55 | 56 | RUSTFLAGS = "-C relocation-model=static"; 57 | TARGET_CC = "${pkgs.stdenv.cc.targetPrefix}cc"; 58 | "CARGO_TARGET_${target_name}_LINKER" = "${pkgs.llvmPackages.lld}/bin/ld.lld"; 59 | "CARGO_TARGET_${target_name}_RUNNER" = "qemu-${arch}"; 60 | }; 61 | 62 | containerPkg = variant: let 63 | pkg = appPkg variant; 64 | # arch = if variant.arch == "x86_64" then "amd" else "arm"; 65 | staticPkg = pkgs.stdenv.mkDerivation { 66 | name = "static-content"; 67 | src = ./static; 68 | phases = [ "installPhase" ]; 69 | installPhase = '' 70 | mkdir -p $out/app_static 71 | cp -r $src/* $out/app_static/ 72 | ''; 73 | }; 74 | in pkgs.dockerTools.buildLayeredImage rec { 75 | name = cargoManifest.package.name; 76 | tag = cargoManifest.package.version; 77 | created = "now"; 78 | # architecture = "linux/${arch}64"; 79 | 80 | contents = [ pkg staticPkg ]; 81 | config.Cmd = ["/bin/${name}"]; 82 | }; 83 | 84 | generatedMatrixJson = builtins.toJSON (pkgs.lib.flatten (map ({ arch, ... }: 85 | { arch = arch; } 86 | ) architectures)); 87 | in { 88 | # `nix run` 89 | apps = { 90 | default = appPkg; 91 | matrix = { 92 | type = "app"; 93 | program = toString (pkgs.writeScript "generate-matrix" '' 94 | #!/bin/sh 95 | echo '${generatedMatrixJson}' 96 | ''); 97 | }; 98 | }; 99 | 100 | # `nix build` 101 | packages = { 102 | default = appPkg; 103 | } // (pkgs.lib.listToAttrs (map ({arch, ...} @ args: { 104 | name = "image-${arch}"; 105 | value = containerPkg args; 106 | }) architectures)); 107 | 108 | # `nix develop` 109 | devShells.default = pkgs.mkShell { 110 | packages = [ toolchain ] ++ buildInputs; 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /doc/SUBMODULOS.md: -------------------------------------------------------------------------------- 1 | # Gestión de Submódulos en Cangrebot 2 | 3 | ## Introducción 4 | 5 | Este proyecto utiliza submódulos de Git para gestionar dependencias externas dentro del repositorio. Un submódulo es un repositorio dentro de otro repositorio, lo que permite incluir código de terceros sin mezclarlo directamente con el código principal. 6 | 7 | ## Inicialización y Actualización de Submódulos 8 | 9 | Si has clonado el repositorio y el submódulo no aparece, debes inicializarlo y actualizarlo manualmente con el siguiente comando: 10 | 11 | ```sh 12 | git submodule update --init --recursive 13 | ``` 14 | 15 | Esto descargará el contenido del submódulo y lo sincronizará con la versión esperada en el repositorio principal. 16 | 17 | > El archivo `build.rs` detectará automáticamente si el submódulo existe. Si no está inicializado, mostrará un mensaje indicando que es necesario ejecutar: `git submodule update --init --recursive` 18 | 19 | 20 | ## Verificación del Estado del Submódulo 21 | 22 | Puedes verificar el estado del submódulo con: 23 | 24 | ```sh 25 | git submodule status 26 | ``` 27 | 28 | Esto mostrará si el submódulo está correctamente sincronizado o si requiere una actualización. 29 | 30 | ## Consideraciones 31 | 32 | - Si trabajas en una rama donde el submódulo ha cambiado, ejecuta `git submodule update --remote` para traer la última versión. 33 | - Al clonar un nuevo repositorio, recuerda siempre inicializar y actualizar los submódulos. 34 | 35 | ## Conclusión 36 | 37 | El uso de submódulos permite gestionar dependencias de manera eficiente sin mezclar código externo en el repositorio principal. 38 | Con la automatización en `build.rs`, se puede reducir la fricción al compilar el proyecto sin necesidad de pasos adicionales 39 | -------------------------------------------------------------------------------- /doc/VARIABLES.md: -------------------------------------------------------------------------------- 1 | # Configuración del Bot 2 | 3 | Para ejecutar el bot, crea un archivo `.env` con las siguientes variables: 4 | 5 | > Existe el archivo `.env.example` que puedes renombrar 6 | 7 | ### ¿Dónde obtener los valores? 8 | 9 | - `DISCORD_TOKEN`: Obtenlo desde el [Token del bot](https://discord.com/developers/applications) 10 | - `GUILD_ID`: Activa el Modo Desarrollador en Discord, haz clic derecho en tu servidor y selecciona 'Copiar ID' 11 | - `BOT_APIKEY`: Autorizacion para canal cifrado entre el bot y el servidor (Puede contener cualquier texto) 12 | - `CHANNEL_DAILY` & `CHANNEL_SUGGEST`: Haz clic derecho en el canal de Discord y selecciona 'Copiar ID' 13 | - `LAVALINK_PASSWORD`: Contraseña Lavalink 14 | 15 | **Opcionales** 16 | 17 | - `STATIC_ROOT`: corresponde a la ubicacion del contenido `static`, por defecto es `./static` 18 | 19 | #### Formato para `.env` 20 | 21 | ```toml 22 | DISCORD_TOKEN = "Bot token" 23 | GUILD_ID = "Server ID" 24 | BOT_APIKEY = "API key for secure communication" 25 | CHANNEL_DAILY = "Channel ID for daily challenges" 26 | CHANNEL_SUGGEST = "Channel ID for suggestions" 27 | ``` 28 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1748370509, 24 | "narHash": "sha256-QlL8slIgc16W5UaI3w7xHQEP+Qmv/6vSNTpoZrrSlbk=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "4faa5f5321320e49a78ae7848582f684d64783e9", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs", 41 | "rust-overlay": "rust-overlay" 42 | } 43 | }, 44 | "rust-overlay": { 45 | "inputs": { 46 | "nixpkgs": [ 47 | "nixpkgs" 48 | ] 49 | }, 50 | "locked": { 51 | "lastModified": 1748572605, 52 | "narHash": "sha256-k0nhPtkVDQkVJckRw6fGIeeDBktJf1BH0i8T48o7zkk=", 53 | "owner": "oxalica", 54 | "repo": "rust-overlay", 55 | "rev": "405ef13a5b80a0a4d4fc87c83554423d80e5f929", 56 | "type": "github" 57 | }, 58 | "original": { 59 | "owner": "oxalica", 60 | "repo": "rust-overlay", 61 | "type": "github" 62 | } 63 | }, 64 | "systems": { 65 | "locked": { 66 | "lastModified": 1681028828, 67 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 68 | "owner": "nix-systems", 69 | "repo": "default", 70 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 71 | "type": "github" 72 | }, 73 | "original": { 74 | "owner": "nix-systems", 75 | "repo": "default", 76 | "type": "github" 77 | } 78 | } 79 | }, 80 | "root": "root", 81 | "version": 7 82 | } 83 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 4 | flake-utils.url = "github:numtide/flake-utils"; 5 | rust-overlay = { 6 | url = "github:oxalica/rust-overlay"; 7 | inputs.nixpkgs.follows = "nixpkgs"; 8 | }; 9 | }; 10 | 11 | outputs = { 12 | nixpkgs, 13 | flake-utils, 14 | rust-overlay, 15 | ... 16 | }: 17 | flake-utils.lib.eachSystem (flake-utils.lib.defaultSystems) ( 18 | system: let 19 | overlays = [ (import rust-overlay) ]; 20 | pkgs = import nixpkgs { 21 | inherit system overlays; 22 | }; 23 | appBundle = import ./. { 24 | inherit system pkgs; 25 | }; 26 | in { 27 | inherit (appBundle) apps packages devShells; 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | profile = "minimal" 4 | components = ["rust-src", "rust-std"] 5 | -------------------------------------------------------------------------------- /src/api/auth.rs: -------------------------------------------------------------------------------- 1 | use axum::extract::{Request, State}; 2 | use axum::http::{HeaderMap, StatusCode}; 3 | use axum::middleware::Next; 4 | use axum::response::Response; 5 | 6 | use crate::CangrebotSecrets; 7 | 8 | pub async fn middleware( 9 | State(secrets): State, 10 | headers: HeaderMap, 11 | req: Request, 12 | next: Next, 13 | ) -> Result { 14 | let header_key = headers.get("Authorization"); 15 | 16 | if header_key 17 | .as_ref() 18 | .is_some_and(|k| k.to_str().unwrap() == secrets.api_key) 19 | { 20 | return Ok(next.run(req).await); 21 | } 22 | 23 | tracing::error!("UNAUTHORIZED: {header_key:?}"); 24 | 25 | Err(axum::http::StatusCode::UNAUTHORIZED) 26 | } 27 | -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | mod auth; 2 | pub mod routes; 3 | 4 | use std::sync::Arc; 5 | 6 | use axum::Router; 7 | use poise::serenity_prelude::Http; 8 | 9 | use crate::CangrebotSecrets; 10 | 11 | pub(super) type RouteState = (CangrebotSecrets, Arc); 12 | 13 | pub fn build_router(secrets: &CangrebotSecrets, ctx: Arc) -> Router { 14 | Router::new() 15 | .route( 16 | "/daily_challenge", 17 | axum::routing::post(routes::daily_challenge), 18 | ) 19 | .route("/send_message", axum::routing::post(routes::send_message)) 20 | .route("/healthcheck", axum::routing::get(routes::healthcheck)) 21 | .layer(axum::middleware::from_fn_with_state( 22 | secrets.clone(), 23 | auth::middleware, 24 | )) 25 | .with_state((secrets.clone(), ctx)) 26 | } 27 | -------------------------------------------------------------------------------- /src/api/routes/daily_challenge.rs: -------------------------------------------------------------------------------- 1 | use axum::extract::State; 2 | use axum::http::StatusCode; 3 | use axum::response::IntoResponse; 4 | use axum::Json; 5 | use serde::{Deserialize, Serialize}; 6 | use tracing::info; 7 | 8 | use crate::api::RouteState; 9 | use crate::serenity::builder::{CreateAllowedMentions, CreateForumPost, CreateMessage}; 10 | use crate::serenity::model::prelude::ChannelId; 11 | 12 | const PARTICIPANT_ROLE: u64 = 1224238464958992495; 13 | 14 | #[derive(Deserialize, Serialize)] 15 | pub struct DailyChallengeRequest { 16 | title: String, 17 | message: String, 18 | tag_name: String, 19 | } 20 | 21 | pub async fn daily_challenge( 22 | State((secrets, ctx)): State, 23 | Json(DailyChallengeRequest { 24 | title, 25 | message, 26 | tag_name, 27 | }): Json, 28 | ) -> impl IntoResponse { 29 | info!("Running daily challenge events"); 30 | let msg_channel = ChannelId::new(secrets.channel_daily); 31 | 32 | let Ok(forum) = msg_channel.to_channel(&ctx).await else { 33 | return ( 34 | StatusCode::INTERNAL_SERVER_ERROR, 35 | "Cannot convert to channel", 36 | ); 37 | }; 38 | 39 | let Some(forum) = forum.guild() else { 40 | return (StatusCode::NOT_FOUND, "GuildId not found"); 41 | }; 42 | 43 | let Some(tag) = forum.available_tags.iter().find(|t| t.name == tag_name) else { 44 | return (StatusCode::NOT_FOUND, "Tag not found"); 45 | }; 46 | 47 | match msg_channel 48 | .create_forum_post( 49 | &ctx, 50 | CreateForumPost::new( 51 | title, 52 | CreateMessage::new() 53 | .content(message) 54 | .allowed_mentions(CreateAllowedMentions::new().roles([PARTICIPANT_ROLE])), 55 | ) 56 | .add_applied_tag(tag.id), 57 | ) 58 | .await 59 | { 60 | Ok(_) => (StatusCode::OK, "Ok"), 61 | Err(_) => ( 62 | StatusCode::INTERNAL_SERVER_ERROR, 63 | "Cannot Create Forum Post", 64 | ), 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/api/routes/mod.rs: -------------------------------------------------------------------------------- 1 | mod daily_challenge; 2 | use axum::http::StatusCode; 3 | use axum::response::IntoResponse; 4 | pub use daily_challenge::daily_challenge; 5 | mod send_message; 6 | pub use send_message::send_message; 7 | 8 | pub mod send_stats; 9 | 10 | pub async fn healthcheck() -> impl IntoResponse { 11 | (StatusCode::OK, "Ok") 12 | } 13 | -------------------------------------------------------------------------------- /src/api/routes/send_message.rs: -------------------------------------------------------------------------------- 1 | use crate::api::RouteState; 2 | use crate::serenity::all::MESSAGE_CODE_LIMIT; 3 | use crate::serenity::builder::{CreateAllowedMentions, CreateMessage}; 4 | use crate::serenity::model::prelude::ChannelId; 5 | use axum::extract::State; 6 | use axum::http::StatusCode; 7 | use axum::response::IntoResponse; 8 | use axum::Json; 9 | use serde::{Deserialize, Serialize}; 10 | use tracing::info; 11 | 12 | #[derive(Deserialize, Serialize)] 13 | #[serde(rename_all = "camelCase")] 14 | pub struct SendMessagePayload { 15 | message: String, 16 | channel_id: u64, 17 | roles: Vec, 18 | } 19 | 20 | fn split_into_chunks(s: &str, chunk_size: usize) -> Vec<&str> { 21 | s.as_bytes() 22 | .chunks(chunk_size) 23 | .map(|chunk| std::str::from_utf8(chunk).unwrap()) 24 | .collect() 25 | } 26 | 27 | pub async fn send_message( 28 | State((_, ctx)): State, 29 | Json(SendMessagePayload { 30 | message, 31 | channel_id, 32 | roles, 33 | }): Json, 34 | ) -> impl IntoResponse { 35 | info!("Running Send Message from API"); 36 | let msg_channel = ChannelId::new(channel_id); 37 | 38 | let messages = split_into_chunks(&message, MESSAGE_CODE_LIMIT); 39 | 40 | for message in &messages { 41 | let message = CreateMessage::new() 42 | .content(*message) 43 | .allowed_mentions(CreateAllowedMentions::new().roles(roles.clone())); 44 | 45 | if let Err(err) = msg_channel.send_message(&ctx, message).await { 46 | tracing::error!("Cannot send message: {err:?}"); 47 | return (StatusCode::INTERNAL_SERVER_ERROR, "Cannot send message"); 48 | } 49 | } 50 | 51 | (StatusCode::OK, "Ok") 52 | } 53 | -------------------------------------------------------------------------------- /src/api/routes/send_stats.rs: -------------------------------------------------------------------------------- 1 | use reqwest::Client; 2 | use serde::Serialize; 3 | use std::error::Error; 4 | use serde_json::Value; 5 | 6 | 7 | /// Estructura que contiene las estadísticas del servidor 8 | #[derive(Serialize, Clone)] 9 | pub struct ServerStats { 10 | pub guild_id: String, 11 | pub guild_name: String, 12 | pub total_members: usize, 13 | pub active_members: usize, 14 | pub total_channels: usize, 15 | pub total_messages: u64, 16 | pub daily_messages: u64, 17 | pub monthly_messages: u64, 18 | pub latest_messages: Option>, 19 | pub new_members: Option>, 20 | } 21 | 22 | /// Función para enviar las estadísticas a la API externa 23 | pub async fn send_stats_to_api(stats: ServerStats) -> Result<(), Box> { 24 | let api_url = "https://webhook.site/7b724fc1-c713-45cd-8b86-fade7386a693"; 25 | let client = Client::new(); 26 | 27 | let response = client.post(api_url).json(&stats).send().await?; 28 | 29 | if response.status().is_success() { 30 | println!("✅ Stats enviadas correctamente"); 31 | } else { 32 | println!("⚠️ Error al enviar stats: {:?}", response.status()); 33 | } 34 | 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /src/bot/commands.rs: -------------------------------------------------------------------------------- 1 | mod crate_docs; 2 | mod explain; 3 | mod help; 4 | mod invite; 5 | mod krate; 6 | mod ping; 7 | mod stats; 8 | mod suggest; 9 | 10 | use super::{Data, Error}; 11 | 12 | pub fn commands() -> Vec> { 13 | vec![ 14 | crate_docs::docs(), 15 | crate_docs::crate_docs(), 16 | explain::explain(), 17 | help::help(), 18 | invite::invite(), 19 | krate::cargo(), 20 | ping::ping(), 21 | suggest::sugerencia(), 22 | stats::send_stats() 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /src/bot/commands/crate_docs.rs: -------------------------------------------------------------------------------- 1 | use crate::bot; 2 | use crate::bot::commands::krate::autocomplete; 3 | use poise::serenity_prelude::CreateEmbed; 4 | use poise::CreateReply; 5 | use regex::Regex; 6 | use reqwest::get; 7 | use scraper::{Html, Selector}; 8 | use tracing::info; 9 | 10 | fn strip_html(input: &str) -> String { 11 | Regex::new(r"]+>") 12 | .unwrap() 13 | .replace_all(input, "") 14 | .replace("<", "<") 15 | .replace(">", ">") 16 | .to_string() 17 | } 18 | 19 | async fn scrap_doc( 20 | ctx: bot::Context<'_>, 21 | search: &str, 22 | package: &str, 23 | url: &str, 24 | ) -> Result, bot::Error> { 25 | let response = get(url).await?; 26 | 27 | if response.status() != 200 { 28 | ctx.say(format!("The crate `{package}` was not found.")) 29 | .await 30 | .ok(); 31 | 32 | return Ok(None); 33 | } 34 | 35 | let html = response.text().await?; 36 | 37 | let selector = Selector::parse("ul.all-items > li > a").unwrap(); 38 | let elements = Html::parse_document(&html); 39 | let elements = elements.select(&selector); 40 | 41 | let Some((_, url)) = elements 42 | .into_iter() 43 | .map(|elem| (elem.inner_html(), elem.attr("href").unwrap())) 44 | .find(|(name, _)| name.to_lowercase().contains(&search.to_lowercase())) 45 | else { 46 | ctx.say(format!( 47 | "The expression `{search}` was not found on `{package}`." 48 | )) 49 | .await 50 | .ok(); 51 | 52 | return Ok(None); 53 | }; 54 | 55 | let html = get(url).await?.text().await?; 56 | 57 | let html = Html::parse_document(&html); 58 | 59 | let element_type_selector = Selector::parse(".main-heading > h1").unwrap(); 60 | let element_name_selector = Selector::parse(".main-heading > h1 > span").unwrap(); 61 | let element_code_selector = Selector::parse(".item-decl > code").unwrap(); 62 | let element_description_selector = Selector::parse(".docblock > p:last-child").unwrap(); 63 | 64 | let element_type = html 65 | .select(&element_type_selector) 66 | .into_iter() 67 | .next() 68 | .unwrap() 69 | .inner_html(); 70 | let element_type = element_type.split_once(" ").unwrap().0; 71 | let element_name = html 72 | .select(&element_name_selector) 73 | .into_iter() 74 | .next() 75 | .unwrap() 76 | .inner_html(); 77 | let element_name = element_name.split(" ").last().unwrap(); 78 | let element_code = strip_html( 79 | &html 80 | .select(&element_code_selector) 81 | .into_iter() 82 | .next() 83 | .unwrap() 84 | .inner_html(), 85 | ); 86 | let element_description = html 87 | .select(&element_description_selector) 88 | .into_iter() 89 | .next() 90 | .unwrap() 91 | .inner_html() 92 | .replace("", "`") 93 | .replace("", "`"); 94 | 95 | Ok(Some(( 96 | element_type.into(), 97 | element_name.into(), 98 | element_code.into(), 99 | element_description.into(), 100 | ))) 101 | } 102 | 103 | #[poise::command(slash_command, prefix_command, broadcast_typing, category = "Crates")] 104 | pub async fn crate_docs( 105 | ctx: bot::Context<'_>, 106 | #[description = "El nombre del crate, crate@version | crate"] 107 | #[rename = "crate"] 108 | #[autocomplete = "autocomplete"] 109 | package: String, 110 | #[description = "Buscar un nombre o expresion minima, como Struct::method."] search: String, 111 | ) -> Result<(), bot::Error> { 112 | let (name, version) = package.split_once("@").unwrap_or((&package, "latest")); 113 | let url = format!("https://docs.rs/{name}/{version}/{name}/all.html"); 114 | let Some((element_type, element_name, element_code, element_description)) = 115 | scrap_doc(ctx, &package, &search, &url).await? 116 | else { 117 | return Ok(()); 118 | }; 119 | 120 | ctx.send( 121 | CreateReply::default().embed( 122 | CreateEmbed::new() 123 | .title(format!("{element_type} {element_name}")) 124 | .description(format!( 125 | "```rs\n{element_code}\n```\n{element_description}\n\nSee more: {url}" 126 | )), 127 | ), 128 | ) 129 | .await?; 130 | 131 | Ok(()) 132 | } 133 | 134 | /// Returns whether the given type name is the one of a primitive. 135 | #[rustfmt::skip] 136 | fn is_in_std(name: &str) -> bool { 137 | name.chars().next().is_some_and(char::is_uppercase) 138 | || matches!( 139 | name, 140 | "f32" | "f64" 141 | | "i8" | "i16" | "i32" | "i64" | "i128" | "isize" 142 | | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" 143 | | "char" | "str" 144 | | "pointer" | "reference" | "fn" 145 | | "bool" | "slice" | "tuple" | "unit" | "array" 146 | ) 147 | } 148 | 149 | #[poise::command(broadcast_typing, slash_command, category = "Crates")] 150 | pub async fn docs( 151 | ctx: bot::Context<'_>, 152 | #[description = "Buscar un nombre o expresion minima, como Struct::method."] query: String, 153 | ) -> Result<(), bot::Error> { 154 | let mut query_iter = query.splitn(2, "::"); 155 | let first_path_element = query_iter.next().unwrap(); 156 | let mut url = "https://doc.rust-lang.org/stable/std/".to_owned(); 157 | 158 | if is_in_std(first_path_element) { 159 | url += "?search="; 160 | url += &query; 161 | } else if let Some(item_path) = query_iter.next() { 162 | url += "?search="; 163 | url += item_path; 164 | } 165 | 166 | let Some((element_type, element_name, element_code, element_description)) = 167 | scrap_doc(ctx, &query, "std docs", &url).await? 168 | else { 169 | return Ok(()); 170 | }; 171 | 172 | ctx.send( 173 | CreateReply::default().embed( 174 | CreateEmbed::new() 175 | .title(format!("{element_type} {element_name}")) 176 | .description(format!( 177 | "```rs\n{element_code}\n```\n{element_description}\n\nSee more: {url}" 178 | )), 179 | ), 180 | ) 181 | .await?; 182 | 183 | Ok(()) 184 | } 185 | -------------------------------------------------------------------------------- /src/bot/commands/explain.rs: -------------------------------------------------------------------------------- 1 | use crate::serenity::all::AutocompleteChoice; 2 | 3 | use crate::bot; 4 | use std::path::PathBuf; 5 | 6 | const TOPICS: [&str; 33] = [ 7 | "arrays", 8 | "borrowing", 9 | "closures", 10 | "condicionales", 11 | "constantes", 12 | "enums", 13 | "for", 14 | "funciones", 15 | "generics", 16 | "if_let", 17 | "iterators", 18 | "let_else", 19 | "lifetimes", 20 | "loop", 21 | "macros", 22 | "match", 23 | "metodos", 24 | "modulos", 25 | "operadores", 26 | "ownership", 27 | "result", 28 | "return", 29 | "scopes", 30 | "shadowing", 31 | "slices", 32 | "string", 33 | "struct", 34 | "tipo_de_datos", 35 | "traits", 36 | "tuplas", 37 | "variables", 38 | "vectores", 39 | "while", 40 | ]; 41 | 42 | /// Explica un concepto de Rust 43 | #[poise::command(slash_command, prefix_command)] 44 | pub async fn explain( 45 | ctx: bot::Context<'_>, 46 | #[description = "Este sera el concepto que se explicara"] 47 | #[autocomplete = "autocomplete"] 48 | concepto: String, 49 | ) -> Result<(), bot::Error> { 50 | let concepts_folder = PathBuf::from(format!( 51 | "{}/rust-examples/docs", 52 | std::env::var("STATIC_ROOT") 53 | .as_deref() 54 | .unwrap_or_else(|_| "static") 55 | )); 56 | 57 | let concept = concepto.to_lowercase() + ".md"; 58 | let concept = std::fs::read_to_string(concepts_folder.join(concept)) 59 | .unwrap_or("No se ha encontrado el concepto".to_string()); 60 | 61 | ctx.say(concept).await?; 62 | 63 | Ok(()) 64 | } 65 | 66 | async fn autocomplete( 67 | _: bot::Context<'_>, 68 | partial: &str, 69 | ) -> impl Iterator { 70 | TOPICS 71 | .iter() 72 | .filter(|topic| topic.contains(partial)) 73 | .map(|topic| AutocompleteChoice::new(*topic, *topic)) 74 | .collect::>() 75 | .into_iter() 76 | } 77 | -------------------------------------------------------------------------------- /src/bot/commands/hello.rs: -------------------------------------------------------------------------------- 1 | use crate::bot::{Context, Error}; 2 | use crate::serenity; 3 | 4 | /// Displays your or another user's account creation date 5 | #[poise::command(slash_command, prefix_command)] 6 | pub async fn age( 7 | ctx: Context<'_>, 8 | #[description = "Selected user"] user: Option, 9 | ) -> Result<(), Error> { 10 | let u = user.as_ref().unwrap_or_else(|| ctx.author()); 11 | let response = format!("{}'s account was created at {}", u.name, u.created_at()); 12 | ctx.say(response).await?; 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /src/bot/commands/help.rs: -------------------------------------------------------------------------------- 1 | use poise::samples::HelpConfiguration; 2 | 3 | use crate::bot::{Context, Error}; 4 | 5 | /// Mostrar mensaje de ayuda 6 | #[poise::command(slash_command, prefix_command, track_edits, category = "Utility")] 7 | pub async fn help( 8 | ctx: Context<'_>, 9 | #[description = "Comando a mirar"] 10 | #[rest] 11 | mut command: Option, 12 | ) -> Result<(), Error> { 13 | // This makes it possible to just make `help` a subcommand of any command 14 | // `/fruit help` turns into `/help fruit` 15 | // `/fruit help apple` turns into `/help fruit apple` 16 | if ctx.invoked_command_name() != "help" { 17 | command = match command { 18 | Some(c) => Some(format!("{} {}", ctx.invoked_command_name(), c)), 19 | None => Some(ctx.invoked_command_name().to_string()), 20 | }; 21 | } 22 | 23 | let prefix = ctx.prefix(); 24 | 25 | let extra_text_at_bottom = &format!( 26 | "\ 27 | Type `{prefix}help command` for more info on a command. 28 | You can edit your `{prefix}help` message to the bot and the bot will edit its response." 29 | ); 30 | 31 | let config = HelpConfiguration { 32 | show_subcommands: true, 33 | show_context_menu_commands: true, 34 | ephemeral: true, 35 | extra_text_at_bottom, 36 | 37 | ..Default::default() 38 | }; 39 | 40 | poise::builtins::help(ctx, command.as_deref(), config).await?; 41 | 42 | Ok(()) 43 | } 44 | -------------------------------------------------------------------------------- /src/bot/commands/invite.rs: -------------------------------------------------------------------------------- 1 | use crate::bot::{Context, Error}; 2 | 3 | /// Retorna el link de invitación del servidor 4 | #[poise::command(slash_command, prefix_command)] 5 | pub async fn invite(ctx: Context<'_>) -> Result<(), Error> { 6 | ctx.say("https://discord.gg/4ng5HgmaMg").await?; 7 | Ok(()) 8 | } 9 | -------------------------------------------------------------------------------- /src/bot/commands/krate.rs: -------------------------------------------------------------------------------- 1 | use poise::serenity_prelude::AutocompleteChoice; 2 | use tracing::error; 3 | 4 | use crate::bot; 5 | 6 | mod fetch; 7 | 8 | /// Busca y obtiene el enlace del crate, documentacion, repositorio y pagina web 9 | #[poise::command(slash_command, prefix_command)] 10 | pub async fn cargo( 11 | ctx: bot::Context<'_>, 12 | #[description = "El nombre del crate que quieres buscar"] 13 | #[autocomplete = "autocomplete"] 14 | nombre: String, 15 | ) -> Result<(), bot::Error> { 16 | let client = reqwest::Client::new(); 17 | 18 | match fetch::fetch_crate(&client, nombre).await { 19 | Ok(s) => ctx.say(s).await?, 20 | Err(e) => ctx.say(e.to_string()).await?, 21 | }; 22 | 23 | Ok(()) 24 | } 25 | 26 | pub async fn autocomplete( 27 | _: bot::Context<'_>, 28 | partial: &str, 29 | ) -> impl Iterator { 30 | let client = reqwest::Client::new(); 31 | 32 | let Ok(value) = fetch::search_crate(&client, partial) 33 | .await 34 | .inspect_err(|err| error!("{err}")) 35 | else { 36 | return vec![AutocompleteChoice::new("Error", "Error")].into_iter(); 37 | }; 38 | 39 | let Ok(value) = value else { 40 | return vec![AutocompleteChoice::new("Error", "Error")].into_iter(); 41 | }; 42 | 43 | let v: Vec<_> = value 44 | .into_iter() 45 | .map(|v| AutocompleteChoice::new(v.clone(), v)) 46 | .collect(); 47 | 48 | v.into_iter() 49 | } 50 | -------------------------------------------------------------------------------- /src/bot/commands/krate/fetch.rs: -------------------------------------------------------------------------------- 1 | use reqwest::Client; 2 | use serde::Deserialize; 3 | 4 | use tracing::info; 5 | 6 | use crate::bot::util::mask_url; 7 | 8 | const MESSAGE: &str = r#"## [@crate_name@](https://crates.io/crates/@crate_name@) 9 | @description@@keywords@ 10 | 11 | @last_version@@stable_version@ 12 | @doc@ 13 | @repo@ 14 | @web@"#; 15 | 16 | #[derive(Deserialize)] 17 | struct CratesIO { 18 | #[serde(rename = "crate")] 19 | krate: CratesDetails, 20 | } 21 | 22 | #[derive(Deserialize)] 23 | struct CratesDetails { 24 | name: String, 25 | description: String, 26 | max_stable_version: String, 27 | newest_version: String, 28 | keywords: Vec, 29 | homepage: Option, 30 | repository: Option, 31 | documentation: Option, 32 | } 33 | 34 | #[derive(Deserialize, Debug)] 35 | struct CratesError { 36 | errors: Vec, 37 | } 38 | 39 | #[derive(Deserialize, Debug)] 40 | struct CratesErrorDetail { 41 | detail: String, 42 | } 43 | 44 | #[derive(Deserialize, Debug)] 45 | struct CargoSearch { 46 | crates: Vec, 47 | } 48 | 49 | #[derive(Deserialize, Debug)] 50 | struct CargoSearchDetail { 51 | name: String, 52 | } 53 | 54 | pub async fn fetch_crate(client: &Client, crate_name: String) -> reqwest::Result { 55 | info!("Running crate searching"); 56 | 57 | let res = client 58 | .get(format!("https://crates.io/api/v1/crates/{crate_name}",)) 59 | .header("User-Agent", "RustLangES Automation Agent") 60 | .send() 61 | .await? 62 | .text() 63 | .await?; 64 | 65 | if let Ok(err) = serde_json::from_str::(&res) { 66 | if let Some(CratesErrorDetail { detail }) = err.errors.first() { 67 | return Ok(detail.to_string()); 68 | } 69 | } 70 | 71 | let Ok(CratesIO { 72 | krate: 73 | CratesDetails { 74 | description, 75 | documentation, 76 | keywords, 77 | homepage, 78 | max_stable_version, 79 | name, 80 | newest_version, 81 | repository, 82 | }, 83 | }) = serde_json::from_str::(&res) 84 | .inspect_err(|e| tracing::error!("Serde Crates.io: {e:?}")) 85 | else { 86 | return Ok("No se pudo deserializar la respuesta".to_string()); 87 | }; 88 | 89 | let res = MESSAGE 90 | .replace("@crate_name@", &name) 91 | .replace("@description@", &description) 92 | .replace( 93 | "@last_version@", 94 | &format!("Última Version: ``{newest_version}``"), 95 | ) 96 | .replace( 97 | "@stable_version@", 98 | &(if max_stable_version == newest_version { 99 | String::new() 100 | } else { 101 | format!("\nÚltima Version Estable: ``{max_stable_version}``") 102 | }), 103 | ) 104 | .replace( 105 | "@keywords@", 106 | &(if keywords.is_empty() { 107 | String::new() 108 | } else { 109 | format!("\n-# {}", keywords.join(" | ")) 110 | }), 111 | ) 112 | .replace( 113 | "@doc@", 114 | &format!( 115 | "Documentación: {}", 116 | documentation.map(mask_url).as_deref().unwrap_or("None") 117 | ), 118 | ) 119 | .replace( 120 | "@repo@", 121 | &format!( 122 | "Repositorio: {}", 123 | repository.map(mask_url).as_deref().unwrap_or("None") 124 | ), 125 | ) 126 | .replace( 127 | "@web@", 128 | &format!( 129 | "Página Web: {}", 130 | homepage.map(mask_url).as_deref().unwrap_or("None") 131 | ), 132 | ); 133 | 134 | Ok(res) 135 | } 136 | 137 | pub async fn search_crate( 138 | client: &Client, 139 | crate_name: &str, 140 | ) -> reqwest::Result, String>> { 141 | info!("Running crate searching"); 142 | 143 | let res = client 144 | .get(format!( 145 | "https://crates.io/api/v1/crates?per_page=20&q={crate_name}", 146 | )) 147 | .header("User-Agent", "RustLangES Automation Agent") 148 | .send() 149 | .await? 150 | .text() 151 | .await?; 152 | 153 | if let Ok(err) = serde_json::from_str::(&res) { 154 | if let Some(CratesErrorDetail { detail }) = err.errors.first() { 155 | return Ok(Err(detail.to_string())); 156 | } 157 | } 158 | 159 | let Ok(CargoSearch { crates }) = serde_json::from_str::(&res) 160 | .inspect_err(|e| tracing::error!("Serde Crates.io: {e:?}")) 161 | else { 162 | return Ok(Err("No se pudo deserializar la respuesta".to_string())); 163 | }; 164 | 165 | let crates = crates 166 | .into_iter() 167 | .map(|CargoSearchDetail { name }| name) 168 | .collect(); 169 | 170 | Ok(Ok(crates)) 171 | } 172 | -------------------------------------------------------------------------------- /src/bot/commands/ping.rs: -------------------------------------------------------------------------------- 1 | use crate::bot::{Context, Error}; 2 | use poise::{serenity_prelude::CreateEmbed, CreateReply}; 3 | use std::time::Instant; 4 | 5 | /// Estoy vivo? Recuerdo el sonido... Ping!... Pong! 6 | #[poise::command(slash_command, prefix_command)] 7 | pub async fn ping(ctx: Context<'_>) -> Result<(), Error> { 8 | let start: Instant = Instant::now(); 9 | 10 | ctx.say("Calculating...⌛").await?; 11 | 12 | let latency: u128 = start.elapsed().as_millis(); 13 | 14 | let mensaje: String = format!("📡 Latencia: `{}` ms", latency); 15 | 16 | let embed = CreateEmbed::new() 17 | .title("🏓 Pong!") 18 | .description(mensaje) 19 | .color(0xEA9010); 20 | 21 | let replay = CreateReply::default(); 22 | 23 | ctx.send(replay.embed(embed)).await?; 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /src/bot/commands/stats.rs: -------------------------------------------------------------------------------- 1 | use crate::api::routes::send_stats::{send_stats_to_api, ServerStats}; 2 | use crate::bot::{Context, Error}; 3 | use anyhow::anyhow; 4 | use poise::serenity_prelude::{ChannelType, GetMessages, GuildId, Message, Timestamp}; 5 | use serde_json::{json, Value}; 6 | use chrono::{Utc, Duration}; 7 | 8 | #[poise::command(slash_command, prefix_command)] 9 | pub async fn send_stats(ctx: Context<'_>) -> Result<(), Error> { 10 | ctx.defer().await?; 11 | 12 | // 1. Asegurarnos de que estamos en un guild 13 | let guild_id: GuildId = ctx 14 | .guild_id() 15 | .ok_or_else(|| anyhow!("Este comando solo funciona en un servidor"))?; 16 | 17 | // 2. Obtener PartialGuild con counts 18 | let guild = guild_id 19 | .to_partial_guild_with_counts(&ctx.http()) 20 | .await 21 | .map_err(|_| anyhow!("No se pudo obtener la info del servidor. Revisa permisos"))?; 22 | 23 | // 3. Traer todos los canales 24 | let channels = guild.channels(&ctx.http()).await?; 25 | 26 | // ───────────────────────────── 27 | // 4. Recopilar mensajes de texto 28 | // ───────────────────────────── 29 | let mut all_messages: Vec = Vec::new(); 30 | for channel in channels.values() { 31 | if channel.kind == ChannelType::Text { 32 | if let Ok(msgs) = channel 33 | .messages(&ctx.http(), GetMessages::new().limit(100)) 34 | .await 35 | { 36 | all_messages.extend(msgs); 37 | } 38 | } 39 | } 40 | // Ordenar de más nuevo a más viejo y quedarnos con los 100 primeros 41 | all_messages.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); 42 | let latest_100 = all_messages.into_iter().take(100).collect::>(); 43 | 44 | let serialized_msgs: Vec = latest_100 45 | .iter() 46 | .map(|m| json!({ 47 | "id": m.id.to_string(), 48 | "channel_id": m.channel_id.to_string(), 49 | "author": { 50 | "id": m.author.id.to_string(), 51 | "name": m.author.name, 52 | }, 53 | "content": m.content, 54 | "timestamp": m.timestamp.to_string(), 55 | })) 56 | .collect(); 57 | 58 | // ───────────────────────────── 59 | // 5. Capturar nuevos miembros del día 60 | // ───────────────────────────── 61 | let members = guild_id 62 | .members(&ctx.http(), None, None) 63 | .await 64 | .map_err(|_| anyhow!("No se pudieron enumerar los miembros"))?; 65 | 66 | let today = Utc::now().date_naive(); 67 | let new_members: Vec = members 68 | .iter() 69 | .filter_map(|m| { 70 | m.joined_at.map(|dt| { 71 | let date = dt.naive_utc().date(); 72 | (m, date) 73 | }) 74 | }) 75 | .filter(|(_, date)| *date == today) 76 | .map(|(m, date)| json!({ 77 | "id": m.user.id.to_string(), 78 | "name": m.user.name.clone(), 79 | "joined_at": date.to_string(), 80 | })) 81 | .collect(); 82 | 83 | // ───────────────────────────── 84 | // 6. variable xd 85 | // ───────────────────────────── 86 | 87 | let members = guild_id 88 | .members(&ctx.http(), Some(1000), None) 89 | .await 90 | .map_err(|_| anyhow!("No se pudieron enumerar los miembros"))?; 91 | 92 | let now_chrono = chrono::Utc::now(); 93 | let threshold = Timestamp::from_unix_timestamp((now_chrono - chrono::Duration::hours(24)).timestamp()) 94 | .expect("Invalid timestamp"); 95 | 96 | 97 | let new_members: Vec = members 98 | .into_iter() 99 | .filter_map(|m| m.joined_at.map(|joined_at| (m, joined_at))) 100 | .filter(|(_, joined_at)| *joined_at > threshold) 101 | .map(|(m, joined_at)| json!({ 102 | "id": m.user.id.to_string(), 103 | "name": m.user.name, 104 | "joined_at": joined_at.to_string(), 105 | })) 106 | .collect(); 107 | 108 | // ───────────────────────────── 109 | // 7. Construir ServerStats y enviar 110 | // ───────────────────────────── 111 | let stats = ServerStats { 112 | guild_id: guild_id.to_string(), 113 | guild_name: guild.name.clone(), 114 | total_members: guild.approximate_member_count.unwrap_or(0) as usize, 115 | active_members: guild.approximate_presence_count.unwrap_or(0) as usize, 116 | total_channels: channels.len(), 117 | total_messages: latest_100.len() as u64, 118 | daily_messages: 0, 119 | monthly_messages: 0, 120 | latest_messages: Some(serialized_msgs), 121 | new_members: Some(new_members), 122 | }; 123 | 124 | send_stats_to_api(stats.clone()) 125 | .await 126 | .map_err(|e| anyhow!("Error al enviar las estadísticas: {}", e))?; 127 | 128 | ctx.say(format!( 129 | "✅ Estadísticas enviadas. Se incluyeron {} mensajes y {} nuevos miembros.", 130 | stats.total_messages, 131 | stats.new_members.as_ref().map_or(0, |v| v.len()) 132 | )) 133 | .await?; 134 | 135 | Ok(()) 136 | } 137 | -------------------------------------------------------------------------------- /src/bot/commands/suggest.rs: -------------------------------------------------------------------------------- 1 | mod cancelled; 2 | mod implemented; 3 | mod new; 4 | 5 | use crate::bot; 6 | 7 | /// Crea, Modifica y administra las sugerencias :D 8 | #[poise::command( 9 | slash_command, 10 | prefix_command, 11 | subcommands("new::nueva", "implemented::implementada", "cancelled::cancelada") 12 | )] 13 | pub async fn sugerencia(_: bot::Context<'_>) -> Result<(), bot::Error> { 14 | Ok(()) 15 | } 16 | -------------------------------------------------------------------------------- /src/bot/commands/suggest/cancelled.rs: -------------------------------------------------------------------------------- 1 | use crate::bot; 2 | 3 | /// Marca una sugerencia como cancelada o que no se realizara 4 | #[poise::command(slash_command, prefix_command)] 5 | pub async fn cancelada(ctx: bot::Context<'_>) -> Result<(), bot::Error> { 6 | // "Sugerencia Marcada como **Cancelada**".to_string() 7 | ctx.say("Esta caracteristica aun no se encuentra disponible") 8 | .await?; 9 | 10 | Ok(()) 11 | } 12 | -------------------------------------------------------------------------------- /src/bot/commands/suggest/implemented.rs: -------------------------------------------------------------------------------- 1 | use crate::bot; 2 | 3 | /// Marca una sugerencia como implementada 4 | #[poise::command(slash_command, prefix_command)] 5 | pub async fn implementada(ctx: bot::Context<'_>) -> Result<(), bot::Error> { 6 | // "Sugerencia Marcada como **Implementada**".to_string() 7 | ctx.say("Esta caracteristica aun no se encuentra disponible".to_string()) 8 | .await?; 9 | 10 | Ok(()) 11 | } 12 | -------------------------------------------------------------------------------- /src/bot/commands/suggest/new.rs: -------------------------------------------------------------------------------- 1 | use poise::serenity_prelude::{ 2 | AutoArchiveDuration, ChannelId, ChannelType, CreateThread, Mentionable, ReactionType, 3 | }; 4 | use tracing::info; 5 | 6 | use crate::bot; 7 | 8 | /// Crea una sugerencia 9 | #[poise::command(slash_command, prefix_command)] 10 | pub async fn nueva( 11 | ctx: bot::Context<'_>, 12 | #[description = "Agrega un Titulo a tu sugerencia"] titulo: String, 13 | #[description = "Cuentanos acerca de tu sugerencia"] contenido: String, 14 | ) -> Result<(), bot::Error> { 15 | info!("Running create suggestion"); 16 | let data = ctx.data(); 17 | 18 | let msg_channel = ChannelId::new(data.secrets.channel_suggest); 19 | 20 | let msg = format!("{} nos sugiere\n\n{contenido}", ctx.author().mention(),); 21 | let msg = msg_channel.say(&ctx, msg).await.unwrap(); 22 | 23 | // Convert string emoji to ReactionType to allow custom emojis 24 | let check_reaction = ReactionType::Unicode("✅".to_string()); 25 | let reject_reaction = ReactionType::Unicode("❌".to_string()); 26 | msg.react(&ctx, check_reaction).await.unwrap(); 27 | msg.react(&ctx, reject_reaction).await.unwrap(); 28 | 29 | let builder = CreateThread::new(titulo.to_string()) 30 | .kind(ChannelType::PublicThread) 31 | .auto_archive_duration(AutoArchiveDuration::ThreeDays); 32 | msg_channel.create_thread(ctx, builder).await.unwrap(); 33 | 34 | ctx.say("Sugerencia Creada").await?; 35 | 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /src/bot/events.rs: -------------------------------------------------------------------------------- 1 | mod anti_spam; 2 | mod compiler; 3 | mod join; 4 | mod new_members_mention; 5 | mod read_github_links; 6 | mod godbolt; 7 | 8 | use poise::serenity_prelude::{Context, FullEvent, GuildId}; 9 | use poise::FrameworkContext; 10 | use tracing::info; 11 | 12 | use crate::bot::{self, Data}; 13 | use crate::CangrebotSecrets; 14 | 15 | pub async fn handle( 16 | ctx: &Context, 17 | event: &FullEvent, 18 | _: FrameworkContext<'_, Data, bot::Error>, 19 | data: &Data, 20 | secrets: &CangrebotSecrets 21 | ) -> Result<(), bot::Error> { 22 | match event { 23 | FullEvent::Ready { data_about_bot, .. } => { 24 | info!("Logged in as {}", data_about_bot.user.name); 25 | } 26 | FullEvent::Message { new_message } => { 27 | if compiler::message(ctx, new_message, &secrets.discord_prefix).await? 28 | || new_members_mention::message(ctx, new_message).await? 29 | || read_github_links::message(ctx, new_message).await 30 | { 31 | return Ok(()); 32 | } 33 | } 34 | 35 | FullEvent::GuildMemberAddition { new_member } => { 36 | join::guild_member_addition(ctx, &GuildId::new(data.secrets.guild_id), new_member) 37 | .await; 38 | } 39 | FullEvent::InteractionCreate { interaction } => { 40 | // for buttons 41 | if let Some(interaction) = interaction.as_message_component() { 42 | if read_github_links::handle_delete_embed(ctx, interaction).await 43 | || read_github_links::handle_save_embed(ctx, interaction).await 44 | { 45 | return Ok(()); 46 | } 47 | } 48 | } 49 | _ => {} 50 | } 51 | 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /src/bot/events/anti_spam.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use poise::serenity_prelude::{ 3 | Channel, ChannelId, Context, GetMessages, Member, Message, Timestamp, UserId, 4 | }; 5 | use std::time::Instant; 6 | use tokio::sync::Mutex; 7 | 8 | use crate::bot; 9 | 10 | #[derive(Debug)] 11 | pub struct MessageTracker { 12 | author_id: UserId, 13 | message_content: String, 14 | channel_ids: Vec, 15 | last_message_time: Instant, 16 | } 17 | 18 | impl MessageTracker { 19 | pub fn builder() -> MessageTrackerBuilder { 20 | MessageTrackerBuilder::default() 21 | } 22 | } 23 | 24 | #[derive(Default)] 25 | pub struct MessageTrackerBuilder { 26 | author_id: Option, 27 | message_content: Option, 28 | channel_ids: Option>, 29 | } 30 | 31 | impl MessageTrackerBuilder { 32 | pub fn author_id(mut self, author_id: UserId) -> Self { 33 | self.author_id = Some(author_id); 34 | self 35 | } 36 | 37 | pub fn message_content(mut self, message_content: String) -> Self { 38 | self.message_content = Some(message_content); 39 | self 40 | } 41 | 42 | pub fn channel_ids(mut self, channel_ids: Vec) -> Self { 43 | self.channel_ids = Some(channel_ids); 44 | self 45 | } 46 | 47 | pub fn build(self) -> Result { 48 | Ok(MessageTracker { 49 | author_id: self.author_id.ok_or("Author id is missing")?, 50 | message_content: self.message_content.ok_or("Message content is missing")?, 51 | channel_ids: self.channel_ids.ok_or("Channel ids are missing")?, 52 | last_message_time: Instant::now(), 53 | }) 54 | } 55 | } 56 | 57 | static MESSAGE_TRACKER: Lazy>> = Lazy::new(|| Mutex::new(Vec::new())); 58 | 59 | // pub fn extract_link(text: &str) -> Option { 60 | // Regex::new(r"(https?://\S+)").map_or(None, |url_re| { 61 | // url_re.find(text).map(|m| m.as_str().to_string()) 62 | // }) 63 | // } 64 | 65 | pub async fn message(ctx: &Context, new_message: &Message) -> Result { 66 | // disable anti spam as it crashes on that unwrap (should be handled.) 67 | return Ok(false); 68 | 69 | let author_id = new_message.author.id; 70 | let mut member = new_message 71 | .guild_id 72 | .unwrap() 73 | .member(&ctx.http, new_message.author.id) 74 | .await?; 75 | let mut message_tracker = MESSAGE_TRACKER.lock().await; 76 | let time = 604800; 77 | let channel_id = new_message.channel_id; 78 | 79 | if let Some(last_message) = message_tracker.iter().last() { 80 | if last_message.author_id == author_id 81 | && last_message.message_content != new_message.content 82 | { 83 | message_tracker.clear(); 84 | } 85 | } 86 | 87 | let message = if let Some(message) = message_tracker 88 | .iter_mut() 89 | .find(|m| m.author_id == author_id && m.message_content == new_message.content) 90 | { 91 | // Inicializa el tiempo del último mensaje 92 | message.last_message_time = Instant::now(); 93 | 94 | // Si el mensaje existe y el canal no está en la lista de canales, añade el canal a la lista de canales 95 | if message.channel_ids.contains(&channel_id) { 96 | // Si el mensaje se repite en el mismo canal, borra el vector 97 | // Debug: println!("Message repeated in the same channel, clearing the vector"); 98 | message_tracker.clear(); 99 | 100 | return Ok(true); 101 | } 102 | 103 | message.channel_ids.push(channel_id); 104 | 105 | message 106 | } else { 107 | // Si el mensaje no existe, crea un nuevo rastreador de mensajes y añádelo a la lista 108 | let message = MessageTracker::builder() 109 | .author_id(author_id) 110 | .message_content(new_message.content.clone()) 111 | .channel_ids(vec![channel_id]) 112 | .build()?; 113 | 114 | message_tracker.push(message); 115 | message_tracker 116 | .last_mut() 117 | .ok_or("Failed to get the last message tracker")? 118 | }; 119 | 120 | if message.channel_ids.len() >= 3 { 121 | apply_timeout(&mut member, ctx, time, new_message).await?; 122 | delete_spam_messages(message, ctx, author_id, &new_message.content).await?; 123 | 124 | // Limpia completamente el rastreador de mensajes para reiniciar el rastreo de mensajes 125 | message_tracker.retain(|m| m.author_id != author_id); 126 | } 127 | // Debug: println!("Tracker: {message_tracker:#?}"); 128 | 129 | drop(message_tracker); 130 | 131 | Ok(false) 132 | } 133 | 134 | async fn delete_spam_messages( 135 | message: &MessageTracker, 136 | ctx: &Context, 137 | author_id: UserId, 138 | message_content: &String, 139 | ) -> Result<(), bot::Error> { 140 | // Borra cada mensaje individualmente 141 | for channel_id in &message.channel_ids { 142 | let channel = channel_id.to_channel(ctx).await?; 143 | let Channel::Guild(channel) = channel else { 144 | return Ok(()); 145 | }; 146 | 147 | let messages = channel.messages(&ctx.http, GetMessages::new()).await?; 148 | for message in messages { 149 | if message.author.id == author_id && &*message.content == message_content { 150 | message.delete(&ctx.http).await?; 151 | } 152 | } 153 | } 154 | 155 | Ok(()) 156 | } 157 | 158 | /// Silencia al autor del mensaje y elimina el mensaje 159 | pub async fn apply_timeout( 160 | member: &mut Member, 161 | ctx: &Context, 162 | time_out_timer: i64, 163 | message: &Message, 164 | ) -> Result<(), bot::Error> { 165 | let time = Timestamp::now().unix_timestamp() + time_out_timer; 166 | let time = Timestamp::from_unix_timestamp(time)?; 167 | member 168 | .disable_communication_until_datetime(&ctx.http, time) 169 | .await?; 170 | message.delete(&ctx.http).await?; 171 | 172 | Ok(()) 173 | } 174 | -------------------------------------------------------------------------------- /src/bot/events/compiler.rs: -------------------------------------------------------------------------------- 1 | use poise::serenity_prelude::{Context, Message}; 2 | use crate::bot::util::send_multiple; 3 | 4 | use super::godbolt::parse_args::{DiscordCompilerOutput, DiscordCompilerCommand}; 5 | 6 | pub async fn message(ctx: &Context, msg: &Message, prefix: &str) -> Result { 7 | if msg.author.bot || !msg.content.starts_with(format!("{}code", prefix).as_str()) { 8 | return Ok(false); 9 | } 10 | 11 | let compile_result = DiscordCompilerCommand::run(&msg.content) 12 | .await 13 | .map_err(|err| format!("{err:#}")); 14 | 15 | let compile_result = match compile_result { 16 | Ok(result) => result, 17 | Err(err) => { 18 | msg.reply(ctx, format!("**Error:** {err}")) 19 | .await 20 | .ok(); 21 | 22 | return Err(err); 23 | } 24 | }; 25 | 26 | let compile_output = match compile_result { 27 | DiscordCompilerOutput::Raw(text) => text.split("").map(|t| t.to_string()).collect(), 28 | DiscordCompilerOutput::Compiler(output) => vec![output.as_discord_message()] 29 | }; 30 | 31 | send_multiple(ctx, msg, compile_output, true) 32 | .await; 33 | 34 | Ok(true) 35 | } 36 | -------------------------------------------------------------------------------- /src/bot/events/godbolt/compiler.rs: -------------------------------------------------------------------------------- 1 | use std::sync::OnceLock; 2 | use poise::serenity_prelude::MESSAGE_CODE_LIMIT; 3 | use reqwest::{Client as HttpClient, Error as ReqwestError}; 4 | use semver::Error as VersionError; 5 | use serde::Deserialize; 6 | use thiserror::Error; 7 | use super::{request::BaseCompilerRequest, response::CompilerResponse, versions::OptionalVersion}; 8 | 9 | #[derive(Error, Debug)] 10 | pub enum GodBoltError { 11 | #[error("An HTTP error occurred when sending a request to GodBolt: {0:#}")] 12 | Http(#[from] ReqwestError), 13 | 14 | #[error("There was an error while parsing versions.")] 15 | VersionParse(#[from] VersionError), 16 | 17 | #[error("The selected compiler doesn't support {0}")] 18 | InvalidOperation(String) 19 | } 20 | 21 | #[derive(Deserialize)] 22 | pub struct GodBoltCompiler { 23 | id: String, 24 | name: String, 25 | #[serde(rename(deserialize = "lang"))] 26 | language: String, 27 | #[serde(rename(deserialize = "semver"))] 28 | version: OptionalVersion, 29 | #[serde(rename(deserialize = "instructionSet"))] 30 | instruction_set: String, 31 | #[serde(rename(deserialize = "supportsBinary"))] 32 | supports_binary: bool, 33 | #[serde(rename(deserialize = "supportsExecute"))] 34 | supports_execute: bool 35 | } 36 | 37 | pub enum CompilationType { 38 | Assembly, 39 | Execution 40 | } 41 | 42 | pub struct GodBoltCompilerOutput { 43 | output: String, 44 | is_success: bool, 45 | version: OptionalVersion, 46 | compiler_name: String, 47 | run_type: CompilationType 48 | } 49 | 50 | impl GodBoltCompilerOutput { 51 | pub fn output(&self) -> &str { 52 | &self.output 53 | } 54 | 55 | pub fn is_success(&self) -> bool { 56 | self.is_success 57 | } 58 | 59 | pub fn version(&self) -> &OptionalVersion { 60 | &self.version 61 | } 62 | 63 | pub fn compiler_name(&self) -> &str { 64 | &self.compiler_name 65 | } 66 | 67 | pub fn run_type(&self) -> &CompilationType { 68 | &self.run_type 69 | } 70 | } 71 | 72 | impl GodBoltCompiler { 73 | pub fn matches(&self, language: &str, version: Option, ins_set: Option) -> bool { 74 | if self.language.trim() != language.trim() { 75 | return false; 76 | } 77 | 78 | if let Some(version_req) = version { 79 | if version_req != self.version { 80 | return false; 81 | } 82 | } else if !self.supports_binary || !self.supports_execute { 83 | return false; 84 | } 85 | 86 | if let Some(ins_set_req) = ins_set { 87 | if self.instruction_set != ins_set_req { 88 | return false; 89 | } 90 | } else if !self.supports_binary || !self.supports_execute { 91 | return false; 92 | } 93 | 94 | true 95 | } 96 | 97 | pub async fn compile(&self, code: &str, user_args: &str, execute: bool) -> Result { 98 | if execute && !self.supports_execute { 99 | return Err(GodBoltError::InvalidOperation("execution".into())); 100 | } 101 | 102 | if !execute && !self.supports_binary { 103 | return Err(GodBoltError::InvalidOperation("compilation".into())) 104 | } 105 | 106 | let response = BaseCompilerRequest::gen_req(code, user_args, execute) 107 | .into_request(&self.id) 108 | .send() 109 | .await? 110 | .json::() 111 | .await?; 112 | 113 | let is_success = response.is_success(); 114 | 115 | Ok(GodBoltCompilerOutput { 116 | is_success, 117 | output: match is_success { 118 | true => response.aggregate_run_out(), 119 | false => response.aggregate_comp_out() 120 | }, 121 | version: self.version.clone(), 122 | compiler_name: self.name.clone(), 123 | run_type: if execute { 124 | CompilationType::Execution 125 | } else { 126 | CompilationType::Assembly 127 | } 128 | }) 129 | } 130 | } 131 | 132 | static AVAILABLE_COMPILERS: OnceLock> = OnceLock::new(); 133 | 134 | pub async fn fetch_compiler(language: &str, version: Option, ins_set: Option) 135 | -> Result, GodBoltError> { 136 | let available_compilers = match AVAILABLE_COMPILERS.get() { 137 | Some(compilers) => compilers, 138 | None => { 139 | let http_client = HttpClient::new(); 140 | 141 | let mut compilers = http_client 142 | .get("https://godbolt.org/api/compilers?fields=id,name,lang,semver,instructionSet,supportsBinary,supportsExecute") 143 | .header("Accept", "application/json") 144 | .send() 145 | .await? 146 | .error_for_status()? 147 | .json::>() 148 | .await? 149 | .into_iter() 150 | .collect::>(); 151 | 152 | compilers.sort_by(|a, b| b 153 | .version 154 | .cmp(&a.version) 155 | ); 156 | 157 | AVAILABLE_COMPILERS.get_or_init(|| compilers) 158 | } 159 | }; 160 | 161 | Ok( 162 | available_compilers 163 | .iter() 164 | .find(|compiler| compiler.matches( 165 | language, 166 | version.clone(), 167 | ins_set.clone() 168 | )) 169 | ) 170 | } 171 | 172 | impl GodBoltCompilerOutput { 173 | pub fn as_discord_message(&self) -> String { 174 | let mut actual_output = self 175 | .output() 176 | .to_string(); 177 | let mut warnings = Vec::new(); 178 | 179 | if let CompilationType::Assembly = self.run_type() { 180 | if actual_output.trim().is_empty() { 181 | warnings.push( 182 | "**Warning:** Mangled sections are filtered by heuristics, consider unmangling relevant sections." 183 | ); 184 | } 185 | } 186 | 187 | // treshold for warnings. 188 | if actual_output.len() > (MESSAGE_CODE_LIMIT - 100) { 189 | actual_output = actual_output[..1840] 190 | .to_string(); 191 | warnings.push( 192 | "**Warning:** The output was trimmed because the output is over 2000 characters long." 193 | ); 194 | } 195 | 196 | let vstr = self.version() 197 | .to_string(); 198 | // hold it (I should re check this.) 199 | let vstr = vstr 200 | .as_str(); 201 | 202 | format!( 203 | "**{}** ({}{})\n```{}\n{}```\n{}", 204 | if self.is_success() { "success" } else { "error" }, 205 | self.compiler_name(), 206 | if self.compiler_name().contains(vstr) { 207 | "".into() 208 | } else { 209 | format!(" {}", vstr) 210 | }, 211 | match self.run_type() { 212 | CompilationType::Assembly if self.is_success() => "x86asm", 213 | _ => "ansi" 214 | }, 215 | if actual_output.is_empty() { 216 | "".into() 217 | } else { 218 | actual_output 219 | }, 220 | warnings.join("\n") 221 | ) 222 | } 223 | } 224 | 225 | impl CompilationType { 226 | pub fn runs(&self) -> bool { 227 | match self { 228 | Self::Assembly => false, 229 | Self::Execution => true 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/bot/events/godbolt/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | pub mod request; 3 | pub mod response; 4 | pub mod compiler; 5 | pub mod parse_args; 6 | pub mod versions; 7 | -------------------------------------------------------------------------------- /src/bot/events/godbolt/parse_args.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use semver::Error as VersionError; 3 | use thiserror::Error; 4 | use super::compiler::{fetch_compiler, CompilationType, GodBoltCompilerOutput, GodBoltError}; 5 | 6 | #[derive(Error, Debug)] 7 | pub enum DiscordCompilerError { 8 | #[error("The message misses a code block.")] 9 | NoCodeBlock, 10 | 11 | #[error("The message has no prefix or it's ambiguous.")] 12 | NoPrefix, 13 | 14 | #[error("You didn't provide a language in the code, please do with `\\`\\`\\`rust`.")] 15 | NoLanguage, 16 | 17 | #[error("Additional characters were found, please remove them.")] 18 | AdditionalCharacters, 19 | 20 | #[error("A bot processed argument is invalid, make sure your arguments don't have white spaces.")] 21 | InvalidBotArg, 22 | 23 | #[error("Error response from GodBolt \"{0}\"")] 24 | CompileError(#[from] GodBoltError), 25 | 26 | #[error("A compiler matching the criteria was not found.")] 27 | CompilerNotFound, 28 | 29 | #[error("Couldn't parse the provided version req.")] 30 | VersionParse(#[from] VersionError) 31 | } 32 | 33 | pub enum DiscordCompilerCommand { 34 | Help, 35 | CompileInput(DiscordCompilerInput) 36 | } 37 | 38 | pub struct DiscordCompilerInput { 39 | compile_type: CompilationType, 40 | bot_args: HashMap, 41 | compiler_args: Vec, 42 | language: String, 43 | code: String 44 | } 45 | 46 | pub enum DiscordCompilerOutput { 47 | Raw(String), 48 | Compiler(GodBoltCompilerOutput) 49 | } 50 | 51 | impl TryFrom for DiscordCompilerCommand { 52 | type Error = DiscordCompilerError; 53 | 54 | fn try_from(value: String) -> Result { 55 | if value.contains("--help") { 56 | return Ok(Self::Help); 57 | } 58 | 59 | let (mut args, next) = value.split_once("```") 60 | .ok_or(DiscordCompilerError::NoCodeBlock) 61 | .map(|(args, next)| (args.to_string(), next.to_string()))?; 62 | 63 | let compile_type = args 64 | .drain(..9) 65 | .as_str() 66 | // hold it. 67 | .to_string(); 68 | // hold it. 69 | let compile_type = compile_type 70 | .split_once(" "); 71 | 72 | let Some((_, compile_type)) = compile_type 73 | else { 74 | return Err(DiscordCompilerError::NoPrefix); 75 | }; 76 | 77 | let compile_type = match compile_type { 78 | "run" => CompilationType::Execution, 79 | "asm" => CompilationType::Assembly, 80 | _ => return Err(DiscordCompilerError::NoPrefix) 81 | }; 82 | 83 | let mut bot_args = HashMap::::new(); 84 | let mut compiler_args = Vec::new(); 85 | 86 | let mut next_is_of: Option = None; 87 | for arg in args.split(" ") { 88 | let arg = arg.trim(); 89 | 90 | if let Some(last) = &next_is_of { 91 | if arg.ends_with("\"") { 92 | let arg_ptr = bot_args 93 | .get_mut(last) 94 | .unwrap(); // key surely exists. 95 | 96 | *arg_ptr += arg; 97 | } 98 | 99 | if arg[..arg.len() - 1].contains("\"") { 100 | return Err(DiscordCompilerError::InvalidBotArg); 101 | } 102 | 103 | continue; 104 | } 105 | 106 | if arg.starts_with("--compiler-") { 107 | let arg_repl = arg.replace("--compiler-", ""); 108 | let (key, value) = &arg_repl 109 | .split_once("=") 110 | .ok_or(DiscordCompilerError::InvalidBotArg)?; 111 | 112 | if value.starts_with("\"") && !value.ends_with("\"") { 113 | next_is_of = Some(key.to_string()); 114 | } 115 | 116 | if value[1..value.len() - 1].contains("\"") { 117 | return Err(DiscordCompilerError::InvalidBotArg); 118 | } 119 | 120 | bot_args.insert(key.to_string(), value.to_string()); 121 | } else { 122 | compiler_args.push(arg.to_string()); 123 | } 124 | } 125 | 126 | let (code, next) = next.split_once("```") 127 | .ok_or(DiscordCompilerError::NoCodeBlock) 128 | .map(|(args, next)| (args.to_string(), next.to_string()))?; 129 | 130 | if !next.replace(" ", "").is_empty() { 131 | return Err(DiscordCompilerError::AdditionalCharacters); 132 | } 133 | 134 | let split_code = code 135 | .splitn(2, |c| c == ' ' || c == '\n') 136 | .map(|val| val.trim()) 137 | .collect::>(); 138 | 139 | Ok(Self::CompileInput(DiscordCompilerInput { 140 | compile_type, 141 | bot_args, 142 | compiler_args, 143 | language: split_code 144 | .get(0) 145 | .ok_or(DiscordCompilerError::NoLanguage)? 146 | .to_string(), 147 | code: split_code 148 | .get(1) 149 | .ok_or(DiscordCompilerError::NoLanguage)? 150 | .to_string() 151 | })) 152 | } 153 | } 154 | 155 | impl DiscordCompilerCommand { 156 | pub async fn run(message: &str) -> Result { 157 | let compiler_input = match Self::try_from(message.to_string())? { 158 | Self::Help => { 159 | return Ok(DiscordCompilerOutput::Raw( 160 | include_str!("../../../../static/compiler_help.txt") 161 | .to_string() 162 | )); 163 | }, 164 | 165 | Self::CompileInput(input) => input 166 | }; 167 | 168 | Ok(DiscordCompilerOutput::Compiler( 169 | fetch_compiler( 170 | &compiler_input.language, 171 | compiler_input.bot_args 172 | .get("version") 173 | .cloned() 174 | .map(|v| v.as_str().into()), 175 | compiler_input.bot_args 176 | .get("arch") 177 | .cloned() 178 | ) 179 | .await? 180 | .ok_or(DiscordCompilerError::CompilerNotFound)? 181 | .compile( 182 | &compiler_input.code, 183 | &compiler_input.compiler_args.join(" "), 184 | compiler_input.compile_type.runs() 185 | ) 186 | .await? 187 | )) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/bot/events/godbolt/request.rs: -------------------------------------------------------------------------------- 1 | use reqwest::{Client as HttpClient, RequestBuilder}; 2 | use serde::Serialize; 3 | 4 | #[derive(Serialize)] 5 | pub struct BaseCompilerRequest { 6 | source: String, 7 | options: BaseCompilerOptions 8 | } 9 | 10 | #[derive(Serialize)] 11 | pub struct BaseCompilerOptions { 12 | #[serde(rename(serialize = "userArguments"))] 13 | user_arguments: String, 14 | #[serde(rename(serialize = "compilerOptions"))] 15 | compiler_options: SpecificCompilerOptions, 16 | filters: SpecificCompilerFilters 17 | } 18 | 19 | #[derive(Serialize)] 20 | pub struct SpecificCompilerOptions { 21 | #[serde(rename(serialize = "executorRequest"))] 22 | executor_request: bool 23 | } 24 | 25 | #[derive(Serialize)] 26 | pub struct SpecificCompilerFilters { 27 | intel: bool, 28 | demangle: bool 29 | } 30 | 31 | impl BaseCompilerRequest { 32 | pub fn gen_req(source: &str, user_args: &str, execute: bool) -> Self { 33 | Self { 34 | source: source.to_string(), 35 | options: BaseCompilerOptions { 36 | user_arguments: user_args.to_string(), 37 | compiler_options: SpecificCompilerOptions { 38 | executor_request: execute 39 | }, 40 | filters: SpecificCompilerFilters { 41 | intel: true, 42 | demangle: false 43 | } 44 | } 45 | } 46 | } 47 | 48 | pub fn into_request(self, compiler_id: &str) -> RequestBuilder { 49 | HttpClient::new() 50 | .post(format!("https://godbolt.org/api/compiler/{compiler_id}/compile")) 51 | .header("Content-Type", "application/json") 52 | .header("Accept", "application/json") 53 | .json(&self) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/bot/events/godbolt/response.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | macro_rules! aggregate { 4 | ($input:expr) => {{ 5 | $input 6 | .into_iter() 7 | .map(|entry| entry.text) 8 | .collect::>() 9 | .join("\n") 10 | }} 11 | } 12 | 13 | #[derive(Deserialize)] 14 | #[serde(untagged)] 15 | pub enum CompilerResponse { 16 | AssemblyResponse(BaseAssemblyResponse), 17 | RunResponse(BaseRunResponse) 18 | } 19 | 20 | #[derive(Deserialize)] 21 | pub struct BaseAssemblyResponse { 22 | asm: Vec, 23 | stdout: Vec, 24 | stderr: Vec, 25 | #[serde(rename(deserialize = "code"))] 26 | exit_code: i32 27 | } 28 | 29 | #[derive(Deserialize)] 30 | pub struct BaseRunResponse { 31 | stdout: Vec, 32 | stderr: Vec, 33 | #[serde(rename(deserialize = "didExecute"))] 34 | did_run: bool, 35 | #[serde(rename(deserialize = "buildResult"))] 36 | build_result: RunCompilerOutput 37 | } 38 | 39 | #[derive(Deserialize)] 40 | pub struct RunCompilerOutput { 41 | stdout: Vec, 42 | stderr: Vec, 43 | } 44 | 45 | #[derive(Deserialize)] 46 | pub struct DataEntry { 47 | text: String 48 | } 49 | 50 | impl CompilerResponse { 51 | pub fn aggregate_run_out(self) -> String { 52 | match self { 53 | Self::AssemblyResponse(res) => res.aggregate_source(), 54 | Self::RunResponse(res) => res.aggregate_run_out() 55 | } 56 | } 57 | 58 | pub fn aggregate_comp_out(self) -> String { 59 | match self { 60 | Self::AssemblyResponse(res) => res.aggregate_comp_out(), 61 | Self::RunResponse(res) => res.aggregate_comp_out() 62 | } 63 | } 64 | 65 | pub fn is_success(&self) -> bool { 66 | match self { 67 | Self::AssemblyResponse(res) => res.success(), 68 | Self::RunResponse(res) => res.success() 69 | } 70 | } 71 | } 72 | 73 | impl BaseAssemblyResponse { 74 | pub fn aggregate_source(self) -> String { 75 | aggregate!(self.asm) 76 | } 77 | 78 | pub fn aggregate_comp_out(self) -> String { 79 | let mut aggregated = Vec::new(); 80 | 81 | aggregated.extend(self.stderr); 82 | aggregated.extend(self.stdout); 83 | 84 | aggregate!(aggregated) 85 | } 86 | 87 | pub fn success(&self) -> bool { 88 | self.exit_code == 0 89 | } 90 | } 91 | 92 | impl BaseRunResponse { 93 | pub fn aggregate_run_out(self) -> String { 94 | let mut aggregated = Vec::new(); 95 | 96 | aggregated.extend(self.stderr); 97 | aggregated.extend(self.stdout); 98 | 99 | aggregate!(aggregated) 100 | } 101 | 102 | pub fn aggregate_comp_out(self) -> String { 103 | let mut aggregated = Vec::new(); 104 | 105 | aggregated.extend(self.build_result.stderr); 106 | aggregated.extend(self.build_result.stdout); 107 | 108 | aggregate!(aggregated) 109 | } 110 | 111 | pub fn success(&self) -> bool { 112 | self.did_run 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/bot/events/godbolt/versions.rs: -------------------------------------------------------------------------------- 1 | use std::cmp::Ordering; 2 | use serde::{Deserialize, Deserializer}; 3 | 4 | #[derive(Eq, PartialOrd, Clone)] 5 | pub struct OptionalVersion { 6 | major: Option, 7 | minor: Option, 8 | patch: Option, 9 | 10 | extra_len: usize 11 | } 12 | 13 | impl OptionalVersion { 14 | pub fn trim_ver_from_len(&mut self) { 15 | self.extra_len -= self.to_string().len(); 16 | } 17 | 18 | pub fn is_none(&self) -> bool { 19 | self.major.is_none() && 20 | self.minor.is_none() && 21 | self.patch.is_none() 22 | } 23 | } 24 | 25 | impl From<&str> for OptionalVersion { 26 | fn from(value: &str) -> Self { 27 | let mut major = None; 28 | let mut minor = None; 29 | let mut patch = None; 30 | 31 | for word in value.split_whitespace().rev() { 32 | let mut parts = word 33 | .split('.') 34 | .filter_map(|s| s.parse::().ok()); 35 | 36 | if let Some(m) = parts.next() { 37 | major = Some(m); 38 | minor = parts.next(); 39 | patch = parts.next(); 40 | break; 41 | } 42 | } 43 | 44 | let mut value = Self { major, minor, patch, extra_len: value.len() }; 45 | value.trim_ver_from_len(); 46 | value 47 | } 48 | } 49 | 50 | impl PartialEq for OptionalVersion { 51 | fn eq(&self, other: &Self) -> bool { 52 | self.major.filter(|m| m != &0) == other.major.filter(|m| m != &0) && 53 | self.minor.filter(|m| m != &0) == other.minor.filter(|m| m != &0) && 54 | self.patch.filter(|p| p != &0) == other.patch.filter(|p| p != &0) 55 | } 56 | } 57 | 58 | impl Ord for OptionalVersion { 59 | fn cmp(&self, other: &Self) -> Ordering { 60 | let s_is_none = self.is_none(); 61 | let o_is_none = other.is_none(); 62 | 63 | if s_is_none && !o_is_none { 64 | Ordering::Less 65 | } else if !s_is_none && o_is_none { 66 | Ordering::Greater 67 | } else if self.extra_len > other.extra_len { 68 | Ordering::Less 69 | } else if self.extra_len < other.extra_len { 70 | Ordering::Greater 71 | } else { 72 | self.major 73 | .cmp(&other.major) 74 | .then_with(|| self.minor 75 | .cmp(&other.minor) 76 | ) 77 | .then_with(|| self.patch 78 | .cmp(&other.patch) 79 | ) 80 | } 81 | } 82 | } 83 | 84 | impl<'de> Deserialize<'de> for OptionalVersion { 85 | fn deserialize>(deserializer: D) -> Result { 86 | let s: &str = Deserialize::deserialize(deserializer)?; 87 | Ok(Self::from(s)) 88 | } 89 | } 90 | 91 | impl ToString for OptionalVersion { 92 | fn to_string(&self) -> String { 93 | match (self.major, self.minor, self.patch) { 94 | (Some(major), Some(minor), Some(patch)) => format!("{}.{}.{}", major, minor, patch), 95 | (Some(major), Some(minor), None) => format!("{}.{}", major, minor), 96 | (Some(major), None, None) => format!("{}", major), 97 | _ => String::new() 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/bot/events/join.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use poise::serenity_prelude::{ 3 | ChannelId, Context, CreateAttachment, CreateMessage, GuildId, Member, Mentionable, ReactionType, 4 | }; 5 | use std::convert::TryFrom; 6 | 7 | const DEFAULT_NEWER_ROLE: u64 = 1263861260932485194; 8 | 9 | const WELCOME_MESSAGE: &str = r#"¡Bienvenidx a la Comunidad de RustLangES! 10 | 11 | Nos alegra que hayas decidido unirte a nuestra comunidad. Aquí encontrarás varios canales dedicados a diferentes aspectos de nuestra comunidad: 12 | 13 | - [#anuncios-de-la-comunidad](): Este es el lugar donde compartimos las últimas novedades y eventos de nuestra comunidad. ¡Mantente al tanto de lo que está sucediendo! 14 | - [#show-case](): ¿Has creado algo increíble con Rust? ¡Este es el canal perfecto para compartirlo con el resto de la comunidad! 15 | - [#proyectos-comunitarios](): Aquí se discuten los proyectos que estamos desarrollando como comunidad, como nuestra página web, blog y bot. ¡Participa y ayúdanos a mejorar! 16 | - [#retos-diarios](): ¿Quieres poner a prueba tus habilidades de programación? ¡Únete a los retos diarios y comparte tus soluciones! 17 | - [#principiantes](): Si estas empezando en Rust, este es el lugar perfecto para encontrar ayuda y recursos para empezar. 18 | 19 | Recuerda revisar los mensajes fijados en cada canal para obtener más información. 20 | 21 | > **Nota:** Es posible que para acceder a algunos canales necesites de un rol especifico 22 | > por lo que te recomendamos que te asignes los roles que te interesen 23 | 24 | ¡No olvides seguirnos en nuestras redes sociales y visitar nuestras webs para mantenerte al día con todo lo que sucede en nuestra comunidad! 25 | 26 | > **Web:** https://rustlang-es.org 27 | > **Blog:** 28 | > **Recursos para aprender Rust:** https://rustlang-es.org/aprende 29 | > **GitHub:** 30 | > **Linkedin:** 31 | 32 | ¡Bienvenidx una vez más y disfruta de tu estancia en nuestro servidor!"#; 33 | 34 | pub async fn guild_member_addition(ctx: &Context, guild_id: &GuildId, member: &Member) { 35 | if let Err(e) = _guild_member_addition(ctx, guild_id, member).await { 36 | tracing::error!("Failed to handle welcome guild_member_addition: {}", e); 37 | } 38 | } 39 | 40 | #[tracing::instrument(skip(ctx))] 41 | async fn _guild_member_addition(ctx: &Context, guild_id: &GuildId, member: &Member) -> Result<()> { 42 | let join_msg = "Bienvenid@ a ! Pásala lindo!".to_string(); 43 | 44 | let msg_channel = ChannelId::new(778674893851983932_u64); 45 | 46 | let join_msg_replaced = join_msg 47 | .replace("", &member.user.mention().to_string()) 48 | .replace("", &member.distinct()) 49 | .replace("", &guild_id.name(ctx).unwrap_or_else(|| "".into())); 50 | 51 | // Download the user's avatar and create a welcome image 52 | let avatar_url = member.face(); 53 | let response = reqwest::get(avatar_url).await?; 54 | let avatar = response.bytes().await?; 55 | 56 | let output_path = format!("/tmp/{}_welcome.png", member.user.name); 57 | 58 | gen_welcome::generate( 59 | &format!( 60 | "{}/welcome_background.png", 61 | std::env::var("STATIC_ROOT") 62 | .as_deref() 63 | .unwrap_or_else(|_| "static") 64 | ), 65 | &avatar, 66 | &member.distinct(), 67 | guild_id 68 | .to_guild_cached(ctx) 69 | .map(|g| g.member_count as usize), 70 | include_bytes!("../../../static/fonts/WorkSans-Bold.ttf"), 71 | include_bytes!("../../../static/fonts/WorkSans-Regular.ttf"), 72 | &output_path, 73 | ) 74 | .expect("Cannot generate welcome image"); 75 | 76 | let attachment = CreateAttachment::path(output_path.as_str()).await?; 77 | 78 | let msg = msg_channel 79 | .send_files( 80 | &ctx, 81 | vec![attachment], 82 | CreateMessage::new().content(&join_msg_replaced), 83 | ) 84 | .await?; 85 | 86 | // Remove the file after sending the message 87 | std::fs::remove_file(&output_path)?; 88 | 89 | // Convert string emoji to ReactionType to allow custom emojis 90 | let reaction = ReactionType::try_from("👋")?; 91 | msg.react(ctx, reaction).await?; 92 | 93 | // asign default role to track 94 | member.add_role(ctx, DEFAULT_NEWER_ROLE).await?; 95 | 96 | // Send DM with guides 97 | member 98 | .user 99 | .dm(ctx, CreateMessage::new().content(WELCOME_MESSAGE)) 100 | .await?; 101 | 102 | Ok(()) 103 | } 104 | -------------------------------------------------------------------------------- /src/bot/events/new_members_mention.rs: -------------------------------------------------------------------------------- 1 | use poise::serenity_prelude::{ChannelId, Context, Message, RoleId}; 2 | use tokio_schedule::Job; 3 | 4 | use crate::bot; 5 | 6 | const NEW_MEMBERS_ROLE_ID: RoleId = RoleId::new(1263861260932485194); 7 | const WELCOME_CHANNEL: ChannelId = ChannelId::new(778674893851983932); 8 | // const INTERNAL_LOGS: ChannelId = ChannelId::new(1230718736206532628); 9 | 10 | // async fn log(ctx: &Context, msg: impl serde::Serialize) { 11 | // ctx.http 12 | // .send_message(INTERNAL_LOGS, Vec::new(), &msg) 13 | // .await 14 | // .unwrap(); 15 | // } 16 | 17 | pub async fn message(ctx: &Context, msg: &Message) -> Result { 18 | if !(msg.mention_roles.contains(&NEW_MEMBERS_ROLE_ID) && msg.channel_id == WELCOME_CHANNEL) { 19 | return Ok(false); 20 | } 21 | 22 | let members = msg 23 | .guild(ctx.cache.as_ref()) 24 | .unwrap() 25 | .members 26 | .iter() 27 | .filter(|&(_, v)| v.roles.contains(&NEW_MEMBERS_ROLE_ID)).map(|(_, v)| v.clone()) 28 | .collect::>(); 29 | 30 | tracing::info!("New Members: {}", members.len()); 31 | 32 | { 33 | let ctx = ctx.clone(); 34 | let members = members.clone(); 35 | let remove_role = tokio_schedule::every(30) 36 | .minute() 37 | .until(&(chrono::Utc::now() + chrono::Duration::hours(1))) 38 | .in_timezone(&chrono::Utc) 39 | .perform(move || { 40 | let ctx = ctx.clone(); 41 | let members = members.clone(); 42 | async move { 43 | for v in members.iter() { 44 | if let Err(e) = v.remove_role(&ctx, NEW_MEMBERS_ROLE_ID).await { 45 | tracing::error!( 46 | "Failed to remove role of: {} - {:?}\nReason: {e:?}", 47 | v.display_name(), 48 | v.nick 49 | ); 50 | } 51 | } 52 | } 53 | }); 54 | 55 | tokio::spawn(remove_role); 56 | } 57 | 58 | Ok(true) 59 | } 60 | -------------------------------------------------------------------------------- /src/bot/events/read_github_links.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use poise::serenity_prelude::{ButtonStyle, ComponentInteraction, Context, CreateButton, CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, Message, ReactionType, MESSAGE_CODE_LIMIT}; 3 | use regex::{Captures, Regex}; 4 | use reqwest::get; 5 | use std::collections::{HashMap, HashSet}; 6 | use std::option::Option; 7 | 8 | lazy_static! { 9 | static ref COMMENT_TEMPLATES: HashMap<&'static str, &'static str> = HashMap::from([ 10 | ("c", "// {}"), 11 | ("cpp", "// {}"), 12 | ("cs", "// {}"), 13 | ("java", "// {}"), 14 | ("js", "// {}"), 15 | ("go", "// {}"), 16 | ("kt", "// {}"), 17 | ("swift", "// {}"), 18 | ("rs", "// {}"), 19 | ("scala", "// {}"), 20 | ("py", "# {}"), 21 | ("sh", "# {}"), 22 | ("pl", "# {}"), 23 | ("rb", "# {}"), 24 | ("r", "# {}"), 25 | ("ps1", "# {}"), 26 | ("php", "// {}"), 27 | ("sql", "-- {}"), 28 | ("html", ""), 29 | ("xml", ""), 30 | ("css", "/* {} */"), 31 | ("lisp", "; {}"), 32 | ("scm", "; {}"), 33 | ("hs", "-- {}"), 34 | ("m", "% {}"), 35 | ("asm", "; {}"), 36 | ("pro", "% {}"), 37 | ("vim", "\" {}"), 38 | ("ini", "; {}"), 39 | ("jl", "# {}"), 40 | ("erl", "% {}"), 41 | ("ex", "# {}"), 42 | ("lua", "-- {}"), 43 | ("tcl", "# {}"), 44 | ("yml", "# {}"), 45 | ("md", "[comment]: # ({})"), 46 | ("lhs", "-- {}"), 47 | ]); 48 | } 49 | 50 | pub enum RangeOrIndex { 51 | Language(String), 52 | Index(String, i32), 53 | Range(String, i32, i32), 54 | } 55 | 56 | fn parse_url(url: &str) -> Option { 57 | let extension_regex = Regex::new(r"\.([^./?#]+)(#|$)").unwrap(); 58 | 59 | let range_regex = 60 | Regex::new(r"(?:\.(?[^#]+))?(?:#L(?\d+)?(?:-L(?\d+))?)?$").unwrap(); 61 | 62 | let language = extension_regex 63 | .captures(url) 64 | .and_then(|caps| caps.get(1)) 65 | .map(|m| m.as_str().to_string()) 66 | .unwrap_or_default(); 67 | 68 | if let Some(caps) = range_regex.captures(url) { 69 | let start = caps 70 | .name("start") 71 | .and_then(|m| m.as_str().parse::().ok()); 72 | let end = caps 73 | .name("end") 74 | .and_then(|m| m.as_str().parse::().ok()); 75 | 76 | if end < start { 77 | return None; 78 | } 79 | 80 | match (start, end) { 81 | (Some(start), Some(end)) => Some(RangeOrIndex::Range(language, start - 1, end)), 82 | (Some(start), None) => Some(RangeOrIndex::Index(language, start - 1)), 83 | (None, None) => Some(RangeOrIndex::Language(language)), 84 | _ => None, 85 | } 86 | } else { 87 | None 88 | } 89 | } 90 | 91 | fn trim_message(lang: String, content: String) -> String { 92 | if content.len() > MESSAGE_CODE_LIMIT { 93 | content[0..MESSAGE_CODE_LIMIT - 200].to_string() 94 | + &*format!( 95 | "\n{}", 96 | COMMENT_TEMPLATES 97 | .get(&*lang) 98 | .unwrap_or(&"// {}") 99 | .replace("{}", "El mensaje fue cortado por limite de caracteres.") 100 | ) 101 | } else { 102 | content 103 | } 104 | } 105 | 106 | async fn read_message(link: String) -> Option { 107 | if let Ok(result) = get(&link).await { 108 | if result.status() == 200 { 109 | if let Ok(text) = result.text().await { 110 | let parsed = parse_url(&link)?; 111 | 112 | let subtext: Vec<&str> = text.split('\n').collect(); 113 | 114 | return match parsed { 115 | RangeOrIndex::Language(language) => Some(format!( 116 | "Mostrando <{link}>\n```{}\n{}\n```", 117 | language.to_owned(), 118 | trim_message(language, text) 119 | )), 120 | RangeOrIndex::Index(language, index) => { 121 | if index < subtext.len() as i32 { 122 | Some(format!( 123 | "Mostrando linea {} de <{link}>\n```{}\n{}\n```", 124 | index + 1, 125 | language.to_owned(), 126 | trim_message(language, subtext[index as usize].to_string()) 127 | )) 128 | } else { 129 | None 130 | } 131 | } 132 | RangeOrIndex::Range(language, start, end) => { 133 | if start < subtext.len() as i32 && end <= subtext.len() as i32 { 134 | Some(format!( 135 | "Mostrando desde la linea {} hasta la linea {end} de <{link}>\n```{}\n{}\n```", 136 | start + 1, 137 | language.to_owned(), 138 | trim_message(language, subtext[start as usize..end as usize].join("\n")) 139 | )) 140 | } else { 141 | None 142 | } 143 | } 144 | }; 145 | } 146 | } 147 | } 148 | None 149 | } 150 | 151 | pub async fn message(ctx: &Context, msg: &Message) -> bool { 152 | if msg.author.bot { 153 | return false; 154 | } 155 | 156 | let repo_regex = Regex::new("(https://github\\.com/(?:[^/]+/){2})blob/(.*)").unwrap(); 157 | let hidden_link_regex = Regex::new(r"[<>]").unwrap(); 158 | 159 | let replaced = if repo_regex.is_match(&msg.content) { 160 | repo_regex.replace_all(&msg.content, |captures: &Captures| { 161 | captures[1].to_string() + &captures[2] 162 | }) 163 | } else { 164 | return false; 165 | } 166 | .replace("https://github.com/", "https://raw.githubusercontent.com/"); 167 | 168 | let without_hidden = hidden_link_regex.replace_all(&replaced, ""); 169 | 170 | let without_spaces = without_hidden.split('\n'); 171 | 172 | let links = without_spaces 173 | .filter(|s| !s.starts_with('!') && s.starts_with("https://raw.githubusercontent.com/")); 174 | 175 | let dup = HashSet::<&str>::from_iter(links); 176 | for link in dup { 177 | if let Some(content) = read_message(link.to_string()).await { 178 | let message = CreateMessage::new() 179 | .content(content) 180 | .button( 181 | CreateButton::new("delete_github_embed") 182 | .label("Borrar") 183 | .style(ButtonStyle::Danger) 184 | .emoji(ReactionType::try_from("🗑️").unwrap()) 185 | ) 186 | .button( 187 | CreateButton::new("save_github_embed") 188 | .label("Guardar") 189 | .style(ButtonStyle::Secondary) 190 | .emoji(ReactionType::try_from("💾").unwrap()) 191 | ); 192 | 193 | if let Some(reference) = &msg.message_reference { 194 | msg.channel_id 195 | .send_message( 196 | &ctx, 197 | message 198 | .reference_message(reference.clone()) 199 | ) 200 | .await 201 | .unwrap(); 202 | } else { 203 | msg.channel_id 204 | .send_message( 205 | &ctx, 206 | message 207 | .reference_message(msg) 208 | ) 209 | .await 210 | .unwrap(); 211 | } 212 | } 213 | } 214 | 215 | true 216 | } 217 | 218 | pub async fn handle_delete_embed(ctx: &Context, interaction: &ComponentInteraction) -> bool { 219 | if interaction.data.custom_id != "delete_github_embed" { 220 | return false; 221 | } 222 | 223 | if interaction.message.mentions.first().map(|m| m.id != interaction.user.id).unwrap_or(true) { 224 | interaction 225 | .create_response( 226 | ctx, 227 | CreateInteractionResponse::Message( 228 | CreateInteractionResponseMessage::new() 229 | .ephemeral(true) 230 | .content("El bloque de codigo no era para ti.") 231 | ) 232 | ) 233 | .await 234 | .ok(); 235 | 236 | return true; 237 | } 238 | 239 | interaction.message.delete(&ctx) 240 | .await 241 | .ok(); 242 | 243 | true 244 | } 245 | 246 | pub async fn handle_save_embed(ctx: &Context, interaction: &ComponentInteraction) -> bool { 247 | if interaction.data.custom_id != "save_github_embed" { 248 | return false; 249 | } 250 | 251 | let send_result = interaction 252 | .user 253 | .dm( 254 | ctx, 255 | CreateMessage::new() 256 | .content( 257 | interaction 258 | .message 259 | .content 260 | .clone() 261 | .replacen("Mostrando", "El codigo que solicitaste:", 1) 262 | ) 263 | ) 264 | .await; 265 | 266 | match send_result { 267 | Ok(_) => interaction 268 | .create_response( 269 | ctx, 270 | CreateInteractionResponse::Message( 271 | CreateInteractionResponseMessage::new() 272 | .content("Enviado. Revisa tus mensajes directos con el bot.") 273 | .ephemeral(true) 274 | ) 275 | ), 276 | Err(err) => interaction 277 | .create_response( 278 | ctx, 279 | CreateInteractionResponse::Message( 280 | CreateInteractionResponseMessage::new() 281 | .content(format!("Error: {err}")) 282 | .ephemeral(true) 283 | ) 284 | ) 285 | } 286 | .await 287 | .ok(); 288 | 289 | return true; 290 | } 291 | -------------------------------------------------------------------------------- /src/bot/mod.rs: -------------------------------------------------------------------------------- 1 | mod commands; 2 | mod events; 3 | mod util; 4 | 5 | use std::sync::Arc; 6 | 7 | use anyhow::anyhow; 8 | use poise::serenity_prelude::{futures::TryFutureExt, GuildId}; 9 | use tracing::info; 10 | 11 | use crate::{serenity, CangrebotSecrets}; 12 | 13 | use commands::commands; 14 | 15 | pub(super) struct Data { 16 | secrets: CangrebotSecrets, 17 | } 18 | 19 | pub(super) type Error = Box; 20 | pub(super) type Context<'a> = poise::Context<'a, Data, Error>; 21 | 22 | pub async fn setup(secrets: &CangrebotSecrets) -> Result { 23 | let guild_id = GuildId::new(secrets.guild_id); 24 | 25 | let intents = serenity::GatewayIntents::all(); 26 | 27 | let data_secrets = secrets.clone(); 28 | let framework = poise::Framework::builder() 29 | .options(poise::FrameworkOptions { 30 | commands: commands(), 31 | 32 | event_handler: |ctx, event, framework, data| { 33 | Box::pin( 34 | events::handle(ctx, event, framework, data, &data.secrets) 35 | .inspect_err(|err| println!("{err:#}")) 36 | ) 37 | }, 38 | 39 | pre_command: |ctx| { 40 | Box::pin(async move { 41 | info!( 42 | "Got command '{}' by user '{}'", 43 | ctx.command().qualified_name, 44 | ctx.author().name 45 | ); 46 | }) 47 | }, 48 | 49 | prefix_options: poise::PrefixFrameworkOptions { 50 | prefix: Some(secrets.discord_prefix.clone()), 51 | mention_as_prefix: true, 52 | ignore_bots: true, 53 | ..Default::default() 54 | }, 55 | 56 | ..Default::default() 57 | }) 58 | .setup(move |ctx, _ready, framework| { 59 | Box::pin(async move { 60 | let commands = &framework.options().commands; 61 | poise::builtins::register_globally(ctx, commands).await?; 62 | poise::builtins::register_in_guild(ctx, commands, guild_id).await?; 63 | Ok(Data { 64 | secrets: data_secrets, 65 | }) 66 | }) 67 | }) 68 | .build(); 69 | 70 | serenity::ClientBuilder::new(&secrets.discord_token, intents) 71 | .framework(framework) 72 | .await 73 | .map_err(|err| anyhow!("Error crating client: {err}")) 74 | } 75 | -------------------------------------------------------------------------------- /src/bot/util.rs: -------------------------------------------------------------------------------- 1 | use poise::serenity_prelude::{Context, CreateMessage, Message}; 2 | 3 | const MASKS: [(&str, &str); 4] = [ 4 | ("github.com/", "github:"), 5 | ("gitlab.com/", "gitlab:"), 6 | ("docs.rs/", "docs.rs:"), 7 | ("crates.io/crates/", "crates:"), 8 | ]; 9 | 10 | pub fn mask_url(url: String) -> String { 11 | let mut masked = url.replace("https://", "").replace("http://", ""); 12 | 13 | for (mask, repl) in &MASKS { 14 | if masked.starts_with(mask) { 15 | masked = masked.replace(mask, repl); 16 | break; 17 | } 18 | } 19 | 20 | format!("[{masked}]({url})") 21 | } 22 | 23 | pub async fn send_multiple( 24 | ctx: &Context, 25 | caller: &Message, 26 | msgs: Vec, 27 | mut reply: bool 28 | ) { 29 | for msg in msgs { 30 | if reply { 31 | caller 32 | .reply(ctx, msg) 33 | .await 34 | .ok(); 35 | 36 | reply = false; 37 | } else { 38 | caller 39 | .channel_id 40 | .send_message( 41 | ctx, 42 | CreateMessage::new() 43 | .content(msg) 44 | ) 45 | .await 46 | .ok(); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | pub use poise::serenity_prelude as serenity; 2 | pub use secrets::CangrebotSecrets; 3 | 4 | mod api; 5 | mod bot; 6 | mod secrets; 7 | 8 | #[tokio::main] 9 | async fn main() -> Result<(), Box> { 10 | color_eyre::install().expect("Failed to install color_eyre"); 11 | 12 | let secrets = CangrebotSecrets::from(std::env::var); 13 | 14 | let mut discord_bot = bot::setup(&secrets).await?; 15 | let router = api::build_router(&secrets, discord_bot.http.clone()); 16 | 17 | let port = format!( 18 | "{}:{}", 19 | std::env::var("BOT_API_ADDR") 20 | .as_deref() 21 | .unwrap_or("0.0.0.0"), 22 | std::env::var("BOT_API_PORT").as_deref().unwrap_or("8080") 23 | ); 24 | let listener = tokio::net::TcpListener::bind(port).await?; 25 | let serve_router = async move { 26 | axum::serve(listener, router.into_make_service()) 27 | .await 28 | .unwrap(); 29 | }; 30 | 31 | tokio::select! { 32 | _ = discord_bot.start_autosharded() => {}, 33 | _ = serve_router => {}, 34 | }; 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /src/secrets.rs: -------------------------------------------------------------------------------- 1 | use dotenvy::dotenv; 2 | use tracing::warn; 3 | 4 | #[derive(Clone, Debug)] 5 | pub struct CangrebotSecrets { 6 | /// Closed key for API communication 7 | pub api_key: String, 8 | /// Channel id for Daily Challenges 9 | pub channel_daily: u64, 10 | /// Channel id for Suggest 11 | pub channel_suggest: u64, 12 | /// Prefix for text commands. Defaults to "&" 13 | pub discord_prefix: String, 14 | /// Discord Bot Token 15 | pub discord_token: String, 16 | /// Server id 17 | pub guild_id: u64, 18 | } 19 | 20 | impl CangrebotSecrets { 21 | pub fn from<'a>(secrets: fn(&'a str) -> Result) -> Self { 22 | dotenv().ok(); 23 | Self { 24 | api_key: secrets("BOT_APIKEY").expect("'BOT_APIKEY' was not found"), 25 | channel_daily: secrets("CHANNEL_DAILY") 26 | .expect("'CHANNEL_DAILY' was not found") 27 | .parse() 28 | .expect("Cannot parse 'CHANNEL_DAILY'"), 29 | channel_suggest: secrets("CHANNEL_SUGGEST") 30 | .expect("'CHANNEL_SUGGEST' was not found") 31 | .parse() 32 | .expect("Cannot parse 'CHANNEL_SUGGEST'"), 33 | discord_prefix: secrets("DISCORD_PREFIX").unwrap_or_else(|_| { 34 | warn!("'DISCORD_PREFIX' was not found. Defaults to \"&\""); 35 | "&".to_owned() 36 | }), 37 | discord_token: secrets("DISCORD_TOKEN").expect("'DISCORD_TOKEN' was not found"), 38 | guild_id: secrets("GUILD_ID") 39 | .expect("'GUILD_ID' was not found") 40 | .parse() 41 | .expect("Cannot parse 'GUILD_ID'"), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | pub trait ToSnakeCase: AsRef { 2 | fn to_snake_case(&self) -> String; 3 | } 4 | 5 | impl ToSnakeCase for T 6 | where 7 | T: AsRef, 8 | { 9 | fn to_snake_case(&self) -> String { 10 | let text = self.as_ref(); 11 | 12 | let mut buffer = String::with_capacity(text.len() + text.len() / 2); 13 | 14 | let mut text = text.chars(); 15 | 16 | if let Some(first) = text.next() { 17 | let mut n2: Option<(bool, char)> = None; 18 | let mut n1: (bool, char) = (first.is_lowercase(), first); 19 | 20 | for c in text { 21 | let prev_n1 = n1.clone(); 22 | 23 | let n3 = n2; 24 | n2 = Some(n1); 25 | n1 = (c.is_lowercase(), c); 26 | 27 | // insert underscore if acronym at beginning 28 | // ABc -> a_bc 29 | if let (Some((false, c3)), Some((false, c2))) = (n3, n2) { 30 | if n1.0 && c3.is_uppercase() && c2.is_uppercase() { 31 | buffer.push('_'); 32 | } 33 | } 34 | 35 | buffer.push_str(&prev_n1.1.to_lowercase().to_string()); 36 | 37 | // insert underscore before next word 38 | // abC -> ab_c 39 | if let Some((true, _)) = n2 { 40 | if n1.1.is_uppercase() { 41 | buffer.push('_'); 42 | } 43 | } 44 | } 45 | 46 | buffer.push_str(&n1.1.to_lowercase().to_string()); 47 | } 48 | 49 | buffer 50 | } 51 | } 52 | 53 | const MASKS: [(&str, &str); 4] = [ 54 | ("https://github.com/", "github:"), 55 | ("https://gitlab.com/", "gitlab:"), 56 | ("https://docs.rs/", "docs.rs:"), 57 | ("https://crates.io/crates", "crates:"), 58 | ]; 59 | 60 | pub fn mask_url(url: &str) -> String { 61 | for (mask, repl) in &MASKS { 62 | if url.starts_with(mask) { 63 | return url.replace(mask, repl); 64 | } 65 | } 66 | 67 | url.replace("https://", "").replace("http://", "") 68 | } 69 | -------------------------------------------------------------------------------- /static/Cloudkicker_-_Loops_-_22_2011_07.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustLangES/cangrebot/435cab9cc01fb77e3960cd977f1e484f77e4ea9b/static/Cloudkicker_-_Loops_-_22_2011_07.mp3 -------------------------------------------------------------------------------- /static/compiler_help.txt: -------------------------------------------------------------------------------- 1 | # Ayuda del compilador de CangreBot 2 | Para leer este mensaje, concatenaste '--help' a cualquier comando bajo la sección '&code', en este caso '--help' cancela cualquier otra acción y la cambia por este menú. 3 | 4 | ## Tipos de compilación 5 | - **`code asm`:** Este tipo de compilación te permite obtener el ensamblador de un código en cualquier lenguaje, si está disponible para ese compilador. 6 | - **`code run`:** Este tipo de compilación ejecuta el código especificado y te muestra su salida respondiendo a tu mensaje. 7 | 8 | El compilador de ejecución y de ensamblado son esencialmente el mismo, por lo que las opciones para compilar también. 9 | 10 | ## Tipos de argumento 11 | Hay 2 tipos de argumento que puedes pasarle a esto. 12 | ***Prefijados con `--compiler`:*** Este tipo de argumentos será interpretado por el bot de Discord, estos modificarán el comportamiento de este. 13 | 14 | > La sintaxis para argumentos de compilador es la siguiente: `--compiler-=`. 15 | > 16 | > Los argumentos prefijados con `--compiler` que tienes disponibles son los siguientes 17 | > - `--compiler-version`: Este argumento es una versión plana, puedes usar "" si el valor tiene espacios, las versiones `trunk` están filtradas debido a incompatibilidades comunes, de todos modos puedes seguir encontrando compiladores que no se comportan como deberían. 18 | > - `--compiler-arch`: Si está soportada, este argumento te permite cambiar la arquitectura del compilador, los nombres de arquitectura dependen en el compilador, es recomendado leer la [documentación de GodBolt](). Una arquitectura bastante soportada es `amd64`. 19 | 20 | ***Argumentos de compilador:*** Basicamente, cualquier argumento que no empiece por --compiler será directamente pasado al compilador de Godbolt para alterar la compilación; en el caso de Rust, pueden ser argumentos como `--debug` o `--release`. 21 | -# Ten en cuenta que argumentos que cambien rutas pueden romper la compilación. 22 | 23 | ## Sintaxis general 24 | El comando se puede ejecutar como 25 | 26 | &code [...argumentos] 27 | \`\`\` 28 | 29 | \`\`\` 30 | -# Caracteres adicionales al bloque de código no son soportados. 31 | -------------------------------------------------------------------------------- /static/fonts/WorkSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustLangES/cangrebot/435cab9cc01fb77e3960cd977f1e484f77e4ea9b/static/fonts/WorkSans-Bold.ttf -------------------------------------------------------------------------------- /static/fonts/WorkSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustLangES/cangrebot/435cab9cc01fb77e3960cd977f1e484f77e4ea9b/static/fonts/WorkSans-Regular.ttf -------------------------------------------------------------------------------- /static/loop.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustLangES/cangrebot/435cab9cc01fb77e3960cd977f1e484f77e4ea9b/static/loop.mp3 -------------------------------------------------------------------------------- /static/ting.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustLangES/cangrebot/435cab9cc01fb77e3960cd977f1e484f77e4ea9b/static/ting.mp3 -------------------------------------------------------------------------------- /static/welcome_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RustLangES/cangrebot/435cab9cc01fb77e3960cd977f1e484f77e4ea9b/static/welcome_background.png --------------------------------------------------------------------------------